如何在不阻塞的情况下避免并发执行耗时的任务?

时间:2014-06-03 03:02:37

标签: java concurrency synchronization

我希望有效避免在多线程环境中并发执行耗时的任务,而不会在另一个线程已经运行任务时让线程等待锁定。相反,在那种情况下,我希望它们优先失败(即跳过尝试执行任务)尽可能快。换句话说:我需要尝试再次启动任务,当它正在进行中立即退出时,最好没有同步成本。

为了说明这个想法,这个不安全(有竞争条件!)代码:

private static boolean running = false;

public void launchExpensiveTask() {
    if (running) return; // Do nothing

    running = true;
    try {
        runExpensiveTask();
    } finally {
        running = false;
    }
}

我虽然使用了Double-Checked Locking的变体(考虑running是一个原始的32位字段,因此是原子的,即使对于低于5的Java也可以正常工作,而不需要{{1} })。它看起来像这样:

volatile

也许我也应该使用该字段的本地副本(现在不确定,请告诉我)。

然后我意识到无论如何我会以一个内部同步块结束,它仍然可以在监视器入口处保持一个正确时间的线程,直到原始执行器离开临界区(我知道几率通常很小但在此我们正考虑在几个线程中竞争这个长期运行的资源。)

那么,你能用更好的方法思考吗?

编辑:我之前省略了部分上下文,为了正确起见,我需要在执行期间保持锁定以保持其他方法尝试更改某些内部共享状态。为了公平起见,我迄今为止提出了有用的答案,包括两种情况:在启动任务后有或没有锁定。

5 个答案:

答案 0 :(得分:3)

我认为这更有意义:

 static volatile Boolean running = false;

    public static void launchTask()
    {
        synchronized(running)
        {
            if(running) return;
            running = true;
        }
            //DOSTUFF
            running = false;
    }

因为你真的只需要在设置布尔值时进行同步:如果多个线程同时询问,第一个线程将设置为true并且其余线程全部返回。

但是,您的设计可能会有更好的整体模式。如果线程向队列提交请求(一个ExecutorService?)得到Future或ListenableFuture(来自Guava)对象,然后继续做其他东西直到期货完成计算怎么办?

答案 1 :(得分:1)

借助Lock#tryLock()(自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();
    }
}

答案 2 :(得分:1)

忽略我的另一个答案。但你要找的是这个。 http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/Semaphore.html

使用信号量。考虑信号量的最简单方法是将其视为一种抽象,允许获取n个单位,并提供获取和释放机制。 TryAcquire是关键,因为根据java的文档 - 从这个信号量获取许可,只有在调用时可用。请自己尝试。

    private Semaphore semaphore = new Semaphore(1);

    public void launchExpensiveTask() {
        if (semaphore.tryAcquire()) {
            try {
               runExpensiveTask();
            } finally {
               semaphore.release();
            }
        }

    }

答案 3 :(得分:1)

请注意,对于大多数人来说,以下解决方案更为可取:

private static Lock execLock = new ReentrantLock();

public void launchExpensiveTask() {
    if (!execLock.tryLock()) return; // skip if already running

    try {
        runExpensiveTask();
    } finally {
        lock.unlock();
    }
}

请注意,像这个问题中暴露的特殊情况非常罕见,通常同步原语的性能绰绰有余,代码越简单越好。 避免过早优化,除非您确定它不适合您,否则请使用此功能。

答案 4 :(得分:0)

更新:在与问题所有者讨论后,这是最终提出的解决方案,代码为:

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();
}
}

正在运行的输出:

I am toys.TwoQueues$SampleHeavyTask@6d0cecb2 and running quite slow!
I am toys.TwoQueues$SampleLightTask@6b87d20c and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@2ce07e6b
I am toys.TwoQueues$SampleLightTask@7fa0d111 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@16fdf48d
I am toys.TwoQueues$SampleLightTask@5fbd7d0e and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@115d533d
I am toys.TwoQueues$SampleLightTask@59c27402 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@6d4e5d57
I am toys.TwoQueues$SampleLightTask@33d232d1 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@79ec3264
I am toys.TwoQueues$SampleLightTask@1e081c5 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@3a67ad79
I am toys.TwoQueues$SampleLightTask@6cae00e3 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@13bc6ed3
I am toys.TwoQueues$SampleLightTask@380fe8c4 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@1c7ab89d
I am toys.TwoQueues$SampleLightTask@3cee5a06 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@44585f2a
I am toys.TwoQueues$SampleLightTask@5cfe174 and running fast!
I am toys.TwoQueues$SampleLightTask@12da89a7 and running fast!
I am toys.TwoQueues$SampleHeavyTask@49833c9c and running quite slow!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@47004b78
I am toys.TwoQueues$SampleLightTask@645ad7b2 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@8071a97
I am toys.TwoQueues$SampleLightTask@a62b39f and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@55fe910c
I am toys.TwoQueues$SampleLightTask@3be4d6ef and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@2cdb03a1
I am toys.TwoQueues$SampleLightTask@5ecb5608 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@777d57d6
I am toys.TwoQueues$SampleLightTask@4611dfe3 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@3f81d405
I am toys.TwoQueues$SampleLightTask@6486b4d5 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@47ca3f82
I am toys.TwoQueues$SampleLightTask@2f0f94a0 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@27e6ac83
I am toys.TwoQueues$SampleLightTask@1947e0ec and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@3dffb2eb
I am toys.TwoQueues$SampleLightTask@5e3b8219 and running fast!
I am toys.TwoQueues$SampleLightTask@14da67a4 and running fast!
I am toys.TwoQueues$SampleHeavyTask@eca4aae and running quite slow!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@2eced18
I am toys.TwoQueues$SampleLightTask@10c1c428 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@213526b0
I am toys.TwoQueues$SampleLightTask@287efdd8 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@294b84ad
I am toys.TwoQueues$SampleLightTask@1cf38f09 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@3a33a6b8
I am toys.TwoQueues$SampleLightTask@150697e2 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@63dd8136
I am toys.TwoQueues$SampleLightTask@634e3372 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@2313b44d
I am toys.TwoQueues$SampleLightTask@62a23d38 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@9615a1f
I am toys.TwoQueues$SampleLightTask@5663ae08 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@2a36bb87
I am toys.TwoQueues$SampleLightTask@6f51b1b7 and running fast!
running, skipped new one: toys.TwoQueues$SampleHeavyTask@5c6a9e79
I am toys.TwoQueues$SampleLightTask@5bca4955 and running fast!

//////////////////////////////////旧答案/////////// /////////////////////////

如果我正确理解您的要求,有两种类型的任务:类型A,CPU密集型,以连续方式执行,并可能修改一些非线程安全的全局状态;键入B,而不是CPU密集型,并且需要尽快完成它们。

为什么不为此使用两个thread pools和两个队列?这是一场完美的比赛。对于类型A的任务,将它们安排在线程池中,并将最大并发数设置为1;对于类型B,在另一个线程池中,将最大并发数设置为CPU核心/线程数或适合您需要的任何内容。这里甚至不需要“检查和优先失败”。

我曾经自己写过很多这些原始的,低级别的并发,线程的东西,但是线程池成为JDK中的标准库的时候,我再也不会回到服务器端(EE)和客户端(Android在这里)。设计干净,性能良好,代码少得多,当然还有更少的错误。调试与并发相关的错误绝非易事。