前言

唉, 忧伤..

问题描述

今天 无意间看到了这样一个问题, 两个线程, 一个打印”1”, 另外的一个打印”2”, 写出程序实现如下输出”121212…”

思路

这个问题, 我以前也没有碰到过, 看到这个问题, 我的第一想法是, 两个线程
1. 让线程2先wait, 然后 线程1执行打印业务之后, 叫醒线程2, 然后 自己也wait
2. 然后 再”线程1 ‘变成’ 线程2, 线程2 ‘变成’ 线程1”, 然后 在走上面的业务[这里 如此说明, 仅仅是为了突出两个线程的业务基本上是相似的, 因此 可以共用一份代码]
3. 周期性的执行

Code01
然后 我写出了我的第一个版本的实现, 如下 :

// 两条线程, 打印出121212
    public static void main(String[] args) {
        MyThread t01 = new MyThread(1), t02 = new MyThread(t01, 2);
        t01.other = t02;

        t01.start();
//      Tools.sleep(1 * 1000);
        t02.start();

    }

    // MyThread
    static class MyThread extends Thread {
        MyThread other;
        private Object lock;
        private int output;

        // 初始化
        public MyThread(MyThread other, int output) {
            this.other = other;
            this.lock = new Object();
            this.output = output;
        }
        public MyThread(int output) {
            this(null, output);
        }

        @Override
        public void run() {
            if(output == 2) {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            while(true) {
                System.out.println(output);

                synchronized (other.lock) {
                    other.lock.notify();
                }
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

这样看似正确了, 但是 多线程的场景下面, 你永远不能使用单线程的思维来思考
呵呵 存在很明显的几个问题[当然 如果存在还有其他问题, 大家可以分享一下], 看到了么
1. 试想如下场景了, t1线程运行了, 执行到了”other.lock.notify()”, 然而 t2线程此时还没有被wait掉, 那么 此时是不是就构成了”死锁”, 在此场景下面, t1, t2两个线程都会wait掉, 而不会有其他的线程唤醒他们, 因此 程序就这样僵持下去了, 直到XXX
2. 另外的一个死锁的原因来了, 当前线程唤醒了另外的一个线程”other.lock.notify()”, 而执行到另外线程的notify的时候”other.lock.notify()”, 当前线程还没有走到wait这一步”lock.wait();”, 造成了死锁


难道说上面的思路存在问题嘛?? no 原因不在于此, 原因在于 实现的家伙太菜了 哈哈哈 就是我嘛

然后 之后的时候, 通过查阅资料[refer : http://jeasonjack.iteye.com/blog/1844512], 我看到了另外的一个版本的实现, 当然 思路和上面的思路是一样的, 存在一些不同的地方

Code02

// 两条线程, 打印出121212
    public static void main(String[] args) {
        MyThread03 t01 = new MyThread03(1), t02 = new MyThread03(2);

        t01.start();
        t02.start();

    }

    // MyThread
    static class MyThread03 extends Thread {
        static Object lock = new Object();
        private int output;

        // 初始化
        public MyThread03(int output) {
            this.output = output;
        }

        // run
        @Override
        public void run() {
            // 可能存在第二个线程还没有wait, 结果 第一个线程notify了lock上面等待的线程[为空], 从而 导致死锁
            // 一个不推荐, 但是 又想不到其他的好方法了, Thread.sleep() 控制流程, 以后 再回来想吧
            if(output == 1) {
                Tools.sleep(1 * 1000);
            }
            if(output == 2) {
                synchronized (lock) {
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

            while(true) {
                synchronized (lock) {
                    System.out.println(output);
                    lock.notify();
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }

这样, 程序就”正常”了
对比一下 上面的存在的两个问题, 第一个问题, 被”Tools.sleep(1 * 1000);”, “解决”掉了, 第二个问题, 因为使用的是用一个对象的锁, 而且 未申请此对象的锁等待的线程仅此一个, 因此 不存在上面的第二个问题

不过 这里要说明的是, 使用”Thread.sleep(long millis) “来控制程序的流程, 不提倡使用, 请慎用

我记得tij中有两个编写java程序的原则, 一个是永远不要通过线程的优先级来控制程序的流程, 还有一个是什么忘记了 [刚才去翻了一下当时做的记录, 也没有找到, 管它的呢, 先记录在这里吧, 以后 找到了再填充回来]


然后, 在分享一个使用一个标志变量 + 忙等, 来实现的方法
refer :

Code03

// 两条线程, 打印出121212
    public static void main(String[] args) {
        MyThread02 t01 = new MyThread02(1), t02 = new MyThread02(2);

        t01.start();
        t02.start();

    }

    // MyThread
    static class MyThread02 extends Thread {
        static volatile boolean isPrint1 = true;
        private int output;

        // 初始化
        public MyThread02(int output) {
            this.output = output;
        }

        // run
        @Override
        public void run() {
            while(true) {
                if(isPrint1) {
                    if(output == 1) {
                        System.out.println(output);
                        isPrint1 = ! isPrint1;
                    }
                } else {
                    if(output == 2) {
                        System.out.println(output);
                        isPrint1 = ! isPrint1;
                    }
                }
            }
        }
    }

看到了吧, 实现也是非常的简洁呢, 使用isPrint1为volitile, 来保证了其他线程的可见性, 当isPrint1为true的时候, 线程1执行打印业务, 线程2什么都不干, 继续进入下一个循环, 当isPring为false的时候, 线程1什么都不干, 进入下一个循环, 线程2执行打印业务

相对的来说, 这个效率应该是比较高的吧, 毕竟业务太简单了

总结

啊 吃饭了, 写了一个小时, 好累啊

参考
线程交替执行?? –2016.08.09

http://jeasonjack.iteye.com/blog/1844512

注 : 因为作者的水平有限,必然可能出现一些bug, 所以请大家指出!