知識點
應該了解的概念
1. 線程與進程
進程是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間逾条,一個進程中可以啟動多個線程燥撞。比如在 Windows
系統(tǒng)中,一個運行的 exe
就是一個進程渠啊。
線程是指進程中的一個執(zhí)行流程输吏,一個進程中可以運行多個線程。比如java.exe
進程中可以運行很多線程替蛉。線程總是屬于某個進程贯溅,進程中的多個線程共享進程的內存拄氯。
1.1 區(qū)別
并發(fā)性:進程之間可以并發(fā)執(zhí)行,同一個進程的多個線程之間也可并發(fā)執(zhí)行它浅。
調度:線程作為調度和分配的基本單位译柏,進程作為擁有資源的基本單位 。
根本區(qū)別:進程是操作系統(tǒng)資源分配的基本單位姐霍,而線程是任務調度和執(zhí)行的基本單位
開銷方面:每個進程都有獨立的代碼和數據空間(程序上下文)鄙麦,進程之間切換開銷大;線程可以看做輕量級的進程镊折,同一類線程共享代碼和數據空間胯府,每個線程都有自己獨立的運行棧和程序計數器(PC),線程之間切換的開銷小
包含關系:線程是進程的一部分恨胚,所以線程也被稱為輕權進程或者輕量級進程
2. 臨界區(qū)
臨界區(qū)用來表示一種公共資源或者說是共享數據骂因,可以被多個線程使用。但是每個線程使用時赃泡,一旦臨界區(qū)資源被一個線程占有寒波,那么其他線程必須等待。多個進程必須互斥的對它進行訪問升熊。
3. 同步VS異步
同步方法:調用者必須等待被調用的方法結束后俄烁,調用者后面的代碼才能執(zhí)行
異步調用:調用者不用管被調用方法是否完成,都會繼續(xù)執(zhí)行后面的代碼级野,當被調用的方法完成后會通知調用者猴娩。
4. 并發(fā)與并行
并發(fā):多個任務交替進行,(類似單個 CPU 勺阐,通過 CPU 調度算法等卷中,處理多個任務的能力,叫并發(fā))
并行:真正意義上的“同時進行”渊抽。(類似多個 CPU 蟆豫,同時并且處理相同多個任務的能力,叫做并行)
5. 阻塞和非阻塞
阻塞和非阻塞通常用來形容多線程間的相互影響懒闷,比如一個線程占有了臨界區(qū)資源十减,那么其他線程需要這個資源就必須進行等待該資源的釋放,會導致等待的線程掛起愤估,這種情況就是阻塞帮辟,而非阻塞就恰好相反,它強調沒有一個線程可以阻塞其他線程玩焰,所有的線程都會嘗試地往前運行由驹。
線程與多線程
1. 什么是線程
線程是操作系統(tǒng)能夠進行運算調度的最小單位,它被包含在進程之中昔园,是進程中的實際運作單位蔓榄。
2. 為什么需要多線程以及出現的問題
CPU并炮、內存、I/O 設備的速度是有極大差異的甥郑,為了合理利用 CPU 的高性能逃魄,平衡這三者的速度差異,計算機體系結構澜搅、操作系統(tǒng)伍俘、編譯程序都做出了貢獻
- CPU 增加了緩存,以均衡與內存的速度差異勉躺;// 導致
可見性
問題 - 操作系統(tǒng)增加了進程养篓、線程,以分時復用 CPU赂蕴,進而均衡 CPU 與 I/O 設備的速度差異;// 導致
原子性
問題 - 編譯程序優(yōu)化指令執(zhí)行次序舶胀,使得緩存能夠得到更加合理地利用概说。// 導致
有序性
問題
3. 線程的特征
- main()方法是個天然的多線程程序 所有的Java 程序,不論并發(fā)與否嚣伐,都有一個名為主線程的Thread 對象糖赔。執(zhí)行該程序時, Java虛擬機( JVM )將創(chuàng)建一個新Thread 并在該線程中執(zhí)行main()方法轩端。這是非并發(fā)應用程序中唯一的線程放典,也是并發(fā)應用程序中的第一個線程。
- Java中的線程共享應用程序中的所有資源基茵,包括內存和打開的文件奋构,快速而簡單地共享信息。但是必須使用同步避免數據競爭
- Java中的所有線程都有一個優(yōu)先級拱层,這個整數值介于Thread.MIN_PRIORITY(1)和Thread.MAX_PRIORITY(10)之間弥臼,默認優(yōu)先級是Thread.NORM_PRIORITY(5)。線程的執(zhí)行順序并沒有保證根灯,通常径缅,較高優(yōu)先級的線程將在較低優(yōu)先級的線程之前執(zhí)行。
4. 線程狀態(tài)
- 創(chuàng)建(NEW):新創(chuàng)建了一個線程對象烙肺,但還沒有調用 start() 方法纳猪。
- 運行(RUNNABLE):Java 線程中將就緒(ready)和運行中(running)兩種狀態(tài)籠統(tǒng)的稱為“運行”。
線程對象創(chuàng)建后桃笙,其他線程(比如 main 線程)調用了該對象的 start() 方法氏堤。該狀態(tài)的線程位于可運行線程池中,等待被線程調度選中搏明,獲取 CPU 的使用權丽猬,此時處于就緒狀態(tài)(ready)宿饱。就緒狀態(tài)的線程在獲得 CPU 時間片后變?yōu)檫\行中狀態(tài)(running)。
- 阻塞(BLOCKED):表示線程阻塞于鎖脚祟。線程的執(zhí)行過程中由于一些原因進入阻塞狀態(tài)比如:調用 sleep 方法谬以、嘗試去得到一個鎖等等
- 超時等待(TIMED_WAITING):該狀態(tài)不同于WAITING,它可以在指定的時間后自行返回由桌。
- 等待(WAITING):進入該狀態(tài)的線程需要等待其他線程做出一些特定動作(通知或中斷)为黎。
- 終止(TERMINATED):表示該線程已經執(zhí)行完畢。
- 運行(running):CPU 開始調度線程行您,并開始執(zhí)行 run 方法
- 消亡(dead):run 方法執(zhí)行完 或者 執(zhí)行過程中遇到了一個異常
5. 使用多線程一定快嗎铭乾?
答:不一定,因為多線程會進行上下文切換娃循,上下文切換會帶來開銷炕檩。
5.1 什么是上下文切換?
單核在一個時刻只能運行一個線程捌斧,當在運行一個線程的過程中轉去運行另外一個線程笛质,這個叫做線程上下文切換(對于進程也是類似)。
線程上下文切換過程中會記錄 程序計數器捞蚂、CPU 寄存器 的 狀態(tài)等數據妇押。
5.2 如何減少上下文切換?
5.2.1 減少線程的數量
由于一個CPU每個時刻只能執(zhí)行一條線程姓迅,而傲嬌的我們又想讓程序并發(fā)執(zhí)行敲霍,操作系統(tǒng)只好不斷地進行上下文切換來使我們從感官上覺得程序是并發(fā)執(zhí)的行。因此丁存,我們只要減少線程的數量肩杈,就能減少上下文切換的次數。
5.2.2 控制同一把鎖上的線程數量
多條線程共用同一把鎖解寝,那么當一條線程獲得鎖后锋恬,其他線程就會被阻塞;當該線程釋放鎖后编丘,操作系統(tǒng)會從被阻塞的線程中選一條執(zhí)行与学,從而又會出現上下文切換
因此,減少同一把鎖上的線程數量也能減少上下文切換的次數
5.2.3 采用無鎖并發(fā)編程
- 需要并發(fā)執(zhí)行的任務是無狀態(tài)的:HASH分段
所謂無狀態(tài)是指并發(fā)執(zhí)行的任務沒有共享變量嘉抓,他們都獨立執(zhí)行索守。對于這種類型的任務可以按照ID進行HASH分段,每段用一條線程去執(zhí)行抑片。
- 需要并發(fā)執(zhí)行的任務是有狀態(tài)的:CAS算法
如果任務需要修改共享變量卵佛,那么必須要控制線程的執(zhí)行順序,否則會出現安全性問題。你可以給任務加鎖截汪,保證任務的原子性與可見性疾牲,但這會引起阻塞,從而發(fā)生上下文切換衙解;為了避免上下文切換阳柔,你可以使用CAS算法, 僅在線程內部需要更新共享變量時使用CAS算法來更新蚓峦,這種方式不會阻塞線程舌剂,并保證更新過程的安全性(具體的方式后面的文章會將)。
6. 使用多線程的缺點
6.1 上下文切換的開銷
當 CPU 從執(zhí)行一個線程切換到執(zhí)行另外一個線程的時候暑椰,它需要先存儲當前線程的本地的數據霍转,程序指針等,然后載入另一個線程的本地數據一汽,程序指針等避消,最后才開始執(zhí)行。這種切換稱為“上下文切換”召夹。CPU 會在一個上下文中執(zhí)行一個線程岩喷,然后切換到另外一個上下文中執(zhí)行另外一個線程。上下文切換并不廉價戳鹅。如果沒有必要,應該減少上下文切換的發(fā)生昏兆。
6.2 增加資源消耗
線程在運行的時候需要從計算機里面得到一些資源枫虏。 除了 CPU,線程還需要一些 內存來維持它本地的堆棧爬虱。它也需要 占用操作系統(tǒng)中一些資源來管理線程
6.3 編程更復雜
在多線程訪問共享數據的時候隶债,要考慮 **線程安全 **問題
7. 線程狀態(tài)的基本操作
線程在生命周期內還有需要基本操作,而這些操作會成為線程間一種通信方式跑筝,比如使用中斷(interrupted)方式通知實現線程間的交互等等
7.1 interrupted
中斷可以理解為線程的一個標志位死讹,它表示了一個運行中的線程是否被其他線程進行了中斷操作。中斷好比其他線程對該線程打了一個招呼曲梗。其他線程可以調用該線程的interrupt()方法對其進行中斷操作赞警,同時該線程可以調用 isInterrupted()來感知其他線程對其自身的中斷操作,從而做出響應虏两。另外愧旦,同樣可以調用Thread的靜態(tài)方法 interrupted()對當前線程進行中斷操作,該方法會清除中斷標志位定罢。需要注意的是笤虫,當拋出InterruptedException時候,會清除中斷標志位,也就是說在調用isInterrupted會返回false琼蚯。
public class InterruptDemo {
public static void main(String[] args) throws InterruptedException {
//sleepThread睡眠1000ms
final Thread sleepThread = new Thread() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
};
//busyThread一直執(zhí)行死循環(huán)
Thread busyThread = new Thread() {
@Override
public void run() {
while (true) ;
}
};
sleepThread.start();
busyThread.start();
sleepThread.interrupt();
busyThread.interrupt();
while (sleepThread.isInterrupted()) ;
System.out.println("sleepThread isInterrupted: " + sleepThread.isInterrupted());
System.out.println("busyThread isInterrupted: " + busyThread.isInterrupted());
}
}
開啟了兩個線程分別為sleepThread和BusyThread, sleepThread睡眠1s酬凳,BusyThread執(zhí)行死循環(huán)。然后分別對著兩個線程進行中斷操作遭庶,可以看出sleepThread拋出InterruptedException后清除標志位宁仔,而busyThread就不會清除標志位。
另外罚拟,同樣可以通過中斷的方式實現線程間的簡單交互台诗, while (sleepThread.isInterrupted()) 表示在Main中會持續(xù)監(jiān)測sleepThread,一旦sleepThread的中斷標志位清零赐俗,即sleepThread.isInterrupted()返回為false時才會繼續(xù)Main線程才會繼續(xù)往下執(zhí)行拉队。因此,中斷操作可以看做線程間一種簡便的交互方式阻逮。一般在結束線程時通過中斷標志位或者標志位的方式可以有機會去清理資源粱快,相對于武斷而直接的結束線程,這種方式要優(yōu)雅和安全叔扼。
7.2 join
join方法可以看做是線程間協(xié)作的一種方式事哭,很多時候,一個線程的輸入可能非常依賴于另一個線程的輸出瓜富,這就像兩個好基友鳍咱,一個基友先走在前面突然看見另一個基友落在后面了,這個時候他就會在原處等一等這個基友与柑,等基友趕上來后谤辜,就兩人攜手并進。其實線程間的這種協(xié)作方式也符合現實生活价捧。在軟件開發(fā)的過程中丑念,從客戶那里獲取需求后,需要經過需求分析師進行需求分解后结蟋,這個時候產品脯倚,開發(fā)才會繼續(xù)跟進。
join方法源碼關鍵是:
while (isAlive()) {
wait(0);
}
線程的合并是指將某一個線程A在調用A.join()方法合并到正在運行的另一個線程B中嵌屎,此時線程B處于阻塞狀態(tài)需要等到線程A執(zhí)行完畢后才開始線程B的繼續(xù)執(zhí)行
public class JoinDemo {
public static void main(String[] args) throws InterruptedException {
TestThread t = new TestThread();
Thread t1 = new Thread(t);
t1.start();
for (int i = 0; i < 100; i++) {
/**
* 當main線程中的i等于50的時候推正,就把t1線程合并到main線程中執(zhí)行。此時main線程是處于阻塞狀態(tài)
* 直到t1線程執(zhí)行完成后宝惰,main才開始繼續(xù)執(zhí)行
*/
if (50==i) {
t1.join();
}
System.out.println("main.."+i);
}
}
}
class TestThread implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join.."+i);
}
}
}
7.3 sleep
public static native void sleep(long millis)方法顯然是Thread的靜態(tài)方法舔稀,很顯然它是讓當前線程按照指定的時間休眠,其休眠時間的精度取決于處理器的計時器和調度器掌测。需要注意的是如果當前線程獲得了鎖内贮,sleep方法并不會失去鎖产园。
public static void main(String[] args) throws InterruptedException {
int num = 10;
while (true) {
System.out.println(num--);
Thread.sleep(1000);
if (num<=0) {
break;
}
}
}
7.4 yield
該暫停方法暫停的時候不一定就暫停了,取決于CPU夜郁,假如剛暫停CPU調度又調到了該線程那就又啟動了.....
public class YieldDemo {
public static void main(String[] args) throws InterruptedException {
TestThread1 t = new TestThread1();
Thread t1 = new Thread(t);
t1.start();
for (int i = 0; i < 100; i++) {
//當main線程中的i是20的倍數時什燕,就暫停main線程
if (i%20==0) {
Thread.yield();//yield寫在哪個線程體中,就暫停哪個線程竞端。這里是在main里屎即,就暫停main線程
System.out.println("main線程暫停");
}
System.out.println("main.."+i);
}
}
}
class TestThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("join.."+i);
}
}
}
8. 兩類線程
8.1 用戶線程(User Thread)
User和Daemon兩者幾乎沒有區(qū)別,唯一的不同之處就在于虛擬機的離開:如果 User Thread已經全部退出運行了事富,只剩下Daemon Thread存在了技俐,虛擬機也就退出了。
8.2 守護線程(Daemon Thread)
Daemon的作用是為其他線程的運行提供便利服務统台,守護線程最典型的應用就是 GC (垃圾回收器)
當所有非守護線程結束時雕擂,程序也就終止,同時會殺死所有守護線程贱勃。main() 屬于非守護線程井赌。
使用 setDaemon() 方法將一個線程設置為守護線程。
8.2.1 需要注意
- thread.setDaemon(true)必須在thread.start()之前設置贵扰,否則會拋出一個IllegalThreadStateException異常仇穗。你不能把正在運行的常規(guī)線程設置為守護線程。
- 在Daemon線程中產生的新線程也是Daemon的
- 不要認為所有的應用都可以分配給Daemon來進行服務戚绕,比如讀寫操作或者計算邏輯纹坐。
8.2.2 守護線程的代碼實踐
- 前臺線程是保證執(zhí)行完畢的,后臺線程還沒有執(zhí)行完畢就退出了舞丛。
public class Test {
public static void main(String args) {
Thread t1 = new MyCommon();
Thread t2 = new Thread(new MyDaemon());
t2.setDaemon(true); //設置為守護線程
t2.start();
t1.start();
}
}
class MyCommon extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("線程1第" + i + "次執(zhí)行耘子!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyDaemon implements Runnable {
public void run() {
for (long i = 0; i < 9999999L; i++) {
System.out.println("后臺線程第" + i + "次執(zhí)行!");
try {
Thread.sleep(7);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執(zhí)行結果:
后臺線程第0次執(zhí)行瓷马!
線程1第0次執(zhí)行拴还!
線程1第1次執(zhí)行跨晴!
后臺線程第1次執(zhí)行欧聘!
后臺線程第2次執(zhí)行!
線程1第2次執(zhí)行端盆!
線程1第3次執(zhí)行怀骤!
后臺線程第3次執(zhí)行!
線程1第4次執(zhí)行焕妙!
后臺線程第4次執(zhí)行蒋伦!
后臺線程第5次執(zhí)行!
后臺線程第6次執(zhí)行焚鹊!
后臺線程第7次執(zhí)行痕届!
- 守護線程在退出的時候并不會執(zhí)行finnaly塊中的代碼,所以將釋放資源等操作不要放在finnaly塊中執(zhí)行,這種操作是不安全的
public class DaemonDemo {
public static void main(String[] args) {
Thread daemonThread = new Thread(new Runnable() {
@Override
public void run() {
//daemodThread run方法中是一個while死循環(huán)
while (true) {
try {
System.out.println("i am alive");
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("finally block");
}
}
}
});
daemonThread.setDaemon(true);
daemonThread.start();
//確保main線程結束前能給daemonThread能夠分到時間片
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
執(zhí)行結果
i am alive
finally block
i am alive
daemodThread run方法中是一個while死循環(huán)研叫,會一直打印,但是當main線程結束后daemonThread就會退出所以不會出現死循環(huán)的情況锤窑。
main線程先睡眠800ms保證daemonThread能夠擁有一次時間片的機會,也就是說可以正常執(zhí)行一次打印“i am alive”操作和一次finally塊中"finally block"操作嚷炉。
緊接著main 線程結束后渊啰,daemonThread退出,這個時候只打印了"i am alive"并沒有打印finnal塊中的申屹。
守護線程在退出的時候并不會執(zhí)行finnaly塊中的代碼绘证,所以將釋放資源等操作不要放在finnaly塊中執(zhí)行,這種操作是不安全的
8.2.3 特點
因為是守護線程哗讥,或者說是支持性線程嚷那,就
意味著這個線程并不屬于程序中不可或缺的一部分
。所以當所有的非守護線程(即用戶線程)結束之后忌栅,程序就會結束车酣,JVM退出,同時也就會殺死所有的非守護線程索绪。所以也就意味著湖员,守護線程不適合去訪問固有資源,比如文件瑞驱,數據庫
娘摔。因為隨時可能中斷。后臺線程會隨著主程序的結束而結束唤反,但是前臺進程則不會凳寺,或者說只要有一個前臺線程未退出,進程就不會終止彤侍。
默認情況下肠缨,程序員創(chuàng)建的線程是用戶線程;用setDaemon(true)可以設置線程為后臺線程盏阶;而用isDaemon( )則可以判斷一個線程是前臺線程還是后臺線程晒奕;
jvm的垃圾回收器其實就是一個后臺線程;
setDaemon函數必須在start函數之前設定名斟,否則會拋出IllegalThreadStateException異常
;
8.2.4 使用場景
qq,飛訊等等聊天軟件,主程序是非守護線程,而所有的聊天窗口是守護線程 ,當在聊天的過程中,直接關閉聊天應用程序時,聊天窗口也會隨之關閉,但是不是 立即關閉,而是需要緩沖,等待接收到關閉命令后才會執(zhí)行窗口關閉操作.
jvm中,gc線程是守護線程,作用就是當所有用戶自定義線以及主線程執(zhí)行完畢后, gc線程才停止
Web服務器中的Servlet脑慧,在容器啟動時,后臺都會初始化一個服務線程砰盐,即調度線程闷袒,負責處理http請求,然后每個請求過來岩梳,調度線程就會從線程池中取出一個工作者線程來處理該請求囊骤,從而實現并發(fā)控制的目的晃择。也就是說,一個實際應用在Java的線程池中的調度線程
8.2.5 總結
守護線程就是用來告訴JVM也物,我的這個線程是一個低級別的線程藕各,不需要等待它運行完才退出,讓JVM喜歡什么時候退出就退出焦除,不用管這個線程激况。
在日常的業(yè)務相關的CRUD開發(fā)中,其實并不會關注到守護線程這個概念膘魄,也幾乎不會用上乌逐。
但是如果要往更高的地方走的話,這些深層次的概念還是要了解一下的创葡,比如一些框架的底層實現浙踢。
7. 線程安全
7.1 什么是線程安全?
所有的隱患都是在多個線程訪問的情況下產生的灿渴,也就是我們要確保在多條線程訪問的時候洛波,我們的程序還能按照我們預期的行為去執(zhí)行.
下面代碼需要在多線程環(huán)境下的測試
Integer count = 0;
public void getCount() {
count ++;
System.out.println(count);
}
//開三個線程代碼,值會重復 所以在多線程的情況下
結論:
當多個線程訪問某個方法時骚露,不管你通過怎樣的調用方式蹬挤、或者說這些線程如何交替地執(zhí)行,我們在主程序中不需要去做任何的同步棘幸,這個類的結果行為都是我們設想的正確行為焰扳,那么我們就可以說這個類是線程安全的。
7.2 什么時候會出現線程安全?
線程安全問題都是由全局變量及靜態(tài)變量引起的误续。若每個線程中對全局變量吨悍、靜態(tài)變量只有讀操作,而無寫操作蹋嵌,一般來說育瓜,這個全局變量是線程安全的;
若有多個線程同時執(zhí)行寫操作栽烂,一般都需要考慮線程同步躏仇,否則的話就可能出現線程安全的問題。
7.3 有哪些方法可以保證線程安全嗎?
7.3.1 synchronized
7.3.1.1 同步代碼塊
synchronized 關鍵字可以用于方法中的某個區(qū)塊中愕鼓,表示只對這個區(qū)塊的資源實行互斥訪問低缩。
synchronized關鍵字就是用來控制線程同步的望门,保證我們的線程在多線程環(huán)境下,不被多個線程同時執(zhí)行呆万,確保我們數據的完整性蚓挤,使用方法一般是加在方法上磺送。
代碼塊中的鎖對象可以是任意對象驻子;
但是必須保證多個線程使用的鎖對象是同一個;
鎖對象的作用就是將同步代碼塊鎖住估灿,只允許一個線程在同步代碼塊中執(zhí)行
// 創(chuàng)建一個鎖對象
Object object = new Object();
int count = 0; // 記錄方法的命中次數
// 創(chuàng)建同步代碼塊
synchronized (object) {
if (ticket > 0) {
count++ ;
int i = 1;
j = j + i;
}
}
注意:
- synchronized鎖的是括號里的對象崇呵,而不是代碼,其次馅袁,對于非靜態(tài)的synchronized方法域慷,鎖的是對象本身也就是this
- synchronized鎖住一個對象之后,別的線程如果想要獲取鎖對象汗销,那么就必須等這個線程執(zhí)行完釋放鎖對象之后才可以犹褒,否則一直處于等待狀態(tài)。
- 加synchronized關鍵字弛针,可以讓我們的線程變得安全叠骑,但是我們在用的時候,也要注意縮小synchronized的使用范圍削茁,如果隨意使用時很影響程序的性能宙枷,別的對象想拿到鎖,結果你沒用鎖還一直把鎖占用茧跋,這樣就有點浪費資源慰丛。
7.3.1.2 同步方法
使用 synchronized 修飾的方法,就叫做同步方法,保證A線程執(zhí)行該方法的時候,其他線程只能在方法外等著
int count = 0; // 記錄方法的命中次數
public synchronized void threadMethod(int j) {
count++ ;
int i = 1;
j = j + i;
}
7.3.2 同步鎖(Lock)
java.util.concurrent.locks.Lock 機制提供了比 synchronized 代碼塊和 synchronized 方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能 Lock 都有,除此之外更強大,更體現面向對象瘾杭。
Lock 鎖使用步驟:
- 在成員位置創(chuàng)建一個 ReentrantLock 對象璧帝;
- 在可能出現安全問題的代碼前調用 Lock 接口中的方法lock();
- 在可能出現安全問題的代碼前調用 Lock 接口中的方法unLock();
7.3.2.1 lock.lock()
跟synchronized不同的是,Lock獲取的所對象需要我們親自去進行釋放
富寿,為了防止我們代碼出現異常睬隶,所以我們的釋放鎖操作放在finally中
,因為finally中的代碼無論如何都是會執(zhí)行的页徐。
private Lock lock = new ReentrantLock(); // ReentrantLock是Lock的子類
private void method(Thread thread){
lock.lock(); // 獲取鎖對象
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
// Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對象
}
}
7.3.2.2 lock.tryLock()
tryLock()這個方法跟Lock()是有區(qū)別的苏潜,Lock在獲取鎖的時候,如果拿不到鎖变勇,就一直處于等待狀態(tài)恤左,直到拿到鎖,但是tryLock()卻不是這樣的搀绣,tryLock是有一個Boolean的返回值的飞袋,如果沒有拿到鎖,直接返回false链患,停止等待巧鸭,它不會像Lock()那樣去一直等待獲取鎖。
private void method(Thread thread){
// lock.lock(); // 獲取鎖對象
if (lock.tryLock()) {
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
// Thread.sleep(2000);
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對象
}
}
}
7.3.2.3 lock.tryLock(5,TimeUnit.SECONDS)
一種方式來控制一下麻捻,讓后面等待的線程纲仍,可以等待5秒呀袱,如果5秒之后,還獲取不到鎖郑叠,那么就停止等夜赵,其實tryLock()是可以進行設置等待的相應時間的。
private void method(Thread thread) throws InterruptedException {
// lock.lock(); // 獲取鎖對象
// 如果5秒內獲取不到鎖對象乡革,那就不再等待
if (lock.tryLock(5,TimeUnit.SECONDS)) {
try {
System.out.println("線程名:"+thread.getName() + "獲得了鎖");
}catch(Exception e){
e.printStackTrace();
} finally {
System.out.println("線程名:"+thread.getName() + "釋放了鎖");
lock.unlock(); // 釋放鎖對象
}
}
}