使用wait / notifyAll正确同步Java线程?

时间:2008-12-08 21:13:28

标签: java concurrency thread-safety

这是我的应用程序的简化版本,显示了我正在做的事情。

/*
in my app's main():

    Runner run = new Runner();

    run.dowork();

*/

class Runner
{
    private int totalWorkers = 2;
    private int workersDone = 0;

    public synchronized void workerDone()
    {
        workersDone++;
        notifyAll();
    }

    public synchronized void dowork()
    {
        workersDone = 0;

        //<code for opening a file here, other setup here, etc>

        Worker a = new Worker(this);
        Worker b = new Worker(this);

        while ((line = reader.readLine()) != null)
        {
            //<a large amount of processing on 'line'>

            a.setData(line);
            b.setData(line);

            while (workersDone < totalWorkers)
            {
                wait();
            }               
        }
    }
}

class Worker implements Runnable
{
    private Runner runner;
    private String data;

    public Worker(Runner r)
    {
        this.runner = r;
        Thread t = new Thread(this);
        t.start();
    }

    public synchronized void setData(String s)
    {
        this.data = s;
        notifyAll();
    }

    public void run
    {
        while (true)
        {
            synchronized(this)
            {
                wait();

                //<do work with this.data here>

                this.runner.workerDone();
            }
        }
    }
}

这里的基本概念是我有一堆工作人员都对传入的数据线进行一些处理,所有这些工作都是独立的,并且可以在任何他们喜欢的地方写出数据 - 他们不需要将任何数据报告给主线程或彼此共享数据。

我遇到的问题是此代码死锁。我正在阅读一个超过100万行的文件,我很幸运在我的应用程序停止响应之前有100行。

实际上,工人们都做了不同的工作,所以我想等到他们都完成工作,然后再转到下一行。

我不能让工作人员以不同的速度处理并在内部排队数据,因为我正在处理的文件太大而且不适合内存。

我不能给每个工作者自己的FileReader来独立获取'line',因为我在工作人员看到它之前在线上进行了大量的处理,并且不想在每个worker中重新进行处理。

我知道我在Java中缺少一些相当简单的同步方面,但我仍然坚持这一点。如果有人能解释我在这里做错了什么我会很感激。我相信我误解了同步的某些方面,但我没有尝试修复它的想法。

2 个答案:

答案 0 :(得分:3)

直接使用synchronizedwait()notify()非常棘手。

幸运的是,Java Concurrency API为这类更直观的事物提供了一些出色的控制对象。请特别注意CyclicBarrierCountDownLatch;其中一个几乎肯定会是你想要的。

您可能还会发现ThreadPoolExecutor对于这种情况很方便。

这是一个简单的示例/转换您的代码段,产生以下输出(当然没有死锁):

  

读取行:第1行   等待工作在线完成:第1行   在线工作:第1行   在线工作:第1行   读行:第2行   等待工作在线完成:第2行   在线工作:2号线   在线工作:2号线   读行:第3行   等待工作在线完成:第3行   在线工作:3号线   在线工作:3号线   所有工作都完成了!

public class Runner
{

    public static void main(String args[]) {
        Runner r = new Runner();
        try {
            r.dowork();
        } catch (IOException e) {
            // handle
            e.printStackTrace();
        }
    }

    CyclicBarrier barrier;
    ExecutorService executor;
    private int totalWorkers = 2;

    public Runner() {
        this.barrier = new CyclicBarrier(this.totalWorkers + 1);
        this.executor = Executors.newFixedThreadPool(this.totalWorkers);
    }

    public synchronized void dowork() throws IOException
    {
        //<code for opening a file here, other setup here, etc>
        //BufferedReader reader = null;
        //String line;

        final Worker worker = new Worker();

        for(String line : new String[]{"Line 1", "Line 2", "Line 3"})
        //while ((line = reader.readLine()) != null)
        {
            System.out.println("Read line: " + line);
            //<a large amount of processing on 'line'>

            for(int c = 0; c < this.totalWorkers; c++) {
                final String curLine = line;
                this.executor.submit(new Runnable() {
                    public void run() {
                        worker.doWork(curLine);
                    }
                });
            }

            try {
                System.out.println("Waiting for work to be complete on line: " + line);
                this.barrier.await();
            } catch (InterruptedException e) {
                // handle
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // handle
                e.printStackTrace();
            }
        }

        System.out.println("All work complete!");
    }

    class Worker
    {
        public void doWork(String line)
        {
            //<do work with this.data here>
            System.out.println("Working on line: " + line);

            try {
                Runner.this.barrier.await();
            } catch (InterruptedException e) {
                // handle
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                // handle
                e.printStackTrace();
            }
        }
    }    
}

答案 1 :(得分:0)

恕我直言,你没有正确地放置“workersDone = 0”。

public synchronized void dowork()
        {
                // workersDone = 0;

                //<code for opening a file here, other setup here, etc>

                Worker a = new Worker(this);
                Worker b = new Worker(this);

                while ((line = reader.readLine()) != null)
                {
                        workersDone = 0;

                        //<a large amount of processing on 'line'>

                        a.setData(line);
                        b.setData(line);

                        while (workersDone < totalWorkers)
                        {
                                wait();
                        }                               
                }
        }