1.join
如圖中所示趟薄,一個線程調(diào)用join后,進(jìn)入阻塞狀態(tài)件甥。join的作用就是等待一個線程并暫停自己何恶,直到等待的那個線程結(jié)束為止:
public static void main(String[] args) {
Thread A = new Thread(){
@Override
public void run() {
System.out.println("A start");
try {
sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A end");
}
};
Thread B = new Thread(){
@Override
public void run() {
System.out.println("B start");
try {
A.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B end");
}
};
A.start();
B.start();
}
測試結(jié)果:
第一次:
A start
B start
A end
B end
第二次:
B start
A start
A end
B end
由于B調(diào)用了join孽锥,所以無論B是否先執(zhí)行,都是等到A執(zhí)行完畢后再執(zhí)行到結(jié)束细层。
2.wait/notify
這兩個是配合使用的惜辑,而且這兩個方法是不Thread特有的,而是屬于Object疫赎,java中所有類頂級父類都是Object盛撑,所以所有類都有這兩個方法。他們出現(xiàn)的原因是虚缎,在對臨界資源訪問時需要加鎖撵彻,但有時候需要放棄已有的鎖,所以就出現(xiàn)了wait实牡,讓出當(dāng)前的鎖陌僵,讓其他申請鎖的線程得以執(zhí)行,另外線程執(zhí)行完之后需要調(diào)用notify创坞,使之前讓出的依舊持有鎖從而可以運(yùn)行碗短。我們先看一個沒有wait的例子
Object lock = new Object();
public static void main(String[] args) {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A start");
synchronized (lock) {
System.out.println("A 1");
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A 2");
System.out.println("A 3");
}
System.out.println("A end");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B start");
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
}
System.out.println("B end");
}
});
A.start();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
B.start();
}
運(yùn)行結(jié)果
A start
A 1
B start
A 2
A 3
A end
B 1
B 2
B 3
B end
可見雖然B啟動了,但臨界區(qū)的鎖被A持有题涨,所以要等A臨界區(qū)代碼執(zhí)行完畢后再執(zhí)行B的鄰接區(qū)代碼偎谁,下面再看有wait的部分:
public static void main(String[] args) {
Object lock = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("A start");
synchronized (lock) {
System.out.println("A 1");
try {
lock.wait();
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("A 2");
System.out.println("A 3");
}
System.out.println("A end");
}
});
Thread B = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("B start");
synchronized (lock) {
System.out.println("B 1");
System.out.println("B 2");
System.out.println("B 3");
lock.notify();
}
System.out.println("B end");
}
});
A.start();
try {
Thread.currentThread().sleep(500);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
B.start();
}
測試結(jié)果
A start
A 1
B start
B 1
B 2
B 3
B end
A 2
A 3
A end
可見A的確讓出了鎖資源,B得到執(zhí)行纲堵,最后B喚醒A巡雨,A繼續(xù)執(zhí)行。如果B執(zhí)行完之后沒有調(diào)用notify席函,A是不會繼續(xù)執(zhí)行的铐望,有疑問的可以試試。還有一個notifyAll方法茂附,功能類似正蛙,是喚醒多個wait的線程
3.CountdownLatch
這是一個類似計(jì)數(shù)器的類,用于一個線程等待一定數(shù)量的線程都達(dá)到某種條件后才執(zhí)行的場景营曼,示例
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(){
@Override
public void run() {
System.out.println("A 開始等待");
try {
countDownLatch.await();
System.out.println("A 等待結(jié)束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
IntStream.of(1,2,3).forEach(i->{
String name = "Thread"+i;
new Thread(){
@Override
public void run() {
System.out.println(name + "開始執(zhí)行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name + "執(zhí)行完畢");
countDownLatch.countDown();
}
}.start();
});
}
結(jié)果:
A 開始等待
Thread2開始執(zhí)行
Thread1開始執(zhí)行
Thread3開始執(zhí)行
Thread2執(zhí)行完畢
Thread3執(zhí)行完畢
Thread1執(zhí)行完畢
A 等待結(jié)束
CountdownLatch的構(gòu)造方法需要傳入一個等待時數(shù)目乒验,在需要等待的線程中調(diào)用await方法,之后每當(dāng)一個線程執(zhí)行完畢或在適當(dāng)時候調(diào)用countDown蒂阱,將計(jì)數(shù)器減一锻全,減到0時狂塘,原來等待的線程就可以執(zhí)行了
4.CyclicBarrier
這是一個類似于計(jì)數(shù)器的類,當(dāng)一定數(shù)量的線程都達(dá)到某種狀態(tài)時鳄厌,這些線程才可以繼續(xù)執(zhí)行睹耐,示例:
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
Random random = new Random();
IntStream.of(1,2,3).forEach(i -> {
String name = "Thread" + i;
new Thread(){
@Override
public void run() {
setName(name);
System.out.println(getName() + "開始準(zhǔn)備");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"準(zhǔn)備完畢");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有線程準(zhǔn)備完畢" + getName() + "開始執(zhí)行");
}
}.start();
});
}
運(yùn)行結(jié)果:
Thread2開始準(zhǔn)備
Thread1開始準(zhǔn)備
Thread3開始準(zhǔn)備
Thread2準(zhǔn)備完畢
Thread3準(zhǔn)備完畢
Thread1準(zhǔn)備完畢
所有線程準(zhǔn)備完畢Thread1開始執(zhí)行
所有線程準(zhǔn)備完畢Thread3開始執(zhí)行
所有線程準(zhǔn)備完畢Thread2開始執(zhí)行
CyclicBarrier的構(gòu)造函數(shù)需要傳入等待時數(shù)量,然后每個線程在合適的時候調(diào)用await等待其他線程部翘,當(dāng)指定數(shù)量的線程都調(diào)用await后,所有線程一起開始執(zhí)行响委。
CyclicBarrier構(gòu)造還可以傳入一個Runnable新思,當(dāng)調(diào)用awit的線程達(dá)到一定數(shù)量時,會挑選一個線程率先執(zhí)行這個Runnable.如下:
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->System.out.println(Thread.currentThread().getName() + "+++"));
Random random = new Random();
IntStream.of(1,2,3).forEach(i -> {
String name = "Thread" + i;
new Thread(){
@Override
public void run() {
setName(name);
System.out.println(getName() + "開始準(zhǔn)備");
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"準(zhǔn)備完畢");
try {
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("所有線程準(zhǔn)備完畢" + getName() + "開始執(zhí)行");
}
}.start();
});
}
Thread1開始準(zhǔn)備
Thread3開始準(zhǔn)備
Thread2開始準(zhǔn)備
Thread1準(zhǔn)備完畢
Thread2準(zhǔn)備完畢
Thread3準(zhǔn)備完畢
Thread3+++
所有線程準(zhǔn)備完畢Thread1開始執(zhí)行
所有線程準(zhǔn)備完畢Thread3開始執(zhí)行
所有線程準(zhǔn)備完畢Thread2開始執(zhí)行
5.Semaphore
類似于我們學(xué)過的進(jìn)程同步的PV操作赘风,看一個生產(chǎn)者消費(fèi)者的例子:
public static void main(String[] args) throws InterruptedException {
Semaphore apple = new Semaphore(0);
Semaphore plate = new Semaphore(1);
Thread Producer = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
System.out.println("申請盤子");
plate.acquire();
System.out.println("獲取盤子");
System.out.println("生成蘋果");
apple.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Thread Consumer = new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
try {
System.out.println("申請?zhí)O果");
apple.acquire();
System.out.println("獲取蘋果");
System.out.println("釋放盤子");
plate.release();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
Consumer.start();
Thread.sleep(1000);
Producer.start();
}
申請?zhí)O果
申請盤子
獲取盤子
生成蘋果
獲取蘋果
釋放盤子
用起來很簡單夹囚,acquire申請一個資源,release釋放一個邀窃,同樣也可以選擇調(diào)用重載方法荸哟,一次申請或釋放多個,構(gòu)造方法也可以傳入一個布爾類型變量瞬捕,表示是否公平分配(若為false鞍历,表示多個線程競爭一個資源時,誰等的時間長肪虎,誰優(yōu)先)劣砍。另外acquire為阻塞方法,一個線程可能無限等待扇救,所有有非阻塞的方法tryAcquire刑枝,調(diào)用availablePermits檢測當(dāng)前可用資源數(shù)。