线程之间需要进行通信,通信有数据共享和线程协作两种方式,这篇主要说线程协作的内容。
一:数据共享
1:文件共享;2:网络共享;3:变量共享。
二:线程协作
先来个场景:落魄程序员摆摊卖起了炒粉,起先有人去买炒粉,发现炒粉卖完了,只能失落的回家了;后来为了不让客户白来一趟,落魄程序员想到了一个办法,线上预定。要是没有炒粉了,客户就不要白跑了,要是炒粉做好了,就通知客户。
2.1 被弃用的suspend和resume
suspend会让当前线程挂起,resume会唤醒当前线程。那么,举个栗子先:
/** 炒粉对象 */
public static Object obj;
public static void main(String[] args) throws InterruptedException {
Demo_Suspend suspend = new Demo_Suspend();
suspend.test1();
}
//不会死锁
public void test1() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
//没有买到炒粉,就挂起等待
Thread.currentThread().suspend();
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
//炒粉做好了,就唤醒客户
thread.resume();
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
上述代码运行结果如下:
第二个栗子:
//死锁,加了synchronized关键字
public void test2() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
synchronized (Demo_Suspend.class) {
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
//没有买到炒粉,就挂起等待
Thread.currentThread().suspend();
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
synchronized (Demo_Suspend.class) {
//炒粉做好了,就唤醒客户
thread.resume();
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
}
运行结果:
发现线程一直挂起了,区别就是加了synchronized锁,客户买不到就把自己关了起来,想要通知到客户,就要拿到钥匙,可是客户把钥匙也拿起来了,所以就通知不到,造成了死锁。
注意:那是因为在synchronized中没有释放锁这个语义的,所以锁不能够进行释放,别人也就获取不到,然后就是死锁了
第三个栗子:
//先唤醒,后挂起,会造成死锁
public void test3() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
try {
Thread.sleep(5000);
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
//没有买到炒粉,就挂起等待
Thread.currentThread().suspend();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
//炒粉做好了,就唤醒客户
thread.resume();
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
上述例子呢,落魄程序员2点就把炒粉做好了,去通知客户,可是客户5点了还在睡觉,运行结果如下:
同样的,会造成死锁。
2.2 wait和notify,notifyAll
wait会让当前线程挂起,而且当线程调用wait之后,会自动释放锁,notify,notifyAll会唤醒线程,wait和notify,notifyAll只能用在synchronized关键字中,而且必须是同一个对象锁,否则会报java.lang.IllegalMonitorStateException异常。
又来栗子了:
public static Object obj;
public static void main(String[] args) throws InterruptedException {
Demo_Wait wait = new Demo_Wait();
wait.test1();
}
//正常,不会死锁
public void test1() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
synchronized (Demo_Wait.class) {
try {
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
Thread.currentThread().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
synchronized (Demo_Wait.class) {
Thread.currentThread().notifyAll();
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
}
运行结果如下:
下一个栗子:
//先唤醒后挂起会死锁
public void test2() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
synchronized (Demo_Wait.class) {
try {
Thread.sleep(6000);
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
Thread.currentThread().wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
synchronized (Demo_Wait.class) {
Thread.currentThread().notifyAll();
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
}
同样的,落魄程序员2点就把炒粉做好了,去通知客户,可是客户6点了还在睡觉,造成了死锁。运行结果如下:
2.3 park和unpark
park会让线程挂起,unpark唤醒线程。话不多说,栗子来啦:
public static Object obj;
public static void main(String[] args) throws InterruptedException {
Demo_park park = new Demo_park();
park.test1();
}
//不会死锁
public void test1() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
//没有买到炒粉,就挂起等待
LockSupport.park();
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
//炒粉做好了,就唤醒客户
LockSupport.unpark(thread);
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
运行结果如下:
第二个栗子:
//先唤醒后挂起不会死锁
public void test2() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
try {
Thread.sleep(6000);
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
//没有买到炒粉,就挂起等待
LockSupport.park();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
LockSupport.unpark(thread);
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
运行结果:
发现,先唤醒后挂起并不会死锁,原因是park,unpark是许可的意思,也就是说你只要有了许可证,就可以通过,若unpark多次,只当作一次,后续过来的park会继续等待。
第三个栗子:
//加了sync会死锁
public void test3() throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (obj == null) {
synchronized (Demo_park.class) {
System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
LockSupport.park();
}
}
System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
}
});
thread.start();
Thread.sleep(2000);
obj = new Object();
synchronized (Demo_park.class) {
LockSupport.unpark(thread);
System.out.println("落魄程序员做好了炒粉,通知客户。");
}
}
运行结果如下:
同样的,park和unpark加了synchronized关键字会造成死锁。
2.4 总结
协作方式 | 加synchronized关键字 | 先唤醒后挂起 |
suspend/resume | 死锁 | 死锁 |
wait/notify,notifyAll | 不会死锁 | 死锁 |
park/unpark | 死锁 | 不会死锁 |