Java多線程(一)
1. 并發(fā)與并行
并行:指兩個(gè)或多個(gè)事件在同一時(shí)刻發(fā)生(同時(shí)發(fā)生)砖第。
并發(fā):指兩個(gè)或多個(gè)事件在同一個(gè)時(shí)間段內(nèi)發(fā)生。
2. 進(jìn)程素征、線程
進(jìn)程:
- 進(jìn)程是正在運(yùn)行的程序的實(shí)例集嵌。
- 進(jìn)程是線程的容器,即一個(gè)進(jìn)程中可以開(kāi)啟多個(gè)線程御毅。
- 比如打開(kāi)一個(gè)瀏覽器根欧、打開(kāi)一個(gè)word等操作,都會(huì)創(chuàng)建進(jìn)程端蛆。
線程:
- 線程是進(jìn)程內(nèi)部的一個(gè)獨(dú)立執(zhí)行單元凤粗;
- 一個(gè)進(jìn)程可以同時(shí)并發(fā)運(yùn)行多個(gè)線程;
- 比如進(jìn)程可以理解為醫(yī)院今豆,線程是掛號(hào)嫌拣、就診柔袁、繳費(fèi)、拿藥等業(yè)務(wù)活動(dòng)
多線程:
多個(gè)線程并發(fā)執(zhí)行异逐。
3. 線程創(chuàng)建
Java中線程有四種創(chuàng)建方式:
- 繼承Thread類(lèi)
- 實(shí)現(xiàn)Runnable接口
- 實(shí)現(xiàn)Callable接口
- 線程池
3.1. 繼承Thread類(lèi)
第一步:創(chuàng)建自定義線程類(lèi)
package cn.edu.nwafu;
import java.util.Date;
/**
* @author shensr
* @version V1.0
* @description: 繼承Thread類(lèi) 創(chuàng)建線程
* @create 2019/9/14
**/
public class MyThread extends Thread{
@Override
public void run() {
for (int i = 0; i <10 ; i++) {
//getTime() 獲取時(shí)間的毫秒數(shù)
System.out.println("myThread執(zhí)行的時(shí)間:"+new Date().getTime());
}
}
}
第二步:創(chuàng)建測(cè)試類(lèi)
package cn.edu.nwafu;
import java.util.Date;
/**
* @author shensr
* @version V1.0
* @description: 測(cè)試類(lèi)
* @create 2019/9/14
**/
public class MyThreadTest {
public static void main(String[] args) {
//1.創(chuàng)建自定義線程類(lèi)實(shí)例
MyThread myThread = new MyThread();
//2.啟動(dòng)線程
myThread.start();
//3.在main主線程中打印信息
for (int i = 0; i < 10; i++) {
System.out.println("主線程執(zhí)行時(shí)間:" + new Date().getTime());
}
}
}
執(zhí)行結(jié)果如下(不唯一)
myThread執(zhí)行的時(shí)間:1568444953164
主線程執(zhí)行時(shí)間:1568444953164
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953165
主線程執(zhí)行時(shí)間:1568444953165
myThread執(zhí)行的時(shí)間:1568444953166
主線程執(zhí)行時(shí)間:1568444953166
主線程執(zhí)行時(shí)間:1568444953166
3.2 實(shí)現(xiàn)Runnable接口
第一步:創(chuàng)建自定義線程類(lèi)
package cn.edu.nwafu;
import java.util.Date;
/**
* @author shensr
* @version V1.0
* @description: 自定義類(lèi) 實(shí)現(xiàn)Runnable接口
* @create 2019/9/14
**/
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
}
}
}
第二步:創(chuàng)建測(cè)試類(lèi)
package cn.edu.nwafu;
import java.util.Date;
/**
* @author shensr
* @version V1.0
* @description: 測(cè)試 MyRunnable類(lèi)
* @create 2019/9/14
**/
public class MyRunnableTest {
public static void main(String[] args) {
//一捶索、實(shí)現(xiàn)Runnable接口
//1.通過(guò)Thread類(lèi)執(zhí)行Runnable類(lèi)
Thread thread = new Thread(new MyRunnable());
//2.啟動(dòng)線程
thread.setName("MyRunnable");
thread.start();
//3.在main主線程中打印信息
Thread.currentThread().setName("主線程");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:" + new Date().getTime()+"執(zhí)行次數(shù):"+i);
}
}
}
執(zhí)行結(jié)果如下(不唯一)
MyRunnable執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):1
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):2
MyRunnable執(zhí)行時(shí)間:1568448360595執(zhí)行次數(shù):1
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):2
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):4
主線程執(zhí)行時(shí)間:1568448360596執(zhí)行次數(shù):4
3.3 實(shí)現(xiàn)Callable接口
Callable需要使用FutureTask類(lèi)幫助執(zhí)行,F(xiàn)utureTask類(lèi)結(jié)構(gòu)如下:
Future接口:
- 判斷任務(wù)是否完成:isDone()
- 能夠中斷任務(wù):cancel()
- 能夠獲取任務(wù)執(zhí)行結(jié)果:get()
第一步:創(chuàng)建自定義線程類(lèi)
package cn.edu.nwafu;
import java.util.Date;
import java.util.concurrent.Callable;
/**
* @author shensr
* @version V1.0
* @description:
* @create 2019/9/14
**/
public class MyCallable implements Callable {
@Override
public Object call() throws Exception {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
}
return "MyCallable執(zhí)行完成灰瞻!";
}
}
第二步:創(chuàng)建測(cè)試類(lèi)
package cn.edu.nwafu;
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @author shensr
* @version V1.0
* @description:
* @create 2019/9/14
**/
public class MyCallableTest {
public static void main(String[] args) {
//3情组、實(shí)現(xiàn)Runnable接口
//3.1.創(chuàng)建FutureTask實(shí)例,創(chuàng)建MyCallable實(shí)例
FutureTask task = new FutureTask( new MyCallable());
//3.2. 創(chuàng)建Thread實(shí)例箩祥,執(zhí)行創(chuàng)建FutureTask
Thread thread = new Thread(task,"MyRunnable");
thread.start();
//3.3.在main主線程中打印信息
Thread.currentThread().setName("主線程");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:" + new Date().getTime()+"執(zhí)行次數(shù):"+i);
}
//3.4. 獲取并打印MyCallable返回結(jié)果
try {
System.out.println("MyCallable返回結(jié)果:"+task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
執(zhí)行結(jié)果如下(不唯一)
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):0
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):0
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):1
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):1
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):2
MyRunnable執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):2
主線程執(zhí)行時(shí)間:1568449491985執(zhí)行次數(shù):3
MyRunnable執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):3
主線程執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):4
MyRunnable執(zhí)行時(shí)間:1568449491986執(zhí)行次數(shù):4
MyCallable返回結(jié)果:MyCallable執(zhí)行完成院崇!
3.4 線程池-Executor
線程池線類(lèi)關(guān)系圖
Executor接口:
聲明了execute(Runnable runnable)方法,執(zhí)行任務(wù)代碼
ExecutorService接口:
繼承Executor接口袍祖,聲明方法:submit底瓣、invokeAll、invokeAny以及shutDown等
AbstractExecutorService抽象類(lèi):
實(shí)現(xiàn)ExecutorService接口蕉陋,基本實(shí)現(xiàn)ExecutorService中聲明的所有方法
ScheduledExecutorService接口:
繼承ExecutorService接口捐凭,聲明定時(shí)執(zhí)行任務(wù)方法
ThreadPoolExecutor類(lèi):
繼承類(lèi)AbstractExecutorService,實(shí)現(xiàn)execute凳鬓、submit茁肠、shutdown、shutdownNow方法
ScheduledThreadPoolExecutor類(lèi):
繼承ThreadPoolExecutor類(lèi)缩举,實(shí)現(xiàn)ScheduledExecutorService接口并實(shí)現(xiàn)其中的方法
Executors類(lèi):
提供快速創(chuàng)建線程池的方法
第一步:創(chuàng)建自定義類(lèi)實(shí)現(xiàn)Runnable接口
package cn.edu.nwafu;
import java.util.Date;
/**
* @author shensr
* @version V1.0
* @description: 自定義類(lèi) 實(shí)現(xiàn)Runnable接口
* @create 2019/9/14
**/
public class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i <5 ; i++) {
System.out.println(Thread.currentThread().getName()+"執(zhí)行時(shí)間:"+new Date().getTime()+"執(zhí)行次數(shù):"+i);
}
}
}
第二步:創(chuàng)建測(cè)試類(lèi)
package cn.edu.nwafu;
import java.util.Date;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @author shensr
* @version V1.0
* @description: 使用線程池創(chuàng)建線程
* @create 2019/9/14
**/
public class ThreadExecutor {
public static void main(String[] args) {
//4. 使用線程池創(chuàng)建線程
//4.1 使用Executors獲取線程池對(duì)象
ExecutorService executorService = Executors.newFixedThreadPool(10);
//4.2 通過(guò)線程池對(duì)象獲取線程并執(zhí)行MyRunnable
executorService.execute(new MyRunnable());
//4.3 主線程打印信息
for (int i = 0; i < 5; i++) {
System.out.println("主線程執(zhí)行時(shí)間:" + new Date().getTime());
}
}
}
4. 小結(jié)
4.1 實(shí)現(xiàn)接口和繼承Thread類(lèi)比較
- 接口更適合多個(gè)相同的程序代碼的線程去共享同一個(gè)資源垦梆。
- 接口可以避免java中的單繼承的局限性。
- 接口代碼可以被多個(gè)線程共享仅孩,代碼和線程獨(dú)立托猩。
- 線程池只能放入實(shí)現(xiàn)Runable或Callable接口的線程,不能直接放入繼承Thread的類(lèi)辽慕。
- 擴(kuò)充:在java中京腥,每次程序運(yùn)行至少啟動(dòng)2個(gè)線程。一個(gè)是main線程溅蛉,一個(gè)是垃圾收集線程公浪。
4.2 Runnable和Callable接口比較
相同點(diǎn):
- 兩者都是接口;
- 兩者都可用來(lái)編寫(xiě)多線程程序船侧;
- 兩者都需要調(diào)用Thread.start()啟動(dòng)線程欠气;
不同點(diǎn):
- 實(shí)現(xiàn)Callable接口的線程能返回執(zhí)行結(jié)果;而實(shí)現(xiàn)Runnable接口的線程不能返回結(jié)果勺爱;
- Callable接口的call()方法允許拋出異常晃琳;而Runnable接口的run()方法的不允許拋異常;
- 實(shí)現(xiàn)Callable接口的線程可以調(diào)用Future.cancel取消執(zhí)行 琐鲁,而實(shí)現(xiàn)Runnable接口的線程不能
注意點(diǎn):
- Callable接口支持返回執(zhí)行結(jié)果卫旱,此時(shí)需要調(diào)用FutureTask.get()方法實(shí)現(xiàn),此方法會(huì)阻塞主線程直到獲取‘將來(lái)’結(jié)果围段;當(dāng)不調(diào)用此方法時(shí)顾翼,主線程不會(huì)阻塞!
5. 線程生命周期
?5.1. 新建
- new關(guān)鍵字創(chuàng)建了一個(gè)線程之后奈泪,該線程就處于新建狀態(tài)
- JVM為線程分配內(nèi)存适贸,初始化成員變量值
5.2. 就緒
- 當(dāng)線程對(duì)象調(diào)用了start()方法之后,該線程處于就緒狀態(tài)
- JVM為線程創(chuàng)建方法棧和程序計(jì)數(shù)器涝桅,等待線程調(diào)度器調(diào)度
5.3. 運(yùn)行
- 就緒狀態(tài)的線程獲得CPU資源拜姿,開(kāi)始運(yùn)行run()方法,該線程進(jìn)入運(yùn)行狀態(tài)
5.4. 阻塞
當(dāng)發(fā)生如下情況時(shí)冯遂,線程將會(huì)進(jìn)入阻塞狀態(tài)
- 線程調(diào)用sleep()方法主動(dòng)放棄所占用的處理器資源
- 線程調(diào)用了一個(gè)阻塞式IO方法蕊肥,在該方法返回之前,該線程被阻塞
- 線程試圖獲得一個(gè)同步鎖(同步監(jiān)視器)蛤肌,但該同步鎖正被其他線程所持有壁却。
- 線程在等待某個(gè)通知(notify)
- 程序調(diào)用了線程的suspend()方法將該線程掛起。但這個(gè)方法容易導(dǎo)致死鎖裸准,所以應(yīng)該盡量避免使用該方法
5.5. 死亡
線程會(huì)以如下3種方式結(jié)束展东,結(jié)束后就處于死亡狀態(tài):
- run()或call()方法執(zhí)行完成,線程正常結(jié)束炒俱。
- 線程拋出一個(gè)未捕獲的Exception或Error盐肃。
- 調(diào)用該線程stop()方法來(lái)結(jié)束該線程,該方法容易導(dǎo)致死鎖权悟,不推薦使用恼蓬。
6. 線程安全問(wèn)題
6.1. 什么是線程安全
如果有多個(gè)線程同時(shí)運(yùn)行同一個(gè)實(shí)現(xiàn)了Runnable接口的類(lèi),程序每次運(yùn)行結(jié)果和單線程運(yùn)行的結(jié)果是一樣的僵芹,而且其他的變量的值也和預(yù)期的是一樣的处硬,就是線程安全的;反之拇派,則是線程不安全的荷辕。
6.2. 問(wèn)題演示
為了演示線程安全問(wèn)題,我們采用多線程模擬多個(gè)窗口同時(shí)售賣(mài)《哪吒之魔童降世》電影票件豌。
6.2.1. 第一步:創(chuàng)建售票線程類(lèi)
package cn.edu.nwafu.safe;
public class Ticket implements Runnable {
private int ticktNum = 100;
public void run() {
while(true){
if(ticktNum > 0){
//1.模擬出票時(shí)間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.打印進(jìn)程號(hào)和票號(hào)疮方,票數(shù)減1
String name = Thread.currentThread().getName();
System.out.println("線程"+name+"售票:"+ticktNum--);
}
}
}
}
6.2.2. 第二步:創(chuàng)建測(cè)試類(lèi)
package cn.edu.nwafu.safe;
import cn.edu.nwafu.safe.Ticket;
public class TicketDemo {
public static void main(String[] args){
Ticket ticket = new Ticket();
Thread thread1 = new Thread(ticket, "窗口1");
Thread thread2 = new Thread(ticket, "窗口2");
Thread thread3 = new Thread(ticket, "窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
6.3. 問(wèn)題分析
線程安全問(wèn)題都是由全局變量及靜態(tài)變量引起的。
若每個(gè)線程對(duì)全局變量茧彤、靜態(tài)變量只讀骡显,不寫(xiě),一般來(lái)說(shuō),這個(gè)變量是線程安全的惫谤;
若有多個(gè)線程同時(shí)執(zhí)行寫(xiě)操作壁顶,一般都需要考慮線程同步,否則的話就可能影響線程安全溜歪。
綜上所述若专,線程安全問(wèn)題根本原因:
- 多個(gè)線程在操作共享的數(shù)據(jù);
- 操作共享數(shù)據(jù)的線程代碼有多條蝴猪;
- 多個(gè)線程對(duì)共享數(shù)據(jù)有寫(xiě)操作调衰;
6.4. 問(wèn)題解決-線程同步
要解決以上線程問(wèn)題,只要在某個(gè)線程修改共享資源的時(shí)候自阱,其他線程不能修改該資源嚎莉,等待修改完畢同步之后,才能去搶奪CPU資源沛豌,完成對(duì)應(yīng)的操作趋箩,保證了數(shù)據(jù)的同步性,解決了線程不安全的現(xiàn)象琼懊。
為了保證每個(gè)線程都能正常執(zhí)行共享資源操作,Java引入了7種線程同步機(jī)制阁簸。
同步代碼塊(synchronized)
同步方法(synchronized)
同步鎖(ReenreantLock)
特殊域變量(volatile)
局部變量(ThreadLocal)
阻塞隊(duì)列(LinkedBlockingQueue)
原子變量(Atomic*)
6.4.1. 同步代碼塊(synchronized)
同步代碼塊 :
synchronized 關(guān)鍵字可以用于方法中的某個(gè)區(qū)塊中,表示只對(duì)這個(gè)區(qū)塊的資源實(shí)行互斥訪問(wèn)哼丈。
語(yǔ)法:
synchronized(同步鎖){
//TODO 需要同步操作的代碼
}
同步鎖:
對(duì)象的同步鎖只是一個(gè)概念,可以想象為在對(duì)象上標(biāo)記了一個(gè)鎖.
- 鎖對(duì)象可以是任意類(lèi)型启妹。
- 多個(gè)線程要使用同一把鎖。
注意:在任何時(shí)候,最多允許一個(gè)線程擁有同步鎖,誰(shuí)拿到鎖就進(jìn)入代碼塊,其他的線程只能在外等著(BLOCKED)醉旦。
使用同步代碼塊代碼如下:
package cn.edu.nwafu.safe;
public class Ticket implements Runnable {
private int ticktNum = 100;
//定義鎖對(duì)象
Object obj = new Object();
public void run() {
while(true){
synchronized (obj){
if(ticktNum > 0){
//1.模擬出票時(shí)間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//2.打印進(jìn)程號(hào)和票號(hào)饶米,票數(shù)減1
String name = Thread.currentThread().getName();
System.out.println("線程"+name+"售票:"+ticktNum--);
}
}
}
}
}
6.4.2. 同步方法(synchronized)
同步方法:
使用synchronized修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時(shí)候,其他線程只能在方法外等著。
格式:
public synchronized void method(){
//TODO 可能會(huì)產(chǎn)生線程安全問(wèn)題的代碼
}
同步鎖是誰(shuí)?
- 對(duì)于非static方法,同步鎖就是this车胡。
- 對(duì)于static方法,同步鎖是當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象(類(lèi)名.class)檬输。
使用同步方法代碼如下:
package cn.edu.nwafu.safe;
/**
* @author shensr
* @version V1.0
* @description: 電影票對(duì)象
* @create 2019/9/15
**/
public class Ticket implements Runnable {
private int ticketNum = 50;//電影票數(shù)量
private Object obj = new Object(); //鎖對(duì)象,可以理解為鑰匙匈棘,拿到鑰匙可以執(zhí)行代碼
//同步方法實(shí)現(xiàn)
public void run() {
while (true) {
safeTicket();
}
}
/**
* 同步方法實(shí)現(xiàn)
* 注意 :對(duì)于static方法,同步鎖是當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象(類(lèi)名.class)丧慈。
* 對(duì)于非static方法,同步鎖就是this。
*/
private synchronized void safeTicket() {
if(ticketNum>0){
//郵票主卫,線程睡眠1000ms逃默,售票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程"+Thread.currentThread().getName()+"售票:"+ticketNum--);
}
}
}
6.4.3. 同步鎖(ReenreantLock)
同步鎖:
java.util.concurrent.locks.Lock 機(jī)制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強(qiáng)大,更體現(xiàn)面向?qū)ο蟆?/p>
同步鎖方法:
public void lock() :加同步鎖簇搅。
public void unlock() :釋放同步鎖完域。
使用重入鎖代碼如下:
package cn.edu.nwafu.safe;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author shensr
* @version V1.0
* @description: 電影票對(duì)象
* @create 2019/9/15
**/
public class Ticket implements Runnable {
//3. 同步鎖實(shí)現(xiàn)
private int ticketNum = 50;//電影票數(shù)量
//定義鎖對(duì)象(重入鎖):構(gòu)造函數(shù)參數(shù)為線程是否公平獲取鎖true-公平;
// false-不公平瘩将,即由某個(gè)線程獨(dú)占吟税,默認(rèn)是false
private Lock lock = new ReentrantLock(true);
public void run() {
while (true) {
lock.lock();//加鎖
try {
if (ticketNum > 0) {
//郵票凹耙,線程睡眠1000ms,售票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() + "售票:" + ticketNum--);
}
}catch (Exception e){
}finally {
lock.unlock();//釋放鎖
}
}
}
}
注意:一定要記得釋放鎖肠仪,否則會(huì)引發(fā)死鎖肖抱。
6.5. 小結(jié)
Synchronized和Lock區(qū)別
- synchronized是java內(nèi)置關(guān)鍵字,在jvm層面藤韵,Lock是個(gè)java類(lèi)虐沥;
- synchronized無(wú)法判斷是否獲取鎖的狀態(tài)熊经,Lock可以判斷是否獲取到鎖泽艘;
- synchronized會(huì)自動(dòng)釋放鎖(a 線程執(zhí)行完同步代碼會(huì)釋放鎖 ;b 線程執(zhí)行過(guò)程中發(fā)生異常會(huì)釋放鎖)镐依,Lock需在finally中手工釋放鎖(unlock()方法釋放鎖)匹涮,否則容易造成線程死鎖;
- 用synchronized關(guān)鍵字的兩個(gè)線程1和線程2槐壳,如果當(dāng)前線程1獲得鎖然低,線程2線程等待。如果線程1阻塞务唐,線程2則會(huì)一直等待下去雳攘,而Lock鎖就不一定會(huì)等待下去,如果嘗試獲取不到鎖枫笛,線程可以不用一直等待就結(jié)束了吨灭;
- synchronized的鎖可重入(拿到鎖之后還可以再次申請(qǐng))、不可中斷刑巧、非公平喧兄,而Lock鎖可重入、可判斷啊楚、可公平(兩者皆可)
- Lock鎖適合大量同步的代碼的同步問(wèn)題吠冤,synchronized鎖適合代碼少量的同步問(wèn)題。
7. 線程死鎖
7.1. 什么是死鎖
多線程以及多進(jìn)程改善了系統(tǒng)資源的利用率并提高了系統(tǒng)的處理能力恭理。然而拯辙,并發(fā)執(zhí)行也帶來(lái)了新的問(wèn)題--死鎖。
所謂死鎖是指多個(gè)線程因競(jìng)爭(zhēng)資源而造成的一種僵局(互相等待)颜价,若無(wú)外力作用涯保,這些進(jìn)程都將無(wú)法向前推進(jìn)。
7.2. 死鎖產(chǎn)生的必要條件(一定要熟悉)
以下這四個(gè)條件是死鎖的必要條件拍嵌,只要系統(tǒng)發(fā)生死鎖遭赂,這些條件必然成立,而只要上述條件之一不滿(mǎn)足横辆,就不會(huì)發(fā)生死鎖撇他。
7.2.1. 互斥條件
進(jìn)程要求對(duì)所分配的資源(如打印機(jī))進(jìn)行排他性控制茄猫,即在一段時(shí)間內(nèi)某資源僅為一個(gè)進(jìn)程所占有。此時(shí)若有其他進(jìn)程請(qǐng)求該資源困肩,則請(qǐng)求進(jìn)程只能等待划纽。
7.2.2. 不可剝奪條件
進(jìn)程所獲得的資源在未使用完畢之前,不能被其他進(jìn)程強(qiáng)行奪走锌畸,即只能由獲得該資源的進(jìn)程自己來(lái)釋放(只能是主動(dòng)釋放)勇劣。
7.2.3. 請(qǐng)求與保持條件
進(jìn)程已經(jīng)保持了至少一個(gè)資源,但又提出了新的資源請(qǐng)求潭枣,而該資源已被其他進(jìn)程占有比默,此時(shí)請(qǐng)求進(jìn)程被阻塞,但對(duì)自己已獲得的資源保持不放盆犁。
7.2.4. 循環(huán)等待條件
存在一種進(jìn)程資源的循環(huán)等待鏈命咐,鏈中每一個(gè)進(jìn)程已獲得的資源同時(shí)被 鏈中下一個(gè)進(jìn)程所請(qǐng)求。即存在一個(gè)處于等待狀態(tài)的進(jìn)程集合{Pl, P2, …, pn}谐岁,其中Pi等 待的資源被P(i+1)占有(i=0, 1, …, n-1)醋奠,Pn等待的資源被P0占有,如圖所示伊佃。
7.2.5. 死鎖示例代碼
package cn.edu.nwafu.safe;
/**
* @author shensr
* @version V1.0
* @description: 模擬死鎖
* @create 2019/9/15
**/
public class DeadLockRunnable implements Runnable {
private static Object obj1 = new Object();//定義成靜態(tài)變量窜司,使線程可以共享實(shí)例
private static Object obj2 = new Object();//定義成靜態(tài)變量,使線程可以共享實(shí)例
public int flag ;
public DeadLockRunnable(int flag) {
this.flag = flag;
}
public void run() {
if(flag == 1){
System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1航揉,正在請(qǐng)求obj2");
synchronized (obj1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj2){
System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1和obj2");
}
}
}
if(flag==2){
System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj2塞祈,正在請(qǐng)求obj1");
synchronized (obj2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (obj1){
System.out.println(Thread.currentThread().getName()+"已經(jīng)獲取到obj1和obj2");
}
}
}
}
}
測(cè)試:
package cn.edu.nwafu.safe;
/**
* @author shensr
* @version V1.0
* @description: 測(cè)試死鎖
* @create 2019/9/15
**/
public class DeadLockRunnableTest {
public static void main(String[] args) {
//1.創(chuàng)建兩個(gè)DeadLockRunnable實(shí)例
DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable(1);
DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable(2);
//2.創(chuàng)建兩個(gè)線程執(zhí)行兩個(gè)DeadLockRunnable實(shí)例
Thread thread1 = new Thread(deadLockRunnable1, "runnable1");
Thread thread2 = new Thread(deadLockRunnable2, "runnable2");
thread1.start();
thread2.start();
}
}
執(zhí)行效果如下:表示死鎖產(chǎn)生
runnable1已經(jīng)獲取到obj1,正在請(qǐng)求obj2
runnable2已經(jīng)獲取到obj2迷捧,正在請(qǐng)求obj1
7.3. 死鎖處理
- 預(yù)防死鎖:通過(guò)設(shè)置某些限制條件织咧,去破壞產(chǎn)生死鎖的四個(gè)必要條件中的一個(gè)或幾個(gè)條件,來(lái)防止死鎖的發(fā)生漠秋。
- 避免死鎖:在資源的動(dòng)態(tài)分配過(guò)程中笙蒙,用某種方法去防止系統(tǒng)進(jìn)入不安全狀態(tài),從而避免死鎖的發(fā)生庆锦。
- 檢測(cè)死鎖:允許系統(tǒng)在運(yùn)行過(guò)程中發(fā)生死鎖捅位,但可設(shè)置檢測(cè)機(jī)構(gòu)及時(shí)檢測(cè)死鎖的發(fā)生,并采取適當(dāng)措施加以清除搂抒。
- 解除死鎖:當(dāng)檢測(cè)出死鎖后艇搀,便采取適當(dāng)措施將進(jìn)程從死鎖狀態(tài)中解脫出來(lái)。
7.3.1. 死鎖預(yù)防
預(yù)防死鎖是設(shè)法至少破壞產(chǎn)生死鎖的四個(gè)必要條件之一,嚴(yán)格的防止死鎖的出現(xiàn)求晶。
7.3.1.1. 破壞“互斥”條件
“互斥”條件是無(wú)法破壞的焰雕。因此,在死鎖預(yù)防里主要是破壞其他幾個(gè)必要條件芳杏,而不去涉及破壞“互斥”條件矩屁。
7.3.1.2. 破壞“占有并等待”條件
破壞“占有并等待”條件辟宗,就是在系統(tǒng)中不允許進(jìn)程在已獲得某種資源的情況下,申請(qǐng)其他資源吝秕。即要想出一個(gè)辦法泊脐,阻止進(jìn)程在持有資源的同時(shí)申請(qǐng)其他資源。
- 方法一:一次性分配資源烁峭,即創(chuàng)建進(jìn)程時(shí)容客,要求它申請(qǐng)所需的全部資源,系統(tǒng)或滿(mǎn)足其所有要求约郁,或什么也不給它缩挑。
- 方法二:要求每個(gè)進(jìn)程提出新的資源申請(qǐng)前,釋放它所占有的資源棍现。這樣调煎,一個(gè)進(jìn)程在需要資源S時(shí)镜遣,須先把它先前占有的資源R釋放掉己肮,然后才能提出對(duì)S的申請(qǐng),即使它可能很快又要用到資源R悲关。
7.3.1.3. 破壞“不可搶占”條件
破壞“不可搶占”條件就是允許對(duì)資源實(shí)行搶奪谎僻。
- 方法一:如果占有某些資源的一個(gè)進(jìn)程進(jìn)行進(jìn)一步資源請(qǐng)求被拒絕,則該進(jìn)程必須釋放它最初占有的資源寓辱,如果有必要艘绍,可再次請(qǐng)求這些資源和另外的資源。
- 方法二:如果一個(gè)進(jìn)程請(qǐng)求當(dāng)前被另一個(gè)進(jìn)程占有的一個(gè)資源秫筏,則操作系統(tǒng)可以搶占另一個(gè)進(jìn)程诱鞠,要求它釋放資源。只有在任意兩個(gè)進(jìn)程的優(yōu)先級(jí)都不相同的條件下这敬,方法二才能預(yù)防死鎖航夺。
7.3.1.4. 破壞“循環(huán)等待”條件
破壞“循環(huán)等待”條件的一種方法,是將系統(tǒng)中的所有資源統(tǒng)一編號(hào)崔涂,進(jìn)程可在任何時(shí)刻提出資源申請(qǐng)阳掐,但所有申請(qǐng)必須按照資源的編號(hào)順序(升序)提出。這樣做就能保證系統(tǒng)不出現(xiàn)死鎖冷蚂。
7.3.2. 死鎖避免
避免死鎖不嚴(yán)格限制產(chǎn)生死鎖的必要條件的存在,因?yàn)榧词顾梨i的必要條件存在,也不一定發(fā)生死鎖缭保。
7.3.2.1. 有序資源分配法
該算法實(shí)現(xiàn)步驟如下:
- 必須為所有資源統(tǒng)一編號(hào),例如打印機(jī)為1蝙茶、傳真機(jī)為2艺骂、磁盤(pán)為3等
- 同類(lèi)資源必須一次申請(qǐng)完,例如打印機(jī)和傳真機(jī)一般為同一個(gè)機(jī)器隆夯,必須同時(shí)申請(qǐng)
- 不同類(lèi)資源必須按順序申請(qǐng)
例如:有兩個(gè)進(jìn)程P1和P2钳恕,有兩個(gè)資源R1和R2
P1請(qǐng)求資源:R1孕锄、R2
P2請(qǐng)求資源:R1、R2
這樣就破壞了環(huán)路條件苞尝,避免了死鎖的發(fā)生畸肆。
7.3.2.2. 銀行家算法
銀行家算法(Banker's Algorithm)是一個(gè)避免死鎖(Deadlock)的著名算法,是由艾茲格·迪杰斯特拉在1965年為T(mén).H.E系統(tǒng)設(shè)計(jì)的一種避免死鎖產(chǎn)生的算法宙址。它以銀行借貸系統(tǒng)的分配策略為基礎(chǔ)轴脐,判斷并保證系統(tǒng)的安全運(yùn)行。流程圖如下:
?銀行家算法的基本思想是分配資源之前抡砂,判斷系統(tǒng)是否是安全的大咱;若是,才分配注益。它是最具有代表性的避免死鎖的算法碴巾。
設(shè)進(jìn)程i提出請(qǐng)求REQUEST [i],則銀行家算法按如下規(guī)則進(jìn)行判斷丑搔。
如果REQUEST [i]<= NEED[i厦瓢,j],則轉(zhuǎn)(2)啤月;否則煮仇,出錯(cuò)。
如果REQUEST [i]<= AVAILABLE[i]谎仲,則轉(zhuǎn)(3)浙垫;否則,等待郑诺。
系統(tǒng)試探分配資源夹姥,修改相關(guān)數(shù)據(jù):
AVAILABLE[i]-=REQUEST[i];//可用資源數(shù)-請(qǐng)求資源數(shù)
ALLOCATION[i]+=REQUEST[i];//已分配資源數(shù)+請(qǐng)求資源數(shù)
NEED[i]-=REQUEST[i];//需要資源數(shù)-請(qǐng)求資源數(shù)
- 系統(tǒng)執(zhí)行安全性檢查,如安全辙诞,則分配成立辙售;否則試探險(xiǎn)性分配作廢,系統(tǒng)恢復(fù)原狀倘要,進(jìn)程等待圾亏。
7.3.2.3. 順序加鎖
當(dāng)多個(gè)線程需要相同的一些鎖,但是按照不同的順序加鎖封拧,死鎖就很容易發(fā)生志鹃。
例如以下兩個(gè)線程就會(huì)死鎖:
Thread 1:
lock A (when C locked)
lock B (when C locked)
wait for C
Thread 2:
wait for A
wait for B
lock C (when A locked)
如果能確保所有的線程都是按照相同的順序獲得鎖,那么死鎖就不會(huì)發(fā)生泽西。 例如以下兩個(gè)線程就不會(huì)死鎖
Thread 1:
lock A
lock B
lock C
Thread 2:
wait for A
wait for B
wait for C
按照順序加鎖是一種有效的死鎖預(yù)防機(jī)制曹铃。但是,這種方式需要事先知道所有可能會(huì)用到的鎖捧杉,但總有些時(shí)候是無(wú)法預(yù)知的陕见,所以該種方式只適合特定場(chǎng)景秘血。
7.3.2.4. 限時(shí)加鎖
限時(shí)加鎖是線程在嘗試獲取鎖的時(shí)候加一個(gè)超時(shí)時(shí)間,若超過(guò)這個(gè)時(shí)間則放棄對(duì)該鎖請(qǐng)求评甜,并回退并釋放所有已經(jīng)獲得的鎖灰粮,然后等待一段隨機(jī)的時(shí)間再重試
以下是一個(gè)例子,展示了兩個(gè)線程以不同的順序嘗試獲取相同的兩個(gè)鎖忍坷,在發(fā)生超時(shí)后回退并重試的場(chǎng)景:
Thread 1 locks A
Thread 2 locks B
Thread 1 attempts to lock B but is blocked
Thread 2 attempts to lock A but is blocked
Thread 1’s lock attempt on B times out
Thread 1 backs up and releases A as well
Thread 1 waits randomly (e.g. 257 millis) before retrying.
Thread 2’s lock attempt on A times out
Thread 2 backs up and releases B as well
Thread 2 waits randomly (e.g. 43 millis) before retrying.
在上面的例子中粘舟,線程2比線程1早200毫秒進(jìn)行重試加鎖,因此它可以先成功地獲取到兩個(gè)鎖佩研。這時(shí)柑肴,線程1嘗試獲取鎖A并且處于等待狀態(tài)。當(dāng)線程2結(jié)束時(shí)旬薯,線程1也可以順利的獲得這兩個(gè)鎖晰骑。
這種方式有兩個(gè)缺點(diǎn):
當(dāng)線程數(shù)量少時(shí),該種方式可避免死鎖绊序,但當(dāng)線程數(shù)量過(guò)多硕舆,這些線程的加鎖時(shí)限相同的概率就高很多,可能會(huì)導(dǎo)致超時(shí)后重試的死循環(huán)政模。
Java中不能對(duì)synchronized同步塊設(shè)置超時(shí)時(shí)間岗宣。你需要?jiǎng)?chuàng)建一個(gè)自定義鎖,或使用Java5中java.util.concurrent包下的工具淋样。
7.3.3. 死鎖檢測(cè)
預(yù)防和避免死鎖系統(tǒng)開(kāi)銷(xiāo)大且不能充分利用資源,更好的方法是不采取任何限制性措施胁住,而是提供檢測(cè)和解脫死鎖的手段趁猴,這就是死鎖檢測(cè)和恢復(fù)。
死鎖檢測(cè)數(shù)據(jù)結(jié)構(gòu):
- E是現(xiàn)有資源向量(existing resource vector)彪见,代碼每種已存在資源的總數(shù)
- A是可用資源向量(available resource vector)儡司,那么Ai表示當(dāng)前可供使用的資源數(shù)(即沒(méi)有被分配的資源)
- C是當(dāng)前分配矩陣(current allocation matrix),C的第i行代表Pi當(dāng)前所持有的每一種類(lèi)型資源的資源數(shù)
- R是請(qǐng)求矩陣(request matrix)余指,R的每一行代表P所需要的資源的數(shù)量
死鎖檢測(cè)步驟:
尋找一個(gè)沒(méi)有結(jié)束標(biāo)記的進(jìn)程Pi捕犬,對(duì)于它而言R矩陣的第i行向量小于或等于A。
如果找到了這樣一個(gè)進(jìn)程酵镜,執(zhí)行該進(jìn)程碉碉,然后將C矩陣的第i行向量加到A中,標(biāo)記該進(jìn)程淮韭,并轉(zhuǎn)到第1步
如果沒(méi)有這樣的進(jìn)程垢粮,那么算法終止
算法結(jié)束時(shí),所有沒(méi)有標(biāo)記過(guò)的進(jìn)程都是死鎖進(jìn)程靠粪。
7.3.4. 死鎖恢復(fù)
利用搶占恢復(fù)蜡吧。
臨時(shí)將某個(gè)資源從它的當(dāng)前所屬進(jìn)程轉(zhuǎn)移到另一個(gè)進(jìn)程毫蚓。
這種做法很可能需要人工干預(yù),主要做法是否可行需取決于資源本身的特性昔善。
利用回滾恢復(fù)
- 周期性的將進(jìn)程的狀態(tài)進(jìn)行備份元潘,當(dāng)發(fā)現(xiàn)進(jìn)程死鎖后,根據(jù)備份將該進(jìn)程復(fù)位到一個(gè)更早的君仆,還沒(méi)有取得所需的資源的狀態(tài)柬批,接著就把這些資源分配給其他死鎖進(jìn)程。
通過(guò)殺死進(jìn)程恢復(fù)
最直接簡(jiǎn)單的方式就是殺死一個(gè)或若干個(gè)進(jìn)程袖订。
盡可能保證殺死的進(jìn)程可以從頭再來(lái)而不帶來(lái)副作用氮帐。
8. 線程通訊
8.1. 為什么要線程通信
多個(gè)線程并發(fā)執(zhí)行時(shí),在默認(rèn)情況下CPU是隨機(jī)切換線程的洛姑,有時(shí)我們希望CPU按我們的規(guī)律執(zhí)行線程上沐,此時(shí)就需要線程之間協(xié)調(diào)通信。
8.2. 線程通訊方式
線程間通信常用方式如下:
- 休眠喚醒方式:
Object的wait楞艾、notify参咙、notifyAll
Condition的await、signal硫眯、signalAll
- CountDownLatch:用于某個(gè)線程A等待若干個(gè)其他線程執(zhí)行完之后蕴侧,它才執(zhí)行
- CyclicBarrier:一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行
- Semaphore:用于控制對(duì)某組資源的訪問(wèn)權(quán)限
8.2.1. 休眠喚醒方式
多線程打印10以?xún)?nèi)的奇偶數(shù):
i從0開(kāi)始,當(dāng)i是奇數(shù)時(shí)两入,奇數(shù)線程打印净宵,偶數(shù)線程等待;
? 當(dāng)i是偶數(shù)時(shí)裹纳,偶數(shù)線程打印择葡,奇數(shù)線程等待。
方式一:Object的wait剃氧、notify敏储、notifyAll
package cn.edu.nwafu.communication;
/**
* @author shensr
* @version V1.0
* @description: **多線程打印10以?xún)?nèi)的奇偶數(shù):**
* i從0開(kāi)始,當(dāng)i是奇數(shù)時(shí)朋鞍,奇數(shù)線程打印已添,偶數(shù)線程等待;
* ? 當(dāng)i是偶數(shù)時(shí)滥酥,偶數(shù)線程打印更舞,奇數(shù)線程等待。
* 使用Object的wait恨狈、notify疏哗、notifyAll方式實(shí)現(xiàn)線程通訊
* @create 2019/9/16
**/
public class WaitNotifyRunnable {
private Object obj = new Object();
private Integer i=0;
public void odd() {
while(i<10){
synchronized (obj){
if(i%2 == 1){
System.out.println("奇數(shù):"+i);
i++;
obj.notify();
} else {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public void even(){
while(i<10){
synchronized (obj){
if(i%2 == 0){
System.out.println("偶數(shù):"+i);
i++;
obj.notify();
} else {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args){
final WaitNotifyRunnable runnable = new WaitNotifyRunnable();
Thread t1 = new Thread(new Runnable() {
public void run() {
runnable.odd();
}
}, "偶數(shù)線程");
//lambda表達(dá)式
Thread t2 = new Thread(() -> runnable.even(), "奇數(shù)線程");
t1.start();
t2.start();
}
}
注意: Object的wait、notify、notifyAll這些方法依賴(lài)于synchronized關(guān)鍵字返奉,沒(méi)有就會(huì)拋出java.lang.IllegalMonitorStateException異常
方式二:Condition的await贝搁、signal、signalAll
package cn.edu.nwafu.communication.conditon;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author shensr
* @version V1.0
* @description:
* @create 2019/9/16
**/
public class WaitNotifyRunnable {
private Lock lock = new ReentrantLock();//這里要設(shè)置為獨(dú)占鎖芽偏,參數(shù)要為false
private Condition condition = lock.newCondition();
private Integer i=0;
public void odd() {
while(i<10){
lock.lock();
try{
if(i%2 == 1){
System.out.println("奇數(shù):"+i);
i++;
condition.signal();
} else {
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public void even(){
while(i<10){
lock.lock();
try{
if(i%2 == 0){
System.out.println("偶數(shù):"+i);
i++;
condition.signal();
} else {
condition.await();
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args){
final WaitNotifyRunnable runnable = new WaitNotifyRunnable();
Thread t1 = new Thread(()->runnable.odd(), "偶數(shù)線程");
Thread t2 = new Thread(() -> runnable.even(), "奇數(shù)線程");
t1.start();
t2.start();
}
}
Object和Condition休眠喚醒區(qū)別
- object wait()必須在synchronized(同步鎖)下使用雷逆,
- object wait()必須要通過(guò)notify()方法進(jìn)行喚醒
- condition await() 必須和Lock(互斥鎖/共享鎖)配合使用
- condition await() 必須通過(guò) signal() 方法進(jìn)行喚醒
8.2.2. CountDownLatch方式
CountDownLatch是在java1.5被引入的,存在于java.util.concurrent包下污尉。
CountDownLatch這個(gè)類(lèi)能夠使一個(gè)線程等待其他線程完成各自的工作后再執(zhí)行膀哲。
CountDownLatch是通過(guò)一個(gè)計(jì)數(shù)器來(lái)實(shí)現(xiàn)的,計(jì)數(shù)器的初始值為線程的數(shù)量被碗。
每當(dāng)一個(gè)線程完成了自己的任務(wù)后某宪,計(jì)數(shù)器的值就會(huì)減1。當(dāng)計(jì)數(shù)器值到達(dá)0時(shí)锐朴,它表示所有的線程已經(jīng)完成了任務(wù)兴喂,然后在閉鎖上等待的線程就可以恢復(fù)執(zhí)行任務(wù)脯颜。
示例代碼:
package cn.edu.nwafu.communication.countDownLatch;
import java.util.concurrent.CountDownLatch;
/**
* @author shensr
* @version V1.0
* @description: CountDownLatch方式
* @create 2019/9/16
**/
public class CountDown {
private Integer i = 0;
private CountDownLatch countDownLatch = new CountDownLatch(1);
public void odd(){
while(i < 10){
if(i%2 == 1){
System.out.println("奇數(shù):"+i);
i++;
countDownLatch.countDown();
} else {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public void even(){
while(i < 10){
if(i%2 == 0){
System.out.println("偶數(shù):"+i);
i++;
countDownLatch.countDown();
} else {
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args){
final CountDown countDown = new CountDown();
Thread t1 = new Thread(() -> countDown.odd(),"奇數(shù)");
Thread t2 = new Thread(() -> countDown.even(),"偶數(shù)");
t1.start();
t2.start();
}
}
8.2.3. CyclicBarrier方式
CyclicBarrier是在java1.5被引入的放接,存在于java.util.concurrent包下慷垮。
CyclicBarrier實(shí)現(xiàn)讓一組線程等待至某個(gè)狀態(tài)之后再全部同時(shí)執(zhí)行冒冬。
CyclicBarrier底層是基于ReentrantLock和Condition實(shí)現(xiàn)。
三個(gè)線程同時(shí)啟動(dòng)实柠,示例代碼如下:
package cn.edu.nwafu.communication.cyclicbarrier;
import java.util.Date;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
/**
* @author shensr
* @version V1.0
* @description:
* @create 2019/9/16
**/
public class CyclicBarrierDemo {
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
public static void run(){
System.out.println(Thread.currentThread().getName()+":準(zhǔn)備...");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"啟動(dòng)完畢:"+new Date().getTime());
}
public static void main(String[] args){
new Thread(() -> run(),"線程1").start();
new Thread(() -> run(),"線程2").start();
new Thread(() -> run(),"線程3").start();
}
}
執(zhí)行效果如下:三個(gè)線程同時(shí)啟動(dòng)
線程1:準(zhǔn)備...
線程2:準(zhǔn)備...
線程3:準(zhǔn)備...
線程3啟動(dòng)完畢:1568605543873
線程1啟動(dòng)完畢:1568605543873
線程2啟動(dòng)完畢:1568605543873
8.2.4. Semaphore方式
Semaphore是在java1.5被引入的橱脸,存在于java.util.concurrent包下抒和。
Semaphore用于控制對(duì)某組資源的訪問(wèn)權(quán)限膳沽。
工人使用機(jī)器工作汗菜,示例代碼如下:
package com.multithread.thread;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
static class Machine implements Runnable{
private int num;
private Semaphore semaphore;
public Machine(int num, Semaphore semaphore) {
this.num = num;
this.semaphore = semaphore;
}
public void run() {
try {
semaphore.acquire();//請(qǐng)求機(jī)器
System.out.println("工人"+this.num+"請(qǐng)求機(jī)器,正在使用機(jī)器");
Thread.sleep(1000);
System.out.println("工人"+this.num+"使用完畢贵少,已經(jīng)釋放機(jī)器");
semaphore.release();//釋放機(jī)器
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args){
int worker = 8;//工人數(shù)
Semaphore semaphore = new Semaphore(3);//機(jī)器數(shù)
for (int i=0; i< worker; i++){
new Thread(new Machine(i, semaphore)).start();
}
}
}
執(zhí)行效果如下:
工人[1]請(qǐng)求機(jī)器呵俏,正在使用機(jī)器...
工人[0]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[2]請(qǐng)求機(jī)器滔灶,正在使用機(jī)器...
工人[0]使用完畢,已經(jīng)釋放機(jī)器!!!
工人[1]使用完畢吼肥,已經(jīng)釋放機(jī)器!!!
工人[3]請(qǐng)求機(jī)器录平,正在使用機(jī)器...
工人[4]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[2]使用完畢缀皱,已經(jīng)釋放機(jī)器!!!
工人[6]請(qǐng)求機(jī)器斗这,正在使用機(jī)器...
工人[3]使用完畢,已經(jīng)釋放機(jī)器!!!
工人[4]使用完畢啤斗,已經(jīng)釋放機(jī)器!!!
工人[5]請(qǐng)求機(jī)器表箭,正在使用機(jī)器...
工人[7]請(qǐng)求機(jī)器,正在使用機(jī)器...
工人[6]使用完畢钮莲,已經(jīng)釋放機(jī)器!!!
工人[7]使用完畢免钻,已經(jīng)釋放機(jī)器!!!
工人[5]使用完畢彼水,已經(jīng)釋放機(jī)器!!!
8.3 小結(jié)
8.3.1. sleep和wait區(qū)別
wait | sleep | |
---|---|---|
同步 | 只能在同步上下文中調(diào)用wait方法,否則拋出java.lang.IllegalMonitorStateException異常 | 不需要在同步方法或同步代碼塊中調(diào)用 |
作用對(duì)象 | wait方法定義在Object類(lèi)中极舔,作用于對(duì)象本身 | sleep方法定義在java.lang.Thread中凤覆,作用于當(dāng)前線程 |
釋放鎖資源 | 是 | 否 |
喚醒條件 | 其他線程調(diào)用對(duì)象的notify()方法或則notifyAll()方法 | 超時(shí)或則調(diào)用interrupt方法 |
方法屬性 | wait是實(shí)例方法 | sleep是靜態(tài)方法 |
8.3.2. wait和notify區(qū)別
wait和notify都是Object中的方法
wait和notify執(zhí)行前線程都必須獲得對(duì)象鎖
wait的作用是使當(dāng)前線程進(jìn)行等待
notify的作用是通知其他等待當(dāng)前線程的對(duì)象鎖的線程