使用currentThread作为锁定对象时卡住

时间:2016-10-20 16:25:22

标签: java multithreading concurrency

我试图模拟这个场景:数组中的线程可以作为数组中的顺序一个接一个地执行。我的想法是让thread[i]始终通知thread[(i+1)%threads.length]

然而,代码将被卡在某处:

    public class OrderedOperationThreadOneLock {

    //<- Assign tasks to workers
    private static final String[] message1 = {"A", "D", "G"};
    private static final String[] message2 = {"B", "E", "H"};
    private static final String[] message3 = {"C", "F", "I"};

    private static final Task task1 = new Task(message1);
    private static final Task task2 = new Task(message2);
    private static final Task task3 = new Task(message3);

    private static Thread[] workers = {
            new Thread(task1),
            new Thread(task2),
            new Thread(task3)
    }; //->

    private static void printThreadsStatus() {
        for (int i = 0; i < workers.length; ++i) {
            System.out.println("Thread: " + workers[i].getName() +
                    "\tPriority: " + workers[i].getPriority() +
                    "\tStatus: " + workers[i].getState()
            );
        }
        System.out.println("----------------------------------------------");
    }


    // Print a message then wait for other thread notify.
    // Until all the messages are outputted.
    private static class Task implements Runnable {
        private final String[] messages;
        Task(String[] messages) {
            this.messages = Arrays.copyOf(messages, messages.length);
        }
        @Override
        public void run() {
            for (int i = 0; i < messages.length; ++i) {
                System.out.println(messages[i]);
                Thread curThread = Thread.currentThread();

                // Stuck here
                synchronized (curThread) {
                    try {
                        curThread.wait();
                        printThreadsStatus();
                    } catch (InterruptedException e) {
                        System.err.println("Oh, Why?");
                    }
                }
                wakeUpNext();
            }
        }

        // `thread[i]` always notify `thread[(i+1)%threads.length]`
        private void wakeUpNext() {
            Thread curThread = Thread.currentThread();
            int i = 0;
            boolean isNotified = false;
            while (i < workers.length && !isNotified) {
                if (curThread.getId() == workers[i].getId()) {
                    int toNotify = (i+1) % workers.length;
                    System.out.println("Worker " + i + " is waking up " + toNotify);
                    synchronized (workers[toNotify]) {
                        workers[toNotify].notify();
                    }
                    isNotified = true;
                }
                i++;
            }
        }
    }

    public static void main(String... args) throws InterruptedException {
        for (int i = 0; i < workers.length; ++i) {
            workers[i].start();
            Thread.sleep(10); // Ensure start in 1,2,3 sequence
        }

        System.out.println("================================");
        printThreadsStatus();
        synchronized (workers[0]) {
            workers[0].notify();
            printThreadsStatus();
        }

        for (int i = 0; i < workers.length; ++i) {
            workers[i].join(5000);
            printThreadsStatus(); // All the threads are in WAITING status...
        }

        printThreadsStatus();
    }
}

但是,如果我使用Task作为监视器锁定。然后该程序将不会被卡住:

public class OrderedOperationThreadSingleLock {
    //<- Assign tasks to workers
    private static final String[] message1 = {"A", "D", "G"};
    private static final String[] message2 = {"B", "E", "H"};
    private static final String[] message3 = {"C", "F", "I"};

    private static final Task[] tasks = {
            new Task(message1),
            new Task(message2),
            new Task(message3)
    };

    private static Thread[] workers = {
            new Thread(tasks[0]),
            new Thread(tasks[1]),
            new Thread(tasks[2])
    };//->

    private static void printThreadsStatus() {
        for (int i = 0; i < workers.length; ++i) {
            System.out.println("Thread: " + workers[i].getName() +
                    "\tPriority: " + workers[i].getPriority() +
                    "\tStatus: " + workers[i].getState()
            );
        }
        System.out.println("----------------------------------------------");
    }


    private static class Task implements Runnable {
        private final String[] messages;
        Task(String[] messages) {
            this.messages = Arrays.copyOf(messages, messages.length);
        }
        @Override
        public void run() {
            for (int i = 0; i < messages.length; ++i) {
                System.out.println(messages[i]);
                synchronized (this) {
                    try {
                        wait();
                        printThreadsStatus();
                    } catch (InterruptedException e) {
                        System.err.println("Oh, Why?");
                    }
                }
                wakeUpNext();
            }
        }

        // Use task as the lock instead of thread object
        private void wakeUpNext() {
            int i = 0;
            boolean isNotified = false;
            Thread t = Thread.currentThread();
            while (i < workers.length && !isNotified) {
                if (t.getId() == workers[i].getId()) {
                    int toNotify = (i+1) % workers.length;
                    System.out.println("Worker " + i + " is waking up " + toNotify);
                    synchronized (tasks[toNotify]) {
                        System.out.println("Prepare Waking up " + toNotify);
                        tasks[toNotify].notify();
                    }
                    isNotified = true;
                }
                i++;
            }
        }
    }

    public static void main(String... args) throws InterruptedException {
        for (int i = 0; i < workers.length; ++i) {
            workers[i].start();
            Thread.sleep(10);
        }

        System.out.println("================================");
        printThreadsStatus();
        synchronized (tasks[0]) {
            tasks[0].notify();
            printThreadsStatus();
        }

        for (int i = 0; i < workers.length; ++i) {
            workers[i].join(5000);
            printThreadsStatus();
        }

        printThreadsStatus();
    }
}

我的问题是为什么线程对象会阻止执行而任务不会?

2 个答案:

答案 0 :(得分:1)

有趣的是问题出在workers[i].join(5000);。方法join也在线程上同步并调用wait,因此当您在wakeUpNext中通知线程时,您会错误地唤醒它。

它发生在下一个方向。

  • 主要线程通知线程1
  • 线程1醒来
  • 线程1通知线程2
  • 线程1睡眠等待线程1通知。
  • 主线程在线程1上调用join,导致主线程休眠。
  • 在某些时候线程3通知线程1,但有两个线程等待这个通知(线程1本身和主线程)由于某种原因主线程得到通知。
  • 由于join方法准备好虚假唤醒
  • ,主线程重新进入休眠状态

答案 1 :(得分:0)

可能导致问题的情况是这样的。

  • 线程1通知线程2
  • 线程2唤醒并通知线程3
  • 线程3唤醒并通知线程1(由于线程1尚未滑动,通知丢失)
  • 线程1进入睡眠状态(并永远睡眠)