JDK 中提供的線程通信方式
1、wait/notify方式
wait/notify要求在同步關鍵字中使用,避免了死鎖現象,但是如果不先調用wait,而先調用notify的情況下,容易導致線程永久掛起
package com.milla.study.netbase.expert.concurrent;
import java.util.Objects;
import java.util.concurrent.locks.LockSupport;
/**
* @Package: com.milla.study.netbase.expert.concurrent
* @Description: <線程通信類>
* @Author: MILLA
* @CreateDate: 2020/4/21 14:29
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/21 14:29
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ThreadCommunicationTest {
private Object product;
public static void main(String[] args) throws InterruptedException {
ThreadCommunicationTest test = new ThreadCommunicationTest();
test.waitAndNotifyAll();//正常情況
test.waitAndNotifyAllWaitingByOrder();//順序問題導致一直阻塞
}
private void waitAndNotifyAllWaitingByOrder() throws InterruptedException {
Thread consumer = new Thread(() -> {
if (Objects.isNull(product)) {
System.out.println("沒有產品可以消費,進行等待...");
try {
Thread.sleep(5000L);//外接因素導致線程先被喚醒後才進行等待
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
try {
this.wait();//該方法會執行後會失去鎖,被喚醒後需從該語句的後一個語句開始執行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("繼續消費...........");
}
});
consumer.start();
System.out.println("啓動消費者線程...");
Thread.sleep(1000L);
synchronized (this) {
this.notifyAll();
System.out.println("生產出產品,喚醒所有等待線程...");
}
}
private void waitAndNotifyAll() throws InterruptedException {
Thread consumer = new Thread(() -> {
synchronized (this) {
while (Objects.isNull(product)) {
System.out.println("沒有產品可以消費,進行等待...");
try {
this.wait();//被喚醒後需從該語句的後一個語句開始執行
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("繼續消費...........");
}
});
consumer.start();
System.out.println("啓動消費者線程...");
Thread.sleep(1000L);
synchronized (this) {
// product = new Object();//當生產者不被創建的時候時候,喚醒線程,線程會出現僞喚醒的情況。所以在使用wait的時候應採用while的判斷模式[JDK推薦方式],而不是if
this.notifyAll();
System.out.println("生產出產品,喚醒所有等待線程...");
}
}
}
執行後,如果不能成功運行結束,可以通過兩種方式去查看線程的狀態:
1、jstack命令
//在黑窗口中執行jps命令,找到java進程的Pid
jsp -l // 獲取所有的Java程序信息
23408 org/netbeans/Main
18132 org.jetbrains.jps.cmdline.Launcher
20804 sun.tools.jps.Jps
22692 org.jetbrains.jps.cmdline.Launcher
4712 com.milla.study.netbase.expert.concurrent.ThreadCommunicationTest//確定該進程pid
4728 org.jetbrains.jps.cmdline.Launcher
9016 org.jetbrains.idea.maven.server.RemoteMavenServer
6108
//繼續執行jstack -jdk自帶的命令
jstack -l 4712
在輸出中沒有找到dead lock的情況下,一般就是沒有死鎖,如果看到 waiting for lock的話,一般是因爲一個線程持有鎖,然後是在阻塞中,且不釋放鎖,而另一個線程也獲取不到這個鎖,就一直阻塞
2.通過JavaVisualVM [JDK中自帶的可視化工具]
查看線程的Dump,基本信息和jstack一樣
3、使用jconsole工具 [jdk提供的可視化工具]
指定需要監測的進程,可分本地和遠程進程
結果顯示,並沒有產生死鎖,只是線程一直處於阻塞狀態
2、通過park/unpark實現通信
park/unpark沒有順序要求,但park並不會釋放鎖,在同步代碼快中依然有永久阻塞的問題
package com.milla.study.netbase.expert.concurrent;
import java.util.Objects;
import java.util.concurrent.locks.LockSupport;
/**
* @Package: com.milla.study.netbase.expert.concurrent
* @Description: <線程通信類>
* @Author: MILLA
* @CreateDate: 2020/4/21 14:29
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/21 14:29
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ThreadCommunicationTest {
private Object product;
public static void main(String[] args) throws InterruptedException {
ThreadCommunicationTest test = new ThreadCommunicationTest();
test.parkAndUnPark();//正常情況
test.parkAndUnParkWaitingBySynchronized();//順序問題導致一直阻塞
}
private void parkAndUnParkWaitingBySynchronized() throws InterruptedException {
Thread consumer = new Thread(() -> {
System.out.println("開始消費....");
while (Objects.isNull(product)) {
System.out.println("沒有許可,阻塞中....");
synchronized (this) {//同步代碼塊,park方法不會釋放鎖,所以,一直在阻塞狀態
LockSupport.park();//許可是不可用的,將線程阻塞
}
}
System.out.println("繼續消費....");
});
consumer.start();
Thread.sleep(3000L);
product = new Object();
synchronized (this) {
LockSupport.unpark(consumer);
}
System.out.println("發放許可,使線程繼續執行....");
}
private void parkAndUnPark() throws InterruptedException {
Thread consumer = new Thread(() -> {
System.out.println("開始消費....");
while (Objects.isNull(product)) {
System.out.println("沒有許可,阻塞中....");
LockSupport.park();//許可是不可用的,將線程阻塞
}
System.out.println("繼續消費....");
});
consumer.start();
Thread.sleep(3000L);
product = new Object();
LockSupport.unpark(consumer);//發送許可,使線程能繼續執行
System.out.println("發放許可,使線程繼續執行....");
}
}
3、已經廢棄的suspend/resume
對調用有順序要求,開發中容易出現死鎖現象,也容易永久掛起,已經被廢棄
package com.milla.study.netbase.expert.concurrent;
import java.util.Objects;
import java.util.concurrent.locks.LockSupport;
/**
* @Package: com.milla.study.netbase.expert.concurrent
* @Description: <線程通信類>
* @Author: MILLA
* @CreateDate: 2020/4/21 14:29
* @UpdateUser: MILLA
* @UpdateDate: 2020/4/21 14:29
* @UpdateRemark: <>
* @Version: 1.0
*/
public class ThreadCommunicationTest {
private Object product;
public static void main(String[] args) throws InterruptedException {
ThreadCommunicationTest test = new ThreadCommunicationTest();
test.oldJdkCommunication();//正常情況
test.oldJdkCommunicationWaitingBySynchronized();//因爲同步塊導致永久阻塞
test.oldJdkCommunicationWaitingByOrder();//喚醒順序問題導致永久掛起
}
//方法調用順序錯誤導致死鎖
private void oldJdkCommunicationWaitingByOrder() throws InterruptedException {
Thread producer = new Thread(() -> {
if (Objects.isNull(product)) {
System.out.println("如果產品是空的話,掛起當前線程[需要獲取到鎖],進入等等");
synchronized (this) {
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread.currentThread().suspend();//掛起線程
}
}
System.out.println("繼續消費");
}, "生產者");
producer.start();
Thread.sleep(1000L);
product = new Object();
producer.resume();//恢復消費線程,在線程被掛起之前喚醒線程,線程掛起之後就一直處於阻塞狀態
System.out.println("重新喚起線程進行消費");
Thread.sleep(3001L);
producer.resume();//繼續喚醒,此時如果在線程被掛起之後繼續喚醒線程,還是可以喚醒成功的()
System.out.println("繼續喚醒...");
}
//同步代碼塊會死鎖
private void oldJdkCommunicationWaitingBySynchronized() throws InterruptedException {
Thread producer = new Thread(() -> {
if (Objects.isNull(product)) {
System.out.println("如果產品是空的話,掛起當前線程[需要獲取到鎖],進入等等");
synchronized (this) {
Thread.currentThread().suspend();//掛起線程
}
}
System.out.println("繼續消費");
}, "生產者");
producer.start();
Thread.sleep(3000L);
product = new Object();
synchronized (this) {
producer.resume();//恢復消費線程
}
System.out.println("重新喚起線程進行消費");
}
private void oldJdkCommunication() throws InterruptedException {
Thread producer = new Thread(() -> {
if (Objects.isNull(product)) {
System.out.println("如果產品是空的話,掛起當前線程[需要獲取到鎖],進入等等");
synchronized (this) {
Thread.currentThread().suspend();//掛起線程
}
}
System.out.println("繼續消費");
}, "生產者");
producer.start();
Thread.sleep(3000L);
product = new Object();
producer.resume();//恢復消費線程
System.out.println("重新喚起線程進行消費");
}
}
注意點:在判斷的時候JDK中推薦採用while(conditions){wait()}.的方式,不建議直接使用if判斷,因爲線程可能不是notify、notifyAll、 unpark等api的調用而喚醒的,可能是更底層的原因喚醒的,應注意僞喚醒的問題。