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ā)生死鎖,這些條件必然成立,而只要上述條件之一不滿足,就不會(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)或滿足其所有要求,或什么也不給它。
- 方法二:要求每個(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ì)象鎖的線程