Java 如何避免在不阻塞的情况下并发执行耗时的任务?
我想有效地避免在多线程环境中并发执行耗时的任务,而不会在另一个线程已经在运行该任务时让线程等待锁定。相反,在这种情况下,我希望他们尽快优雅地失败(即跳过执行任务的尝试)。换言之:我需要在任务已在进行时再次尝试启动任务,以便立即退出,最好是不需要同步成本 为了说明此不安全(有竞争条件!)代码的想法: 我考虑使用双重检查锁定的一种变体(考虑到Java 如何避免在不阻塞的情况下并发执行耗时的任务?,java,concurrency,synchronization,Java,Concurrency,Synchronization,我想有效地避免在多线程环境中并发执行耗时的任务,而不会在另一个线程已经在运行该任务时让线程等待锁定。相反,在这种情况下,我希望他们尽快优雅地失败(即跳过执行任务的尝试)。换言之:我需要在任务已在进行时再次尝试启动任务,以便立即退出,最好是不需要同步成本 为了说明此不安全(有竞争条件!)代码的想法: 我考虑使用双重检查锁定的一种变体(考虑到running是一个原始的32位字段,因此是原子的,即使对于低于5的Java,它也可以很好地工作,而不需要volatile)。它可能是这样的: private
running
是一个原始的32位字段,因此是原子的,即使对于低于5的Java,它也可以很好地工作,而不需要volatile
)。它可能是这样的:
private static boolean running = false;
private static Object execLock = new Object();
public void launchExpensiveTask() {
if (running) return; // Do nothing
synchronized (execLock) {
if (running) return;
running = true;
try {
runExpensiveTask();
} finally {
running = false;
}
}
}
也许我也应该使用该字段的本地副本(现在不确定,请告诉我)
但后来我意识到,无论如何,我将以一个内部同步块结束,该块仍然可以在监视器入口保持一个具有正确计时的线程,直到原始执行器离开关键部分(我知道可能性通常很小,但在这种情况下,我们考虑在几个线程中竞争这个长时间运行的资源)
那么,你能想出更好的办法吗
EDIT:我之前省略了部分上下文,为了保证正确性,我需要在执行过程中维护一个锁,以保持试图更改某些内部共享状态的其他方法。公平地说,到目前为止,我选择了有用的答案,包括两种情况:启动任务后是否需要锁。我认为这更有意义:
static volatile Boolean running = false;
public static void launchTask()
{
synchronized(running)
{
if(running) return;
running = true;
}
//DOSTUFF
running = false;
}
因为您实际上只需要在设置布尔值时同步:如果多个线程同时请求,第一个线程将设置为running为true,其余线程将全部返回
但是,您的设计可能有更好的总体模式。如果线程向队列提交请求,(ExecutorService?)获得Future或ListenableFuture(来自Guava)对象,然后继续执行其他操作,直到futures完成计算,该怎么办 我认为这更有意义:
static volatile Boolean running = false;
public static void launchTask()
{
synchronized(running)
{
if(running) return;
running = true;
}
//DOSTUFF
running = false;
}
因为您实际上只需要在设置布尔值时同步:如果多个线程同时请求,第一个线程将设置为running为true,其余线程将全部返回
但是,您的设计可能有更好的总体模式。如果线程向队列提交请求,(ExecutorService?)获得Future或ListenableFuture(来自Guava)对象,然后继续执行其他操作,直到futures完成计算,该怎么办 借助(Java 5提供的API),我们可以实现无阻塞:
private static boolean running = false;
private static Lock execLock = new ReentrantLock();
public void launchExpensiveTask() {
if (running) return; // fast exit without sync
if (!execLock.tryLock()) return; // quit if lock is not free
try {
running = true;
runExpensiveTask();
} finally {
running = false;
execLock.unlock();
}
}
如果在任务执行期间不需要持有锁,请查看以下代码:
private static boolean running = false;
private static Object execLock = new Object();
private boolean start() {
synchronized (execLock) {
boolean ret = running;
running = true;
return ret;
}
}
private void end() {
synchronized (execLock) {
running = false;
}
}
public void launchExpensiveTask() {
if (running) return; // fast exit without sync
if (start()) return; // already running, do nothing
try {
runExpensiveTask();
} finally {
end();
}
}
借助(自Java 5开始提供的API),我们可以实现无阻塞:
private static boolean running = false;
private static Lock execLock = new ReentrantLock();
public void launchExpensiveTask() {
if (running) return; // fast exit without sync
if (!execLock.tryLock()) return; // quit if lock is not free
try {
running = true;
runExpensiveTask();
} finally {
running = false;
execLock.unlock();
}
}
如果在任务执行期间不需要持有锁,请查看以下代码:
private static boolean running = false;
private static Object execLock = new Object();
private boolean start() {
synchronized (execLock) {
boolean ret = running;
running = true;
return ret;
}
}
private void end() {
synchronized (execLock) {
running = false;
}
}
public void launchExpensiveTask() {
if (running) return; // fast exit without sync
if (start()) return; // already running, do nothing
try {
runExpensiveTask();
} finally {
end();
}
}
更新:与问题负责人讨论后,以下是最终建议的解决方案,代码为:
package toys;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TwoQueues {
//tweak it for your purpose.
private final int CPU_COUNT = 4;
private BlockingQueue<Runnable> lightTaskQueue = new LinkedBlockingDeque<Runnable>();
private ThreadPoolExecutor lightExecutor = new ThreadPoolExecutor(CPU_COUNT, CPU_COUNT, 60L, TimeUnit.SECONDS, lightTaskQueue);
private BlockingQueue<Runnable> heavyTaskQueue = new LinkedBlockingDeque<Runnable>();
private ThreadPoolExecutor heavyExecutor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, heavyTaskQueue);
public static class SampleLightTask implements Runnable {
@Override
public void run() {
System.out.println("I am " + this + " and running fast!");
}
}
private static AtomicBoolean heavyTaskRunning = new AtomicBoolean();
public static class SampleHeavyTask implements Runnable {
@Override
public void run() {
try {
heavyTaskRunning.set(true);
System.out.println("I am " + this + " and running quite slow!");
final long start = System.currentTimeMillis();
while (true) {
//burn the CPU for ten senconds.
if (System.currentTimeMillis()-start >= 10000L)
break;
}
} finally {
heavyTaskRunning.set(false);;
}
}
}
public void shutDownNow() {
this.lightExecutor.shutdownNow();
this.heavyExecutor.shutdownNow();
}
public void runOrQueueLightTask(SampleLightTask lightOne) {
this.lightExecutor.execute(lightOne);
}
public void runOrQueueHeavyTask(SampleHeavyTask heavyOne) {
if (heavyTaskRunning.get()) {
System.out.println("running, skipped new one: " + heavyOne);
return;
}
this.heavyExecutor.execute(heavyOne);
}
public static void main(String[] args) throws Exception {
TwoQueues q = new TwoQueues();
final long start = System.currentTimeMillis();
//Run the queues for 30 seconds, add CPU-light and CPU-weight tasks
//every second.
while (System.currentTimeMillis()-start<=30*1000L) {
q.runOrQueueHeavyTask(new SampleHeavyTask());
q.runOrQueueLightTask(new SampleLightTask());
Thread.sleep(1000L);
}
q.shutDownNow();
}
}
//////////////////////////////////旧答案////////////////////////////////////
如果我正确理解了您的需求,那么有两种类型的任务:类型A,CPU密集型,以串行方式执行,以及可能修改一些非线程安全的全局状态;类型B,不是CPU密集型,需要尽快完成它们
为什么不使用两个和两个队列呢?这是一对完美的组合。对于类型A的任务,在最大并发设置为1的线程池中调度它们;对于类型B,在另一个线程池中,最大并发性设置为CPU核心/线程数或任何适合您需要的值。这里甚至不需要“检查并优雅地失败”
我曾经自己编写过很多这些原始的、低级并发的线程,但是当线程池成为JDK中的标准库时,我再也不会回去了,无论是在服务器端(EE)还是客户端(这里是Android)。设计是干净的,性能是好的,更少的代码,当然,更少的bug。调试与并发相关的bug从来都不是一件容易的事。更新:在与问题所有者讨论之后,这里是最终建议的解决方案,代码如下:
package toys;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class TwoQueues {
//tweak it for your purpose.
private final int CPU_COUNT = 4;
private BlockingQueue<Runnable> lightTaskQueue = new LinkedBlockingDeque<Runnable>();
private ThreadPoolExecutor lightExecutor = new ThreadPoolExecutor(CPU_COUNT, CPU_COUNT, 60L, TimeUnit.SECONDS, lightTaskQueue);
private BlockingQueue<Runnable> heavyTaskQueue = new LinkedBlockingDeque<Runnable>();
private ThreadPoolExecutor heavyExecutor = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, heavyTaskQueue);
public static class SampleLightTask implements Runnable {
@Override
public void run() {
System.out.println("I am " + this + " and running fast!");
}
}
private static AtomicBoolean heavyTaskRunning = new AtomicBoolean();
public static class SampleHeavyTask implements Runnable {
@Override
public void run() {
try {
heavyTaskRunning.set(true);
System.out.println("I am " + this + " and running quite slow!");
final long start = System.currentTimeMillis();
while (true) {
//burn the CPU for ten senconds.
if (System.currentTimeMillis()-start >= 10000L)
break;
}
} finally {
heavyTaskRunning.set(false);;
}
}
}
public void shutDownNow() {
this.lightExecutor.shutdownNow();
this.heavyExecutor.shutdownNow();
}
public void runOrQueueLightTask(SampleLightTask lightOne) {
this.lightExecutor.execute(lightOne);
}
public void runOrQueueHeavyTask(SampleHeavyTask heavyOne) {
if (heavyTaskRunning.get()) {
System.out.println("running, skipped new one: " + heavyOne);
return;
}
this.heavyExecutor.execute(heavyOne);
}
public static void main(String[] args) throws Exception {
TwoQueues q = new TwoQueues();
final long start = System.currentTimeMillis();
//Run the queues for 30 seconds, add CPU-light and CPU-weight tasks
//every second.
while (System.currentTimeMillis()-start<=30*1000L) {
q.runOrQueueHeavyTask(new SampleHeavyTask());
q.runOrQueueLightTask(new SampleLightTask());
Thread.sleep(1000L);
}
q.shutDownNow();
}
}
//////////////////////////////////旧答案////////////////////////////////////
如果我正确理解了您的需求,那么有两种类型的任务:类型A,CPU密集型,以串行方式执行,以及可能修改一些非线程安全的全局状态;类型B,不是CPU密集型,需要尽快完成它们
为什么不使用两个和两个队列呢?这是一对完美的组合。对于类型A的任务,在最大并发设置为1的线程池中调度它们;对于类型B,在另一个线程池中,最大并发性设置为CPU核心/线程数或任何适合您需要的值。这里甚至不需要“检查并优雅地失败”
我曾经自己编写过很多这些原始的、低级并发的线程,但是当线程池成为JDK中的标准库时,我再也不会回去了,无论是在服务器端(EE)还是客户端(这里是Android)。设计是干净的,性能是好的,更少的代码,当然,更少的bug。调试与并发相关的bug从来都不容易。忽略我的另一个答案。但你要找的是这个。 通过使用信号量。一个信号量的最简单的方法是考虑它是一个抽象,允许N个单元被获取,并提供获取和释放机制。TryAcquire是关键,因为根据java的文档,它从这个信号量获取许可,只有在调用时有一个信号量可用时才可以。您自己试试
private Semaphore semaphore = new Semaphore(1);
public void launchExpensiveTask() {
if (semaphore.tryAcquire()) {
try {
runExpensiveTask();
} finally {
semaphore.release();
}
}
}
无视我的另一个答案。B