Java 为我的API的每个用户创建一个线程会很好地扩展吗?

Java 为我的API的每个用户创建一个线程会很好地扩展吗?,java,multithreading,timer,api-design,Java,Multithreading,Timer,Api Design,我正在构建一个将与JavaSpark中的SpotifyAPI交互的API。我使用了令牌管理的授权代码流,这意味着令牌将在一个小时内有效(对于给定的用户),然后需要刷新 对于每个连接Spotify帐户的用户,我都会创建一个计时器,用于检查该用户在50分钟后是否处于活动状态: 如果是->我刷新用户的令牌。 如果否->我将用户连同用户的令牌一起删除,这意味着如果他们想使用我的服务(出于存储目的),他们必须再次登录 我还保存了一个包含用户对象的HashMap,其中包含来自每个用户的各种信息,如他们的个人

我正在构建一个将与JavaSpark中的SpotifyAPI交互的API。我使用了令牌管理的授权代码流,这意味着令牌将在一个小时内有效(对于给定的用户),然后需要刷新

对于每个连接Spotify帐户的用户,我都会创建一个计时器,用于检查该用户在50分钟后是否处于活动状态:

如果是->我刷新用户的令牌。 如果否->我将用户连同用户的令牌一起删除,这意味着如果他们想使用我的服务(出于存储目的),他们必须再次登录

我还保存了一个包含用户对象的HashMap,其中包含来自每个用户的各种信息,如他们的个人资料名称、图像、播放列表等。如果计时器的检查证明该用户处于非活动状态,该信息也会从HashMap中删除

问题: 每个计时器对象都创建一个新线程。理论上,如果有成千上万的用户使用我的服务,那么就会有成千上万的线程。。。我的直觉告诉我这是不可接受的。我似乎不能对这件事耿耿于怀。我应该如何跟踪每个用户50分钟的时间,同时保持尽可能少的线程,而不是API“过度供电”?任何提示都将不胜感激

代码:

我为每个新用户创建一个此类的新实例,并调用startAutomaticProcess()方法

为我的API的每个用户创建一个线程会很好地扩展吗

显然不是

每个线程都有一个线程堆栈,使用至少64K字节,默认情况下为1MB;见:

因此,如果用户数量增加,您将耗尽内存。这是不可伸缩的

此外,每次执行刷新时,每个线程都需要唤醒。这需要2个上下文切换和相关的开销

建议:

  • 创建一个
    UserToken
    类,该类表示每个用户令牌,并包含上次检查令牌的时间戳
  • 创建按令牌时间戳排序的
    PriorityQueue
  • 使用
    TimerTask
    从优先级队列中删除需要检查的
    UserToken
    对象
  • 当检查成功(即用户仍处于活动状态)时,更新时间戳并将
    UserToken
    重新添加到队列中
这种方法需要更好的规模。假设
N
是经过身份验证的用户数:

  • 只有一个线程,而不是
    N
    线程和
    TimerTask
    对象
  • 线程需要每
    M
    分钟唤醒一次,而不是
    N
    线程都需要每
    M2
    分钟唤醒一次
  • 它需要每个活动用户少于500字节1,而不是64K(最小值)
  • 优先级队列插入/重新插入成本低廉,可扩展为
    O(logN)


1-空间由
UserToken
对象及其子对象以及优先级队列中的内部“节点”组成。100到200字节是一个更好的估计值,尽管这将是特定于实现的。

线程池。您可能还需要查看光纤。@m0skit0您好,谢谢您的快速回复。我曾经考虑过线程池——但这不也会带来一些限制吗?因为每个计时器对象都需要一个线程,有一个池,比如说20个线程,那么对于前20个用户来说,这些线程不是都很忙吗?现在我将阅读关于线程池的内容,也许我会找到答案。“当需要多个工作线程时,或者当需要ThreadPoolExecutor(该类扩展)的额外灵活性或功能时,该类比Timer更可取”,或者反过来说:让一个进程检查所有“用户会话”每隔一分钟左右,查看哪些用户需要令牌刷新,并在需要时启动任务(进入线程池)进行刷新。我可能在这一点上错了,但你为什么要这样做?您可以在需要时刷新令牌。意思是:用户或您的程序请求一些东西,您有令牌的到期日期,如果它过期,您将获得一个新的令牌。但你不会贪婪地刷新那些代币。这不是它的工作原理。无论是否需要,我(个人)都会过度思考整个算法。谢谢你的精彩而清晰的描述!第一次使用API——这对我的结构有帮助。干杯
package Authentication;

import Spotify.Users.UserSessions;
import java.util.Date;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;

public class RefreshTokens extends TimerTask {
    private UserSessions userSessions;
    private Authentication authentication;
    private String currentUserSession;
    private Timer timer = new Timer(true);

    public RefreshTokens(UserSessions userSessions, Authentication authentication, String currentUserSession) {
        this.userSessions = userSessions;
        this.authentication = authentication;
        this.currentUserSession = currentUserSession;
    }

    public void startAutomaticProcess() {
        timer.schedule(this, 20000, 20000); //runs every 20 seconds for testing purposes
    }

    @Override
    public void run() {
        System.out.println("Automatic process started: " + new Date());
        refresh();
    }

    private void refresh() {
        if (userSessions.contains(currentUserSession)) {
            if (userSessions.get(currentUserSession).isActive()) {
                authentication.refreshToken(userSessions.get(currentUserSession));
            } else {
                System.out.println("User was not active enough and has been removed from the server.");
                System.out.println("----------");
                System.out.println("Size of HashMap before: " + userSessions.getHashMap().size());
                userSessions.getHashMap().remove(currentUserSession);
                System.out.println("Size of HashMap after: " + userSessions.getHashMap().size());
                timer.cancel();
                timer.purge();
            }
        }
    }
}