为什么Java 多线程代码死锁

Why does Java muti-threaded code deadlock

此问题的完整代码可在此处获得: https://github.com/NACHC-CAD/thread-tool

下面显示的代码似乎 运行 完成但从未逃脱此处显示的 while 循环。

    while (this.active.size() > 0) {
        // System.out.println("here");
    }

如果我取消对 // System.out.println("here"); 的注释,它会 运行 完成。如果我添加 sleep-for-one-second,它也会运行完成。

更大的问题是,如果我尝试在实际应用程序中使用此代码,代码会运行一段时间然后似乎死锁(即代码只是停止 运行)。

我需要做什么来解决这个问题?

package org.nachc.tools.threadtool;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.nachc.tools.threadtool.runnableiter.ThreadToolUser;
import org.nachc.tools.threadtool.worker.ThreadToolWorker;
import org.nachc.tools.threadtool.worker.ThreadToolWorkerRunnable;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class ThreadRunner {

    private int numberOfThreadsPerWorker;

    private int numberOfRunnablesPerWorker;

    private int numberOfWorkers;

    private ThreadToolUser runnableIter;

    private List<ThreadToolWorker> active = new ArrayList<ThreadToolWorker>();

    private Object lock = new Object();

    private ThreadPoolExecutor executor;

    public ThreadRunner(int numberOfThreadsPerWorker, int numberOfRunnablesPerWorker, int numberOfWorkers, ThreadToolUser runnableIter) {
        this.numberOfThreadsPerWorker = numberOfThreadsPerWorker;
        this.numberOfRunnablesPerWorker = numberOfRunnablesPerWorker;
        this.numberOfWorkers = numberOfWorkers;
        this.runnableIter = runnableIter;
        this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numberOfWorkers);
    }

    public void exec() {
        synchronized (lock) {
            addWorkers();
        }
        while (this.active.size() > 0) {
            // System.out.println("here");
        }
        log.info("SHUTTING DOWN----------------");
        executor.shutdown();
        try {
            executor.awaitTermination(1000, TimeUnit.HOURS);
        } catch (Exception exp) {
            throw (new RuntimeException(exp));
        }
    }

    private void addWorkers() {
        log.info("start addWorkers");
        while (runnableIter.hasNext() && active.size() < numberOfWorkers) {
            ThreadToolWorker worker = getNextWorker();
            if (worker == null) {
                break;
            } else {
                this.active.add(worker);
            }
        }
        log.info("done addWorkers");
    }

    private ThreadToolWorker getNextWorker() {
        synchronized (lock) {
            log.info("start next worker");
            if (runnableIter.hasNext() == false) {
                return null;
            }
            List<Runnable> runnableList = new ArrayList<Runnable>();
            while (runnableList.size() < numberOfRunnablesPerWorker && runnableIter.hasNext()) {
                runnableList.add(runnableIter.getNext());
            }
            ThreadToolWorker worker = new ThreadToolWorker(runnableList, numberOfThreadsPerWorker, this);
            ThreadToolWorkerRunnable runnable = new ThreadToolWorkerRunnable(worker);
            this.executor.execute(runnable);
            log.info("done next worker");
            return worker;
        }
    }

    public void done(ThreadToolWorker worker) {
        synchronized (lock) {
            log.info("start done");
            this.active.remove(worker);
            if (active.size() > 0) {
                addWorkers();
            }
            log.info("done done");
        }
    }

    public void logActive() {
        synchronized (lock) {
            log.info("------------");
            log.info("active:  " + active.size());
            log.info("waiting: " + runnableIter.waiting());
            log.info("------------");
        }
    }

}

您的循环在没有任何同步的情况下访问 this.active.size()。 Java 的 memory visibility rules 不保证何时(如果 ever)一个查看共享变量的线程将看到其他线程所做的更改,如果正在查看的线程确实不与其他线程使用某种形式的同步。


如果你想 poll active 列表的状态,那么考虑做这样的事情:

while (true) {
    synchronized(lock) {
        if (this.active.size() <= 0) break;
    }
    try {
        Thread.sleep(1000);
    }
    catch (InterruptedException ex) {
        ex.printStackTrace();
    }
}

sleep() 调用让其他线程有机会 运行。如果没有睡眠,运行 执行此循环的线程将使用 100% 的 CPU,这可能会损害应用程序的性能。


但是还有比轮询更好的选择。您可以执行此操作以等待 active 列表变空。

synchronized(lock) {
    while (this.active.size() > 0) {
        lock.wait();
    }
}

wait() call temporarily releases the lock, then it waits for another thread to notify() 锁,最后 re-locks 它之前的锁 returns:

public void done(ThreadToolWorker worker) {
    synchronized (lock) {
        log.info("start done");
        this.active.remove(worker);
        if (active.size() > 0) {
            addWorkers();
        }
        else {
            lock.notifyAll();
        }
        log.info("done done");
    }
}