我有一个Manager类,多个线程自己注册(使用UUID
为每个请求生成唯一标识符),提供有效负载处理并从管理器获取相应的响应。我正在使用java.util.concurrent.ExecutorService
来启动多个线程。以下是测试Manager功能的实现 -
public class ManagerTest {
public static void main(String[] args) {
try {
Manager myManager = new Manager();
// Start listening to the messages from different threads
myManager.consumeMessages();
int num_threads = Integer.parseInt(args[0]);
ExecutorService executor = Executors.newFixedThreadPool(num_threads);
for (int i = 0; i < num_threads; i++) {
// class implementation is given below
Runnable worker = new MyRunnable(myManager);
executor.execute(worker);
}
executor.shutdown();
// Wait until all threads are finish
while (!executor.isTerminated()) {
}
System.out.println("\nFinished all threads");
myManager.closeConnection();
} catch (IOException | TimeoutException e) {
e.printStackTrace();
}
}
}
以下是MyRunnable
类
class MyRunnable implements Runnable {
private Manager managerObj;
public MyRunnable(Manager managerObj) {
this.managerObj = managerObj;
}
@Override
public void run() {
try {
Random rand = new Random();
int n = rand.nextInt(35);
String requestId = UUID.randomUUID().toString();
managerObj.registerRequest(requestId, n);
managerObj.publishMessage(requestId);
// Want to avoid this while loop
while( ! managerObj.getRequestStatus(requestId)){
}
int response = managerObj.getRequestResponse(requestId);
// do something else
managerObj.unregisterRequest(requestId);
} catch (IOException e) {
e.printStackTrace();
}
}
}
管理员将处理请求,并且根据有效负载,请求的响应可能需要不同的时间。每当manager获得响应时,它通过调用此函数setRequestStatus(requestId)
将请求状态设置为true。在此之后,线程将从while loop
退出并继续执行。
代码如果工作正常,但是线程正在做太多工作,然后需要通过连续循环while循环直到满足条件。
在将请求发送给管理器之后,它们是一种使线程休眠的方法,并且管理器会在响应准备就绪时通知此线程唤醒。
请原谅我,如果这听起来对某人来说太简单了,我是java和java-threading界面的新手。
答案 0 :(得分:3)
没关系,我们习惯于简单的问题,你的问题实际上写得很好并且有一个合理的问题需要解决,我们每天看到的情况要糟糕得多,就像人们不知道他们想要什么,如何要求它等等。
所以,你正在做的是一个忙碌的自旋循环,这是一个非常糟糕的事情,因为a)它每个线程消耗一个完整的CPU核心,而b)它实际上让CPU忙碌,意味着它正在从可能有用的工作的其他线程中窃取处理时间。
有很多方法可以解决这个问题,我会将它们从最差到最好的列出。
改进代码的最简单方法是调用java.lang.Thread.sleep(long millis)
方法,并将其作为参数传递给0
。这也称为“yield”操作,它实质上意味着“如果有任何其他线程有一些有用的工作要做,让它们运行,并在完成后返回给我。”这仅比忙碌循环略好,因为它仍将消耗100%的CPU。好处是它只消耗CPU,而其他线程没有任何事情可做,所以至少它不会减慢其他事情。
改进代码的下一个最佳但仍然不是很聪明的方法是调用java.lang.Thread.sleep(long millis)
方法将其作为参数传递给它1
。这被称为“传递”操作,它实质上意味着“将我的时间片的剩余部分释放到可能有一些有用工作要做的任何其他线程”。换句话说,即使在整个系统中不需要进行任何有用的工作,时间片的剩余部分也会被没收。这将使CPU消耗降至几乎为零。缺点是a)CPU消耗实际上略高于零,b)机器将无法进入某种低功耗睡眠模式,并且c)您的工作线程的响应性稍差:它会恢复只在时间片边界上工作。
解决问题的最佳方法是使用java中内置的同步机制,如本答案中所述:https://stackoverflow.com/a/5999146/773113这不仅会消耗零CPU,而且它甚至可以让机器在等待时进入低功耗模式。
为了解决最常见的问题,你不想等到条件,但实际上也要传递有关工作的信息,你会使用BlockingQueue
。 (https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/BlockingQueue.html)阻塞队列使用java内置同步机制和允许一个线程将信息传递给另一个线程。
答案 1 :(得分:0)
@MikeNakis已经有了很好的答案,但我也倾向于提供其他选择。
但是,此选项涉及更改Manager API以返回 Future 。
我建议的经理的改变是这些:
getRequestStatus()
方法getRequestResponse()
返回Future<Integer>
这些更改MyRunnable
的{{1}}方法可以更改为:
run()
实现public void run() {
try {
Random rand = new Random();
int n = rand.nextInt(35);
String requestId = UUID.randomUUID().toString();
managerObj.registerRequest(requestId, n);
managerObj.publishMessage(requestId);
// Future.get() blocks and waits for the result without consuming CPU
int response = managerObj.getRequestResponse(requestId).get();
// do something else
managerObj.unregisterRequest(requestId);
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
的最简单方法可能是使用Java的java.util.concurrent.FutureTask
。