線程之間需要進行通信,通信有數據共享和線程協作兩種方式,這篇主要說線程協作的內容。
一:數據共享
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 | 死鎖 | 不會死鎖 |