你和你的朋友共同完成一個任務,你們隨時交流對方的工作進度,因爲有些任務是要對等待對方先完成再做,這個過程就是線程通信。
Table of Contents
synchronized實現通信
進入等待(是Object類的方法):
- void wait();
- void wait(long timeoutMillis)
- void wait(long timeoutMillis, int nanos)
- wait方法執行後,會釋放監視器,會釋放鎖!
喚醒(是Object類的方法):
- void notify(); 隨機喚醒一個正在wait的方法;
- void notifyAll(); 喚醒所有正在wait的方法;
第一個同步程序:用三個線程模擬三個人報數。
/**
* 功能:三個線程輪流報數
*
* @author KYLE
*
*/
public class Main {
public static void main(String[] args) {
CountOff co = new CountOff();
Thread t1 = new Thread(co);
Thread t2 = new Thread(co);
Thread t3 = new Thread(co);
t1.setName("線程1");
t2.setName("線程2");
t3.setName("線程3");
t1.start();
t2.start();
t3.start();
}
}
class CountOff implements Runnable {
private volatile int initNum = 1;
private final int endNum = 10;
@Override
public void run() {
synchronized (this) {
while(initNum <= 10) {
try {
System.out.println(Thread.currentThread().getName() + "報數:" + initNum++);
this.notifyAll(); //喚醒其他線程開始執行
this.wait(); //進入等待!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.notifyAll();
}
}
}
重入鎖(ReentrantLock)實現通信
進入等待(juc.locks.Condition接口):
- void await() 將線程放在這個條件的等待集中。
- boolean await(long time, TimeUnit unit) 使當前線程等待直到發出信號或中斷,或指定的等待時間過去。
- long awaitNanos(long nanosTimeout) 使當前線程等待直到發出信號或中斷,或指定的等待時間過去。
- void awaitUninterruptibly() 使當前線程等待直到發出信號。
- boolean awaitUntil(Date deadline) 使當前線程等待直到發出信號或中斷,或者指定的最後期限過去。
喚醒:
- void signal() 隨機喚醒一個等待集中的線程,解除其阻塞狀態。
- void signalAll() 喚醒所有等待線程,解除阻塞狀態。
第一個重入鎖實現的線程間通信:
程序功能:A線程通知B線程
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadDemo {
public static void main(String[] args) {
ReentrantLockCondition rc = new ReentrantLockCondition();
Thread t1 = new Thread(rc,"線程1");
t1.start();
//通知rc對象的t1線程繼續執行!!!
//如果沒有以下三段代碼,則在Console只會顯示:線程1開始執行。。。
rc.lock.lock();
rc.cond.signal(); //通知rc對象的t1線程:可以繼續執行了!
rc.lock.unlock();
}
}
class ReentrantLockCondition implements Runnable {
public ReentrantLock lock = new ReentrantLock();
public Condition cond = lock.newCondition(); //newCondition方法是juc.locks.Lock接口中的方法
@Override
public void run() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName()+"開始執行。。。");
cond.await(); //使得本線程進入等待,並且能夠釋放當前鎖
System.out.println(Thread.currentThread().getName()+"線程繼續執行。。。");
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
輸出:
線程1開始執行。。。
線程1線程繼續執行。。。
線程之間的通訊工具類
爲了能夠更好控制線程之間的通信,Java爲我們提供了一些工具類:
- CountDownLatch(閉鎖)
- CyclicBarrier(柵欄)
- Semaphore(信號量)
CountDownLatch(閉鎖)
CountDownLatch的功能:CountDownLatch是一個同步的輔助類,允許一個或多個線程一直等待,直到其它線程完成它們的操作。
特性:
- 來自juc.CountDownLatch類
- 它常用的API其實就兩個:await()和countDown()
理解:
- count初始化CountDownLatch,然後需要等待的線程調用await方法。await方法會一直受阻塞直到count=0。而其它線程完成自己的操作後,調用countDown()使計數器count減1。當count減到0時,所有在等待的線程均會被釋放
- 說白了就是通過count變量來控制等待,如果count值爲0了(其他線程的任務都完成了),那就可以繼續執行。
- 實例:你是一個實習生,其他的員工還沒下班,你也不好意思走,等其他的員工都走光了,你再走。
第一個CountDownLatch程序
import java.util.Random;
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) {
YourName aa = new YourName();
aa.GoOffWork(); //下班了
}
}
//你是一個實習生,在XxOo公司上班,其他的員工還沒下班,你也不好意思走,等其他的員工都走光了,你再走。(改編自:Java3y)
class YourName {
private int num; //公司人數
private final CountDownLatch cd;
public YourName() {
Random rand = new Random();
this.num = rand.nextInt(10); //隨機獲取一個公司人數
cd = new CountDownLatch(num); //你必須等待公司全部人走了你才能走
}
//下班了
public void GoOffWork() {
System.out.println("現在6點下班了。。。");
//啓動你自己的下班線程
new Thread(new Runnable() {
@Override
public void run() {
try {
//這裏調用的是await()而不是wait()方法
cd.await(); //要等待其他人都走了(執行完畢),你才能走(執行)
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("其他員工都走了,我也可以下班了");
}
}).start();
//公司其他員工的下班線程
for(int i=0; i<num; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("員工xx下班了!");
cd.countDown();
}
}).start();
}
}
}
CyclicBarrier(柵欄)
理解:
- CyclicBarrier允許一組線程互相等待,直到到達某個點。
- 叫做cyclic是因爲當所有等待線程都被釋放以後,CyclicBarrier可以被重用(對比於CountDownLatch是不能重用的)
- 區別:CountDownLatch注重的是等待其他線程完成;CyclicBarrier注重的是:當線程到達某個狀態後,暫停下來等待其他線程,所有線程均到達以後,繼續執行。
- 例如:你和你的朋友們(一組線程)約定一起去旅遊,要等到所有朋友都到齊了以後(線程間相互等待),才能出發(到達某個點)。
第一個CyclicBarrier程序
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Main {
public static void main(String[] args) {
Meet meet = new Meet();
Thread t0 = new Thread(meet);
Thread t1 = new Thread(meet);
Thread t2 = new Thread(meet);
Thread t3 = new Thread(meet);
Thread t4 = new Thread(meet);
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Meet implements Runnable {
private final CyclicBarrier cb = new CyclicBarrier(5, ()->{
System.out.println("所有朋友都到了,可以出發去旅遊了!");
}); //你有5個朋友相約一起去旅遊
@Override
public void run() {
try {
synchronized (this) {
speedTime();
}
cb.await(); //等待所有朋友到齊
} catch(InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}
//每個朋友需要到達聚會點的所花時間
public void speedTime() {
try {
Thread.sleep(2000); //模擬這個朋友需要2s才能到達聚會點
} catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println("朋友(線程):"+Thread.currentThread().getName()+"到達了!");
}
}
Semaphore(信號量)
背景:
- 無論是內部鎖synchronized還是重入鎖ReentrantLock,一次都只允許一個線程訪問一個資源;
- 那麼,有些資源必須允許多個線程訪問同時訪問,應該怎麼實現呢?
- 例如,某個小店(資源)一次只能允許5個顧客(多個線程)同時挑選購買,超過5個就需要排隊等待了。
理解:
- Semaphore(信號量)實際上就是可以指定多個線程同時訪問某一資源的一種解決方案。
構造方法:
- Semaphore(int permits) 使用給定數量的許可和非公平方式設置創建 Semaphore 。
- Semaphore(int permits, boolean fair) 使用給定數量的許可和公平的方式設置創建 Semaphore 。
核心方法:
- void acquire() 從此信號量上獲取一個許可;如果沒有則一直阻塞,直到有一個可用;或者線程爲interrupted
- void acquire(int permits) 從此信號量獲取給定數量的許可;阻塞直到所有可用;或者線程爲 interrupted
- void acquireUninterruptibly() 從此信號量獲取許可,如果沒有一直阻塞,直到有一個可用
- boolean tryAcquire(int permits) 只有在調用時所有許可都可用時,才從此信號量獲取給定數量的許可。【嘗試獲取】
- boolean tryAcquire(int permits, long timeout, TimeUnit unit) 如果在給定的等待時間內所有許可都可用,且當前線程不是 interrupted ,則從此信號量獲取給定數量的許可。【時間等待獲取】
- boolean tryAcquire(long timeout, TimeUnit unit) 如果在給定的等待時間內有可用的並且當前線程不是 interrupted ,則從該信號量獲取許可。【時間條件獲取】
- void release() 釋放許可。
第一個信號量程序
import java.util.concurrent.Semaphore;
public class Main {
public static void main(String[] args) {
SemaphoreExample se = new SemaphoreExample();
//準備6個線程
Thread t0 = new Thread(se);
Thread t1 = new Thread(se);
Thread t2 = new Thread(se);
Thread t3 = new Thread(se);
Thread t4 = new Thread(se);
Thread t5 = new Thread(se);
t0.start();
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
}
class SemaphoreExample implements Runnable {
private final Semaphore sema = new Semaphore(5); //某一資源同一時刻只能允許5個線程訪問
@Override
public void run() {
try {
sema.acquire(); //申請對該資源的訪問,但構造方法規定該資源一次只能允許5個線程同時訪問
System.out.println("線程"+Thread.currentThread().getName()+"正在訪問該資源!");
Thread.sleep(3000); //一旦申請成功,立即休眠3s,模擬該線程執行任務
} catch(InterruptedException e) {
e.printStackTrace();
} finally {
sema.release();
}
}
}