目錄
一、Object的wait()、notify()、notifyAll()方法
二、Condition的await()、signal()、signalAll()方法
線程間的通信方式常用的有如下幾種:
Object的wait()、notify()、notifyAll()方法;
Condition的await()、signal()、signalAll()方法;
CountDownLatch:用於某個線程A等待若干個其他線程執行完之後,它本身才會執行;
CyclicBarrier :一組線程等待至某個狀態後再全部同時執行;
Semaphore:用於控制對某組資源的訪問權限;
一、Object的wait()、notify()、notifyAll()方法
我們通過一個例子來理解這種線程間的通訊:開啓兩個線程,一個用來打印10以內的奇數,一個用來打印10以內的偶數。
代碼如下:
public class ThreadComunicationDemo01 {
private int i = 0;//要打印得數
private Object obj = new Object();
//奇數打印方法,由奇數線程調用
public void odd(){
//1.判斷i是否小於10
while(i < 10){
synchronized(obj){
if(i % 2 == 1){
System.out.println(Thread.currentThread().getName() +"奇數:" + i);
i++;
//notify()方法和wait()必須放在synchronized同步代碼塊或方法中
//否則會報IllegalMonitorStateException異常
obj.notify();//喚醒偶數線程打印
}else {
try {
obj.wait();//等待偶數線程打印完畢
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//偶數打印方法,由偶數線程調用
public void even(){
//1.判斷i是否小於10
while(i < 10){
synchronized (obj){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() +"偶數:" + i);
i++;
//notify()方法和wait()必須放在synchronized同步代碼塊或方法中
//否則會報IllegalMonitorStateException異常
obj.notify();//喚醒奇數線程打印
}else {
try {
obj.wait();//等待奇數線程打印完畢
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
final ThreadComunicationDemo01 threadComunicationDemo01 = new ThreadComunicationDemo01();
//1.開啓奇數打印線程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.odd();
}
});
//1.開啓偶數打印線程
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.even();
}
});
thread1.start();
thread2.start();
}
}
輸出結果:
Thread-1偶數:0
Thread-0奇數:1
Thread-1偶數:2
Thread-0奇數:3
Thread-1偶數:4
Thread-0奇數:5
Thread-1偶數:6
Thread-0奇數:7
Thread-1偶數:8
Thread-0奇數:9
二、Condition的await()、signal()、signalAll()方法
還是上面的例子我們用Condition來實現:
public class ThreadComunicationDemo02 {
private int i = 0;//要打印得數
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
//奇數打印方法,由奇數線程調用
public void odd(){
//1.判斷i是否小於10
while(i < 10){
lock.lock();
try {
if(i % 2 == 1){
System.out.println(Thread.currentThread().getName() + "奇數:" + i);
i++;
condition.signal();//喚醒偶數線程打印
}else {
try {
condition.await();//等待偶數線程打印完畢
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
}
}
//偶數打印方法,由偶數線程調用
public void even(){
//1.判斷i是否小於10
while(i < 10){
lock.lock();
try{
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "偶數:" + i);
i++;
condition.signal();//喚醒奇數線程打印
}else {
try {
condition.await();//等待奇數線程打印完畢
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
final ThreadComunicationDemo02 threadComunicationDemo01 = new ThreadComunicationDemo02();
//1.開啓奇數打印線程
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.odd();
}
});
//1.開啓偶數打印線程
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo01.even();
}
});
thread1.start();
thread2.start();
}
}
輸出結果:
Thread-1偶數:0
Thread-0奇數:1
Thread-1偶數:2
Thread-0奇數:3
Thread-1偶數:4
Thread-0奇數:5
Thread-1偶數:6
Thread-0奇數:7
Thread-1偶數:8
Thread-0奇數:9
可以看到通過Object的的wait()、notify()方法和通過Condition的await()、signal()方法,實現的效果是一樣的。那麼他們二者有什麼差別嗎?通過上面的代碼,我們總結如下:
- Object的wait()必須在synchronized(同步鎖)下使用;
- Object的wait()必須要通過notify()方法進行喚醒;
- Condition的await()必須和Lock(互斥鎖/共享鎖)配合使用;
- Codition的await()必須通過signal()方法進行喚醒;
三、CountDownLatch
CountDownLatch是在jdk1.5被引入的,存在於java.util.concurrent包下。CountDownLatch能夠使一個線程等待其他線程都執行完後再進行本身的執行操作,它的這一功能是通過一個計數器來實現的,這個計數器的初始值一般就是“其他線程的數量”。我們通過一個圖來理解一下CountDownLatch的執行原理:
如上圖所示:假如一共有四個線程T1、T2、T3、TA,現在我們要求線程TA需要在其他的三個線程T1、T2、T3都執行完了以後才能執行。那初始時,我們設置CountDownLatch計數器的值爲3,然後讓線程TA調用CountDownLatch的await()方法,這個時候線程TA就會一直阻塞在哪裏,在阻塞的過程中他會不斷的去檢查計數器的數量,如果不爲0,那麼它就一直阻塞在哪裏。我們讓其他三個線程正常執行,並且每個線程在執行完自身的操作後讓其調用CountDownLatch的countDown()方法,每調用一次這個方法CountDownLatch的計數器數量就會減1。所以當T1、T2、T3這三個線程搜執行完後,countDown()這個方法就被調用了3次,CountDownLatch的計數器就會減爲0,此時,被阻塞的線程TA檢測到計數器的值爲0後就會被喚醒,執行其本身的操作。
我們繼續通過一個例子來說明CountDownLatch的使用:假設有三個運動員線程和一個教練線程,教練線程必須要等到三個運動員線程都準備好才能開始自身線程的訓練工作。
代碼如下:
public class ThreadComunicationDemo03 {
private CountDownLatch countDownLatch = new CountDownLatch(3);//設置要等待得運動員是3個
//運動員方法,由運動員線程調用
public void racer(){
//1.獲取運動員名稱(線程名)
String racerName = Thread.currentThread().getName();
//2.運動員開始準備:打印準備信息
System.out.println(racerName + "運動員正在準備。。。");
//3.讓線程睡眠1000毫秒,表示運動員在準備
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.運動員準備完畢:打印準備完畢信息,同時計數-1
System.out.println(racerName + "運動員準備完畢!");
countDownLatch.countDown();
}
//教練方法,由教練線程調用
public void coach(){
//1.獲取教練線程得名稱
String coachName = Thread.currentThread().getName();
//2.教練等待所有運動員準備完畢,打印等待信息
System.out.println(coachName + "教練等待運動員準備。。。");
//3.調用countDownLatch得await()方法,等待其他運動員準備線程執行完畢
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.所有運動員準備就緒,教練開始訓練:打印訓練信息
System.out.println("所有運動員已經就緒," + coachName + "教練開始訓練");
}
public static void main(String[] args) {
//1.創建TThreadComunicationDemo03實例
final ThreadComunicationDemo03 threadComunicationDemo03 = new ThreadComunicationDemo03();
//2.創建三個線程對象,調用ThreadComunicationDemo03的racer方法
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.racer();
}
});
//3.創建一個線程對象,調用ThreadComunicationDemo03的coach方法
Thread coachThread = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo03.coach();
}
},"教練");
coachThread.start();
thread1.start();
thread2.start();
thread3.start();
}
}
輸出結果:
hread-0運動員正在準備。。。
Thread-2運動員正在準備。。。
Thread-1運動員正在準備。。。
教練教練等待運動員準備。。。
Thread-2運動員準備完畢!
Thread-1運動員準備完畢!
Thread-0運動員準備完畢!
所有運動員已經就緒,教練教練開始訓練
四、CyclicBarrier
CyclicBarrier也是在jdk1.5引入的,在java.util.concurrent包下。CyclicBarrier可以實現讓“一組”線程等待至某個狀態後再全部“同時”執行。CyclicBarrier底層是基於ReentrantLock和Codition實現的,有興趣的同學可以找來源碼看看。
我們繼續通過一個例子來說明CyclicBarrier的使用:我們開啓三個線程,並且等到這三個線程都處於就緒狀態後同時讓這三個線程一起執行。
public class ThreadComunicationDemo04 {
//參數3是表示參與CyclicBarrier的線程數
private CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public void startThread(){
//1.打印線程準備啓動
String name = Thread.currentThread().getName();
System.out.println(name + "正在準備");
//2.調用CyclicBarriar的await()方法等待線程全部準備完畢(所有線程在此處都會阻塞)
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
//3.打印線程啓動完畢
System.out.println(name + "已經啓動完畢:" + new Date().getTime());
}
public static void main(String[] args) {
final ThreadComunicationDemo04 threadComunicationDemo04 = new ThreadComunicationDemo04();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
Thread thread3 = new Thread(new Runnable() {
@Override
public void run() {
threadComunicationDemo04.startThread();
}
});
thread1.start();
thread2.start();
thread3.start();
}
}
輸出結果:
Thread-0正在準備
Thread-1正在準備
Thread-2正在準備
Thread-2已經啓動完畢:1582774772670
Thread-0已經啓動完畢:1582774772670
Thread-1已經啓動完畢:1582774772670
通過輸出結果我們可以看到,這三個線程是同時執行的,都是在1582774772670這個時間點開始執行。
五、Semaphore
Semaphore也是在jdk1.5後引入的,同樣也在java.util.concurrent包下。Semaphore主要是用於控制對某組資源的訪問權限。這樣說可能還是比較模糊,我們繼續通過一個例子來說明Semaphore的使用:假設現在有8個工人3臺機器,機器爲互斥資源(即每次只能一個人使用),代碼如下:
public class ThreadComunicationDemo05 {
//內部類,當然你也可以新建一個文件定義在外面
static class Work implements Runnable{
private int workerNum;//工人的工號
private Semaphore semaphore;//機器數
public Work(int workerNum,Semaphore semaphore){
this.semaphore = semaphore;
this.workerNum = workerNum;
}
@Override
public void run() {
try{
//1.工人要去獲取機器
semaphore.acquire();
//2.打印工人獲取到機器,開始工作
String workerName = Thread.currentThread().getName();
System.out.println(workerName + "獲取到機器,開始工作。。。");
//3.線程睡眠1000毫秒,模擬工人使用機器的過程
Thread.sleep(1000);
//4.使用完畢,釋放機器,打印工人使用完畢,釋放機器
semaphore.release();
System.out.println(workerName + "使用完畢,釋放機器");
}catch (Exception e){
e.printStackTrace();
}
}
}
public static void main(String[] args) {
int workers = 8;//工人數是8個
Semaphore semaphore = new Semaphore(3);//機器數是3個
//i代表工人的工號,8個工人線程開啓,並使用3臺機器進行工作
for (int i = 0; i < workers; i++){
new Thread(new Work(i,semaphore)).start();
}
}
}
輸出結果:
Thread-1獲取到機器,開始工作。。。
Thread-0獲取到機器,開始工作。。。
Thread-2獲取到機器,開始工作。。。
Thread-1使用完畢,釋放機器
Thread-4獲取到機器,開始工作。。。
Thread-0使用完畢,釋放機器
Thread-5獲取到機器,開始工作。。。
Thread-2使用完畢,釋放機器
Thread-3獲取到機器,開始工作。。。
Thread-3使用完畢,釋放機器
Thread-7獲取到機器,開始工作。。。
Thread-6獲取到機器,開始工作。。。
Thread-5使用完畢,釋放機器
Thread-4使用完畢,釋放機器
Thread-6使用完畢,釋放機器
Thread-7使用完畢,釋放機器
通過上面的輸出結果我們可以看到,三個機器在八個工人手裏正常的完成了工作,這就是Semaphore的作用所在。
就寫先到這裏吧!!!!!!!!!!!!!!!!