前言
唉, 忧伤..
问题描述
今天 无意间看到了这样一个问题, 两个线程, 一个打印”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, 所以请大家指出!