Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/311.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Java 如何避免在不阻塞的情况下并发执行耗时的任务?_Java_Concurrency_Synchronization - Fatal编程技术网

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