Synchronized

我们可以结合Sync和Object#notifyAll来完成,如下所示

public class Test001 {

    // 锁,用于线程间通信
    public static final Object LOCK = new Object();
    // 数据
    public static volatile int count = 0;

    private static final Integer MAX = 100;

    public static void main(String[] args) {
        for(int i=0;i<3;i++) {
            new Thread(new worker(i)).start();
        }
    }

    public static class worker implements Runnable {

        private Integer index;

        public worker(Integer index) {
            this.index = index;
        }

        @Override
        public void run() {
            while (count < MAX) {
                synchronized (LOCK) {
                    try {
                        while (count % 3 != index) {
                            LOCK.wait();
                        }
                        System.out.println("Thread-" + index + " | count: " + count);
                        count++;
                        LOCK.notifyAll();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}

ReentrantLock

我们可以使用ReentrantLock和Condition尝试解决这种问题。大概的解决思路就是先通过lock对资源加锁,然后通过condition指定的唤醒下一个线程。相信大家都已经发现,这种方式比Synchronized的优点就是sync只能唤醒一个线程或者全部唤醒来让大家竞争,但是通过condition我们可以唤醒指定线程,避免资源浪费。

public class Test002 {
    private static final int WORKER_COUNT = 3;
    private static volatile int countIndex = 0;
    private static final ReentrantLock LOCK = new ReentrantLock();

    public static void main(String[] args) {
        final List<Condition> conditions = new ArrayList<>();
        for (int i=0;i<WORKER_COUNT;i++) {
            Condition condition = LOCK.newCondition();
            conditions.add(condition);
            Worker worker = new Worker(conditions, i);
            worker.start();
        }
    }

    static class Worker extends Thread {

         List<Condition> conditions;
         int index;

        public Worker(List<Condition> conditions, int index) {
            super("Thread-" + index);
            this.conditions = conditions;
            this.index = index;
        }

        private void singleNext() {
            int nextIndex = (index + 1) % conditions.size();
            conditions.get(nextIndex).signal();
        }

        @Override
        public void run() {
            while (true) {
                LOCK.lock();
                try {
                    System.out.println(this.getName() + "  开始运行...");
                    while (countIndex % 3 != index) {
                        if (countIndex > 100) {
                            singleNext();
                            System.out.println(this.getName() + "  已终止...");
                            return;
                        }
                        System.out.println(this.getName() + "  等待中...");
                        conditions.get(index).await();
                    }
                    if (countIndex > 100) {
                        singleNext();
                        System.out.println(this.getName() + "  已终止...");
                        return;
                    }
                    System.out.println(this.getName() + " count:" + countIndex);
                    countIndex++;
                    singleNext();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    LOCK.unlock();
                }
            }
        }
    }
    
}

知识扩展

这种问题还有很多变种,如三个线程顺序打印ABC,三个线程按照要求顺序打印ABC等。

这些问题都是上线问题的变体,譬如把1-100换成ABC,常见的1-100转为ABC的代码转换如下所示:

for (int i = 0; i < 100; i ++) {
   char ascii = (char)(65 + i%3);
}