Java多線程中Lock的使用小結(jié)
Jdk1.5 以后,在 java.util.concurrent.locks 包下,有一組實(shí)現(xiàn)線程同步的接口和類,說到線程的同步,可能大家都會想到 synchronized 關(guān)鍵字,
這是 java 內(nèi)置的關(guān)鍵字,用來處理線程同步的,但這個(gè)關(guān)鍵字有很多的缺陷,使用起來也不是很方便和直觀,所以就出現(xiàn)了 Lock,下面,我們
就來對比著講解 Lock。
通常我們在使用 synchronized 關(guān)鍵字的時(shí)候會遇到下面這些問題:
(1)不可控性,無法做到隨心的加鎖和釋放鎖。
(2)效率比較低下,比如我們現(xiàn)在并發(fā)的讀兩個(gè)文件,讀與讀之間是互不影響的,但如果給這個(gè)讀的對象使用 synchronized 來實(shí)現(xiàn)同步的話,
那么只要有一個(gè)線程進(jìn)入了,那么其他的線程都要等待。
(3)無法知道線程是否獲取到了鎖。
而上面 synchronized 的這些問題,Lock 都可以很好的解決,并且 jdk1.5 以后,還提供了各種鎖,例如讀寫鎖,但有一點(diǎn)需要注意,使用 synchronized
關(guān)鍵時(shí),無須手動釋放鎖,但使用 Lock 必須手動釋放鎖。下面我們就來學(xué)習(xí)一下 Lock 鎖。
Lock 是一個(gè)上層的接口,其原型如下,總共提供了 6 個(gè)方法:
public interface Lock { // 用來獲取鎖,如果鎖已經(jīng)被其他線程獲取,則一直等待,直到獲取到鎖 void lock(); // 該方法獲取鎖時(shí),可以響應(yīng)中斷,比如現(xiàn)在有兩個(gè)線程,一個(gè)已經(jīng)獲取到了鎖,另一個(gè)線程調(diào)用這個(gè)方法正在等待鎖,但是此刻又不想讓這個(gè)線程一直在這死等,可以通過 調(diào)用線程的Thread.interrupted()方法,來中斷線程的等待過程 void lockInterruptibly() throws InterruptedException; // tryLock方法會返回bool值,該方法會嘗試著獲取鎖,如果獲取到鎖,就返回true,如果沒有獲取到鎖,就返回false,但是該方法會立刻返回,而不會一直等待 boolean tryLock(); // 這個(gè)方法和上面的tryLock差不多是一樣的,只是會嘗試指定的時(shí)間,如果在指定的時(shí)間內(nèi)拿到了鎖,則會返回true,如果在指定的時(shí)間內(nèi)沒有拿到鎖,則會返回false boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 釋放鎖 void unlock(); // 實(shí)現(xiàn)線程通信,相當(dāng)于wait和notify,后面會單獨(dú)講解 Condition newCondition(); }
那么這幾個(gè)方法該如何使用了?前面我們說到,使用 Lock 是需要手動釋放鎖的,但是如果程序中拋出了異常,那么就無法做到釋放鎖,有可能引起死鎖,
所以我們在使用 Lock 的時(shí)候,有一種固定的格式,如下:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally {// 必須使用try,最后在finally里面釋放鎖 l.unlock(); }
下面我們來看一個(gè)簡單的例子,代碼如下:
/** * 描述:Lock使用 */ public class LockDemo { // new一個(gè)鎖對象,注意此處必須聲明成類對象,保持只有一把鎖,ReentrantLock是Lock的唯一實(shí)現(xiàn)類 Lock lock = new ReentrantLock(); public void readFile(String fileMessage){ lock.lock();// 上鎖 try{ System.out.println(Thread.currentThread().getName()+"得到了鎖,正在讀取文件……"); for(int i=0; i<fileMessage.length(); i++){ System.out.print(fileMessage.charAt(i)); } System.out.println(); System.out.println("文件讀取完畢!"); }finally{ System.out.println(Thread.currentThread().getName()+"釋放了鎖!"); lock.unlock(); } } public void demo(final String fileMessage){ // 創(chuàng)建若干個(gè)線程 ExecutorService service = Executors.newCachedThreadPool(); // 提交20個(gè)任務(wù) for(int i=0; i<20; i++){ service.execute(new Runnable() { @Override public void run() { readFile(fileMessage); try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } }); } // 釋放線程池中的線程 service.shutdown(); } }
Lock與synchronized的對比
1、作用
lock 和 synchronized 都是 Java 中去用來解決線程安全問題的一個(gè)工具。
2、來源
sychronized 是 Java 中的一個(gè)關(guān)鍵字。
lock 是 JUC 包里面提供的一個(gè)接口,這個(gè)接口有很多實(shí)現(xiàn)類,其中就包括我們最常用的 ReentrantLock(可重入鎖)。
3、鎖的力度
sychronized 可以通過兩種方式去控制鎖的力度:
把 sychronized 關(guān)鍵字修飾在方法層面。
修飾在代碼塊上。
鎖對象的不同:
鎖對象為靜態(tài)對象或者是class對象,那這個(gè)鎖屬于全局鎖。
鎖對象為普通實(shí)例對象,那這個(gè)鎖的范圍取決于這個(gè)實(shí)例的生命周期。
lock鎖的力度是通過 lock()與unlock()兩個(gè)方法決定的。在兩個(gè)方法之間的代碼能保證其線程安全。lock的作用域取決于lock實(shí)例的生命周期。
4、靈活性
lock鎖比sychronized的靈活性更高。
lock可以自主的去決定什么時(shí)候加鎖與釋放鎖。只需要調(diào)用lock 的lock()和unlock()這兩個(gè)方法就可以。
sychronized 由于是一個(gè)關(guān)鍵字,所以他無法實(shí)現(xiàn)非阻塞競爭鎖的方法,一個(gè)線程獲取鎖之后,其他鎖只能等待那個(gè)線程釋放之后才能有獲取鎖的機(jī)會。
5、公平鎖與非公平鎖
公平鎖:多個(gè)線程按照申請鎖的順序去獲得鎖,線程會直接進(jìn)入隊(duì)列去排隊(duì),永遠(yuǎn)都是隊(duì)列的第一位才能得到鎖。
優(yōu)點(diǎn):所有的線程都能得到資源,不會餓死。
缺點(diǎn):吞吐量低,隊(duì)列里面除了第一個(gè)線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷大。
非公平鎖:多個(gè)線程去獲取鎖的時(shí)候,會直接去嘗試獲取,獲取不到,再去進(jìn)入等待隊(duì)列,如果能獲取到,就直接獲取到鎖。
優(yōu)點(diǎn):可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點(diǎn),CPU也不必取喚醒所有線程,會減少喚起線程的數(shù)量。
缺點(diǎn):可能導(dǎo)致隊(duì)列中間的線程一直獲取不到鎖或者長時(shí)間獲取不到鎖,最終餓死。
lock提供了公平鎖和非公平鎖兩種機(jī)制(默認(rèn)非公平鎖)。
sychronized是非公平鎖。
6、異常是否釋放鎖
synchronized鎖的釋放是被動的,當(dāng)sychronized同步代碼塊執(zhí)行結(jié)束或者出現(xiàn)異常的時(shí)候才會被釋放。
lock鎖發(fā)生異常的時(shí)候,不會主動釋放占有的鎖,必須手動unlock()來釋放,所以我們一般都是將同步代碼塊放進(jìn)try-catch里面,finally中寫入unlock()方法,避免死鎖發(fā)生。
7、判斷是否能獲取鎖
synchronized不能。
lock提供了非阻塞競爭鎖的方法trylock(),返回值是Boolean類型。它表示的是用來嘗試獲取鎖:成功獲取則返回true;獲取失敗則返回false,這個(gè)方法無論如何都會立即返回。
8、調(diào)度方式
synchronized使用的是object對象本身的wait、notify、notifyAll方法,而lock使用的是Condition進(jìn)行線程之間的調(diào)度。
9、是否能中斷
synchronized只能等待鎖的釋放,不能響應(yīng)中斷。
lock等待鎖過程中可以用interrupt()來中斷。
10、性能
如果競爭不激烈,性能差不多;競爭激烈時(shí),lock的性能會更好。
lock鎖還能使用readwritelock實(shí)現(xiàn)讀寫分離,提高多線程的讀操作效率。
11、sychronized鎖升級
synchronized 代碼塊是由一對 monitorenter/monitorexit 指令實(shí)現(xiàn)的。Monitor的實(shí)現(xiàn)完全是依靠操作系統(tǒng)內(nèi)部的互斥鎖,因?yàn)樾枰M(jìn)行用戶態(tài)到內(nèi)核態(tài)的切換,所以同步操作是一個(gè)無差別的重量級操作。
所以現(xiàn)在JVM提供了三種不同的鎖:偏向鎖、輕量級鎖、重量級鎖。
偏向鎖:
當(dāng)沒有競爭出現(xiàn)時(shí),默認(rèn)使用偏向鎖。線程會利用 CAS 操作在對象頭上設(shè)置線程 ID ,以表示對象偏向當(dāng)前線程。
目的:在很多應(yīng)用場景中,大部分對象生命周期最多會被一個(gè)線程鎖定,使用偏向鎖可以降低無競爭時(shí)的開銷。
輕量級鎖:
JVM比較當(dāng)前線程的 threadID 和 Java 對象頭中的threadID是否一致,如果不一致(比如線程2要競爭鎖對象),那么需要查看 Java 對象頭中記錄的線程1是否存活(偏向鎖不會主動釋放因此還是存儲的線程1的 threadID),如果沒有存活,那么鎖對象還是為偏向鎖(對象頭中的threadID為線程2的);如果存活,那么撤銷偏向鎖,升級為輕量級鎖。
當(dāng)有其他線程想訪問加了輕量級鎖的資源時(shí),會使用自旋鎖優(yōu)化,來進(jìn)行資源訪問。
目的:競爭鎖對象的線程不多,而且線程持有鎖的時(shí)間也不長的情景。因?yàn)樽枞€程需要CPU從用戶態(tài)轉(zhuǎn)到內(nèi)核態(tài),開銷大,如果剛剛阻塞不久這個(gè)鎖就被釋放了,就得不償失了,因此這個(gè)時(shí)候就干脆不阻塞這個(gè)線程,讓它自旋這等待鎖釋放。
重量級鎖:
自旋失敗,很大概率 再一次自選也是失敗,因此直接升級成重量級鎖,進(jìn)行線程阻塞,減少cpu消耗。
當(dāng)鎖升級為重量級鎖后,未搶到鎖的線程都會被阻塞,進(jìn)入阻塞隊(duì)列。
到此這篇關(guān)于Java多線程中Lock的使用小結(jié)的文章就介紹到這了,更多相關(guān)Java多線程Lock內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解XML,Object,Json轉(zhuǎn)換與Xstream的使用
這篇文章主要介紹了詳解XML,Object,Json轉(zhuǎn)換與Xstream的使用的相關(guān)資料,需要的朋友可以參考下2017-02-02Java創(chuàng)建數(shù)組的幾種方式總結(jié)
下面小編就為大家?guī)硪黄狫ava創(chuàng)建數(shù)組的幾種方式總結(jié)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2016-10-10Spring框架構(gòu)造注入type屬性實(shí)例詳解
這篇文章主要介紹了Spring框架構(gòu)造注入type屬性實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12