假设我们有一个线程池,线程数量有限。
Executor executor = Executors.newFixedThreadPool(3);
现在让我们说一个活动任务必须休眠3秒(无论出于何种原因)。
executor.execute(() -> {
try {
Thread.sleep(3000L);
} catch (InterruptedException ignore) {}
});
我们如何实现这样的线程池,当任务休眠时(或监视器上的waits / condition),< em> thread 1 是否可以有效地用于执行其他任务?
1 通过线程并不是指“物理” Java线程,因为当线程处于睡眠状态时这是不可能的。我的意思是,线程池具有抽象实现,该实现实际上似乎允许线程在睡眠期间运行另一个任务。关键点是,总是有N个同时运行(非睡眠)任务。
有点类似于监视器处理对关键区域的访问的方式:
答案 0 :(得分:0)
延迟后使用ScheduledExecutorService
运行任务比在线程内部使用Thread.sleep()
更好。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
executor.schedule(() -> { ... }, 3000, TimeUnit.MILLISECONDS);
但是在您的示例中,线程池只有3个线程。在达到数十个或数百个线程之前,在服务器类计算机上休眠线程不会有太大影响。
答案 1 :(得分:0)
我实现了一个最小的工作示例,基本上可以满足我的要求。
任务接口(与可运行接口非常相似,只是带有传递的上下文来执行等待)
package io.medev.stackoverflow;
import java.util.concurrent.TimeUnit;
import java.util.function.BooleanSupplier;
public interface Task {
/**
* Wraps the given runnable into a Task with a not guessable execution time (meaning guessExecutionTime always returns Long.MAX_VALUE)
* @param runnable The runnable to wrap
* @return a Task wrapping this runnable
*/
static Task wrap(Runnable runnable) {
return wrap(runnable, Long.MAX_VALUE);
}
/**
* Wraps the given runnable using the given guessedExecutionTimeMillis
* @param runnable The runnable to wrap
* @param guessedExecutionTimeMillis The guessed execution time in millis for this runnable
* @return a Task wrapping this runnable
*/
static Task wrap(Runnable runnable, long guessedExecutionTimeMillis) {
return new Task() {
@Override
public long guessExecutionTimeMillis() {
return guessedExecutionTimeMillis;
}
@Override
public void run(Context context) {
runnable.run();
}
};
}
/**
* Should more or less guess how long this task will run
* @return The execution time of this Task in milliseconds
*/
long guessExecutionTimeMillis();
void run(Context context);
interface Context {
/**
* Block until the condition is met, giving other Tasks time to execute
* @param condition the condition to check
* @throws InterruptedException if the current thread is interrupted
*/
void idle(BooleanSupplier condition) throws InterruptedException;
/**
* Blocks at least for the given duration, giving other Tasks time to execute
* @param timeout
* @param timeUnit
* @throws InterruptedException if the current thread is interrupted
*/
void idle(long timeout, TimeUnit timeUnit) throws InterruptedException;
/**
* Blocks until the condition is met or the timeout expires, giving other Tasks time to execute
* @param condition the condition to check
* @param timeout
* @param timeUnit
* @throws InterruptedException if the current thread is interrupted
*/
void idle(BooleanSupplier condition, long timeout, TimeUnit timeUnit) throws InterruptedException;
}
}
还有一个基本的固定线程池执行器-但您必须依靠这里的具体实现:
package io.medev.stackoverflow;
import java.util.Comparator;
import java.util.concurrent.*;
import java.util.function.BooleanSupplier;
public class TimeEfficientExecutor implements Executor {
private final BlockingQueue<Task> taskQueue;
private final CountDownLatch latch;
private volatile boolean alive;
public TimeEfficientExecutor(int threads) {
this.taskQueue = new PriorityBlockingQueue<>(10, Comparator.comparingLong(Task::guessExecutionTimeMillis));
this.latch = new CountDownLatch(threads);
this.alive = true;
for (int i = 0; i < threads; i++) {
Thread thread = new Thread(new TimeEfficientExecutorRunnable());
thread.start();
}
}
@Override
public void execute(Runnable runnable) {
execute(Task.wrap(runnable));
}
public void execute(Runnable runnable, long guessedExecutionTimeMillis) {
execute(Task.wrap(runnable, guessedExecutionTimeMillis));
}
public void execute(Task task) {
this.taskQueue.offer(task);
}
public void shutdown() {
this.alive = false;
}
public void awaitShutdown() throws InterruptedException {
this.latch.await();
}
public void awaitShutdown(long timeout, TimeUnit timeUnit) throws InterruptedException {
this.latch.await(timeout, timeUnit);
}
private class TimeEfficientExecutorRunnable implements Runnable {
@Override
public void run() {
try {
while (TimeEfficientExecutor.this.alive) {
Task task = TimeEfficientExecutor.this.taskQueue.poll();
if (task != null) {
try {
task.run(new IdleTaskContext());
} catch (Exception e) {
// TODO: logging
}
}
}
} finally {
TimeEfficientExecutor.this.latch.countDown();
}
}
}
private class IdleTaskContext implements Task.Context {
@Override
public void idle(BooleanSupplier condition) throws InterruptedException {
idle(condition, Long.MAX_VALUE);
}
@Override
public void idle(long timeout, TimeUnit timeUnit) throws InterruptedException {
idle(() -> false, timeout, timeUnit);
}
@Override
public void idle(BooleanSupplier condition, long timeout, TimeUnit timeUnit) throws InterruptedException {
idle(condition, System.currentTimeMillis() + timeUnit.toMillis(timeout));
}
private void idle(BooleanSupplier condition, long idleUntilTs) throws InterruptedException {
long leftMillis = idleUntilTs - System.currentTimeMillis();
while (TimeEfficientExecutor.this.alive && !condition.getAsBoolean() && leftMillis >= 1L) {
Task task = TimeEfficientExecutor.this.taskQueue.poll(leftMillis, TimeUnit.MILLISECONDS);
leftMillis = idleUntilTs - System.currentTimeMillis();
if (task != null) {
if (leftMillis >= 1L && task.guessExecutionTimeMillis() < leftMillis) {
task.run(new IdleTaskContext());
} else {
TimeEfficientExecutor.this.taskQueue.offer(task);
}
}
}
}
}
}
请注意,您不能只是逐步退出堆栈-堆栈已绑定到执行线程。这意味着,如果某些“子”任务开始闲置,则不可能跳回到底层的闲置任务。您必须“信任” guessExecutionTimeMillis
-方法中每个任务返回的内容。
由于执行器中使用了PriorityQueue,该队列将始终返回执行时间最短的任务。
答案 2 :(得分:0)
您所要的实际上是在JVM / OS线程之上实现协程/光纤。 Sanhong Li很好地谈论了阿里巴巴的工程师如何实现这种构造的方法-这个想法不是依靠OS线程调度程序,而是需要依靠自己的Selector。
另请参阅Loom project中的光纤(用户端绿色线程)。