Java 在ConcurrentMap中存储线程安全吗?
我正在构建一个后端服务,通过对我的服务的REST调用可以创建一个新线程。线程等待另一个REST调用,如果在5分钟内没有收到任何消息,线程将死亡。 为了跟踪所有线程,我有一个集合来跟踪当前运行的所有线程,这样当REST调用最终进入时(例如用户接受或拒绝某个操作),我就可以使用userID识别该线程。如果它被拒绝,我们将从集合中删除该线程,如果它被接受,则该线程可以继续执行下一个操作。我使用ConcurrentMap实现了这一点,以避免并发问题 由于这是我第一次使用线程,我想确保我没有忽视任何可能出现的问题。请看一下我的代码,告诉我是否可以做得更好,或者是否有任何缺陷Java 在ConcurrentMap中存储线程安全吗?,java,multithreading,concurrenthashmap,Java,Multithreading,Concurrenthashmap,我正在构建一个后端服务,通过对我的服务的REST调用可以创建一个新线程。线程等待另一个REST调用,如果在5分钟内没有收到任何消息,线程将死亡。 为了跟踪所有线程,我有一个集合来跟踪当前运行的所有线程,这样当REST调用最终进入时(例如用户接受或拒绝某个操作),我就可以使用userID识别该线程。如果它被拒绝,我们将从集合中删除该线程,如果它被接受,则该线程可以继续执行下一个操作。我使用ConcurrentMap实现了这一点,以避免并发问题 由于这是我第一次使用线程,我想确保我没有忽视任何可能出
public class UserAction extends Thread {
int userID;
boolean isAccepted = false;
boolean isDeclined = false;
long timeNow = System.currentTimeMillis();
long timeElapsed = timeNow + 50000;
public UserAction(int userID) {
this.userID = userID;
}
public void declineJob() {
this.isDeclined = true;
}
public void acceptJob() {
this.isAccepted = true;
}
public boolean waitForApproval(){
while (System.currentTimeMillis() < timeElapsed){
System.out.println("waiting for approval");
if (isAccepted) {
return true;
} else if (declined) {
return false;
}
}
return isAccepted;
}
@Override
public void run() {
if (!waitForApproval) {
// mustve timed out or user declined so remove from list and return thread immediately
tCollection.remove(userID);
// end the thread here
return;
}
// mustve been accepted so continue working
}
}
public class Controller {
public static ConcurrentHashMap<Integer, Thread> tCollection = new ConcurrentHashMap<>();
public static void main(String[] args) {
int barberID1 = 1;
int barberID2 = 2;
tCollection.put(barberID1, new UserAction(barberID1));
tCollection.put(barberID2, new UserAction(barberID2));
tCollection.get(barberID1).start();
tCollection.get(barberID2).start();
Thread.sleep(1000);
// simulate REST call accepting/declining job after 1 second. Usually this would be in a spring mvc RESTcontroller in a different class.
tCollection.get(barberID1).acceptJob();
tCollection.get(barberID2).declineJob();
}
}
public类UserAction扩展线程{
int用户标识;
布尔值isAccepted=false;
布尔值isDeclined=false;
long-timeNow=System.currentTimeMillis();
长时间运行=timeNow+50000;
公共用户操作(int userID){
this.userID=userID;
}
public void declineJob(){
this.isDecline=true;
}
公共作业(){
this.isAccepted=true;
}
公共布尔值waitForApproval(){
while(System.currentTimeMillis()
此操作不需要(显式)线程。只是在第一次rest调用时创建的任务对象的共享池
当第二个rest调用到来时,您已经有一个线程要使用(处理rest调用的线程)。您只需要根据用户id检索任务对象。您还需要删除过期的任务,这可以通过使用DelayQueue
来完成
伪代码:
public void rest1(User u) {
UserTask ut = new UserTask(u);
pool.put(u.getId(), ut);
delayPool.put(ut); // Assuming UserTask implements Delayed with a 5 minute delay
}
public void rest2(User u, Action a) {
UserTask ut = pool.get(u.getId());
if(!a.isAccepted() || ut == null)
pool.remove(u.getId());
else
process(ut);
// Clean up the pool from any expired tasks, can also be done in the beginning
// of the method, if you want to make sure that expired actions aren't performed
while((UserTask u = delayPool.poll()) != null)
pool.remove(u.getId());
}
有一个同步问题,您应该使类
原子布尔
的标记isAccepted
和isDeclined
。
一个关键的概念是,您需要采取步骤确保一个线程中的内存更改被传递给需要该数据的其他线程。它们被称为内存围栏,通常在同步调用之间隐式出现
对于大多数现代机器来说,带有“中央内存”的(简单的)冯·诺依曼体系结构的想法是错误的,您需要知道数据是否在缓存/线程之间正确共享
正如其他人所建议的,为每个任务创建一个线程是一个糟糕的模型。它的伸缩性很差,如果提交的任务太多,您的应用程序很容易崩溃。内存有一些限制,所以一次只能有这么多挂起的任务,但是线程的上限要低得多。
那会变得更糟,因为你在等待。自旋等待将线程放入循环中,等待条件。一个更好的模型应该等待一个条件变量
,这样没有做任何事情(除了等待)的线程可能会被操作系统挂起,直到通知他们正在等待的事情已经(或者可能已经)准备好
创建和销毁线程通常会在时间和资源上产生大量开销。考虑到大多数平台只能同时执行相对较少数量的线程,创建大量“昂贵”的线程,让它们花费大部分时间交换(挂起)什么都不做是非常低效的
正确的模型启动一个由固定数量的线程(或相对固定数量的线程)组成的池,并将任务放置在共享队列中,线程从该队列“获取”工作并进行处理
该模型一般称为“线程池”。
您应该查看的入门级实现是ThreadPoolExecutor
:
不要扩展
线程。改为实现Runnable
或Callable
。使用ExecutorService
运行这些服务。对于刚开始使用线程的人来说,你太聪明了CompletableFuture
可能也适用于此。是的,您应该使用ExecutorService
和java.util.concurrent
中的高级类,尤其是当您仍然掌握了一些窍门的时候。很可能你的app/http容器也有处理并发性的功能,所以你应该仔细阅读,而不是重新设计它,很可能是以一种失败的方式。为什么rest调用“创建线程”?它已经在线程中运行。真的不清楚你想要完成什么。甚至不清楚它是否需要线程。我认为这里根本不需要线程。只是一个等待另一个REST调用来接受它(或拒绝它,或被删除)的对象