個人筆記--多線程(鎖)
死鎖:就是同步方法中有同步代碼塊,或反之。
例子:
public class DeadLock { public static String obj1 = "obj1"; public static String obj2 = "obj2"; public static void main(String[] args){ Thread a = new Thread(new Lock1()); Thread b = new Thread(new Lock2()); a.start(); b.start(); } }class Lock1 implements Runnable{ @Override public void run(){ try{ System.out.println("Lock1 running"); while(true){ synchronized(DeadLock.obj1){ System.out.println("Lock1 lock obj1"); Thread.sleep(3000);//獲取obj1後先等一會兒,讓Lock2有足夠的時間鎖住obj2synchronized(DeadLock.obj2){ System.out.println("Lock1 lock obj2"); } } } }catch(Exception e){ e.printStackTrace(); } } } class Lock2 implements Runnable{ @Override publicvoid run(){ try{ System.out.println("Lock2 running"); while(true){ synchronized(DeadLock.obj2){ System.out.println("Lock2 lock obj2"); Thread.sleep(3000); synchronized(DeadLock.obj1){ System.out.println("Lock2 lock obj1"); } } } }catch(Exception e){ e.printStackTrace(); } } }
同步鎖synchronized和互斥鎖ReentrantLock的區別:
同步是隱示的鎖操作,而Lock對象是顯示的鎖操作。
例子:
ReentrantLock lck = new ReentrantLock();//創建一個互斥鎖對象lck.lock(): 獲取鎖對象 //被同步的內容 lck.unlock();//釋放鎖對象 //監視器 Condition c = lck.newCondition();//獲取監視器對象 c.signal();//喚醒等待中的線程 signalAll()喚醒所有 c.await();//讓線程等待.
synchronized和Lock的區別:
Lock中可以自己控制鎖是否公平,而且,默認的是非公平鎖。
1.兩種鎖的底層實現方式:
synchronized:我們知道java是用字節碼指令來控制程序(這裏不包括熱點代碼編譯成機器碼)。在字節指令中,存在有synchronized所包含的代碼塊,那麽會形成2段流程的執行。
其實synchronized映射成字節碼指令就是增加來兩個指令:monitorenter和monitorexit。
當一條線程進行執行的遇到monitorenter指令的時候,它會去嘗試獲得鎖,如果獲得鎖那麽鎖計數+1(為什麽會加一呢,因為它是一個可重入鎖,所以需要用這個鎖計數判斷鎖的情況),如果沒有獲得鎖,那麽阻塞。當它遇到monitorexit的時候,鎖計數器-1,當計數器為0,那麽就釋放鎖。
問:上圖有2個monitorexit呀?
答:synchronized鎖釋放有兩種機制,一種就是執行完釋放;另外一種就是發送異常,虛擬機釋放。圖中第二個monitorexit就是發生異常時執行的流程
synchronized是悲觀鎖,Lock是CAS樂觀鎖。
Lock底層主要靠volatile和CAS操作實現的。
建議:盡可能去使用synchronized而不要去使用LOCK。
悲觀鎖和樂觀鎖的使用場景:
讀取頻繁使用樂觀鎖(版本號+時間戳),寫入頻繁使用悲觀鎖(上鎖)。
問:解釋以下名詞:重排序,自旋鎖,偏向鎖,輕量級鎖,可重入鎖,公平鎖,非公平鎖
答:
重入鎖(ReentrantLock)是一種遞歸無阻塞的同步機制。重入鎖,也叫做遞歸鎖,指的是同一線程 外層函數獲得鎖之後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。在JAVA環境下 ReentrantLock 和synchronized都是 可重入鎖。
自旋鎖,由於自旋鎖使用者一般保持鎖時間非常短,因此選擇自旋而不是睡眠是非常必要的,自旋鎖的效率遠高於互斥鎖。如何旋轉呢?何為自旋鎖,就是如果發現鎖定了,不是睡眠等待,而是采用讓當前線程不停地的在循環體內執行實現的,當循環的條件被其他線程改變時 才能進入臨界區。
偏向鎖(Biased Locking)是Java6引入的一項多線程優化,它會偏向於第一個訪問鎖的線程,如果在運行過程中,同步鎖只有一個線程訪問,不存在多線程爭用的情況,則線程是不需要觸發同步的,這種情況下,就會給線程加一個偏向鎖。 如果在運行過程中,遇到了其他線程搶占鎖,則持有偏向鎖的線程會被掛起,JVM會消除它身上的偏向鎖,將鎖恢復到標準的輕量級鎖。
輕量級鎖是由偏向所升級來的,偏向鎖運行在一個線程進入同步塊的情況下,當第二個線程加入鎖爭用的時候,偏向鎖就會升級為輕量級鎖。
公平鎖,就是很公平,在並發環境中,每個線程在獲取鎖時會先查看此鎖維護的等待隊列,如果為空,或者當前線程線程是等待隊列的第一個,就占有鎖,否則就會加入到等待隊列中,以後會按照FIFO的規則從隊列中取到自己
非公平鎖比較粗魯,上來就直接嘗試占有鎖。在公平的鎖上,線程按照他們發出請求的順序獲取鎖,但在非公平鎖上,則允許‘插隊’:當一個線程請求非公平鎖時,如果在發出請求的同時該鎖變成可用狀態,那麽這個線程會跳過隊列中所有的等待線程而獲得鎖。 非公平的ReentrantLock 並不提倡 插隊行為,但是無法防止某個線程在合適的時候進行插隊。
問:什麽時候應該使用可重入鎖?
答:
場景1:如果已加鎖,則不再重復加鎖。a、忽略重復加鎖。b、用在界面交互時點擊執行較長時間請求操作時,防止多次點擊導致後臺重復執行(忽略重復觸發)。以上兩種情況多用於進行非重要任務防止重復執行,(如:清除無用臨時文件,檢查某些資源的可用性,數據備份操作等)
場景2:如果發現該操作已經在執行,則嘗試等待一段時間,等待超時則不執行(嘗試等待執行)這種其實屬於場景2的改進,等待獲得鎖的操作有一個時間的限制,如果超時則放棄執行。用來防止由於資源處理不當長時間占用導致死鎖情況(大家都在等待資源,導致線程隊列溢出)。
場景3:如果發現該操作已經加鎖,則等待一個一個加鎖(同步執行,類似synchronized)這種比較常見大家也都在用,主要是防止資源使用沖突,保證同一時間內只有一個操作可以使用該資源。但與synchronized的明顯區別是性能優勢(伴隨jvm的優化這個差距在減小)。同時Lock有更靈活的鎖定方式,公平鎖與不公平鎖,而synchronized永遠是公平的。這種情況主要用於對資源的爭搶(如:文件操作,同步消息發送,有狀態的操作等)
場景4:可中斷鎖。synchronized與Lock在默認情況下是不會響應中斷(interrupt)操作,會繼續執行完。lockInterruptibly()提供了可中斷鎖來解決此問題。(場景3的另一種改進,沒有超時,只能等待中斷或執行完畢)這種情況主要用於取消某些操作對資源的占用。如:(取消正在同步運行的操作,來防止不正常操作長時間占用造成的阻塞)
問:簡述鎖的等級方法鎖、對象鎖、類鎖
答:
方法鎖(synchronized修飾方法時)通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized方法。synchronized方法控制對類成員變量的訪問: 每個類實例對應一把鎖,每個synchronized方法都必須獲得調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類實例,其所有聲明為 synchronized的成員函數中至多只有一個處於可執行狀態,從而有效避免了類成員變量的訪問沖突。 對象鎖(synchronized修飾方法或代碼塊)當一個對象中有synchronized method或synchronized block的時候調用此對象的同步方法或進入其同步區域時,就必須先獲得對象鎖。如果此對象的對象鎖已被其他調用者占用,則需要等待此鎖被釋放。(方法鎖也是對象鎖)。java的所有對象都含有1個互斥鎖,這個鎖由JVM自動獲取和釋放。線程進入synchronized方法的時候獲取該對象的鎖,當然如果已經有線程獲取了這個對象的鎖,那麽當前線程會等待;synchronized方法正常返回或者拋異常而終止,JVM會自動釋放對象鎖。這裏也體現了用synchronized來加鎖的1個好處,方法拋異常的時候,鎖仍然可以由JVM來自動釋放。 類鎖(synchronized修飾靜態的方法或代碼塊),由於一個class不論被實例化多少次,其中的靜態方法和靜態變量在內存中都只有一份。所以,一旦一個靜態的方法被申明為synchronized。此類所有的實例化對象在調用此方法,共用同一把鎖,我們稱之為類鎖。對象鎖是用來控制實例方法之間的同步,類鎖是用來控制靜態方法(或靜態變量互斥體)之間的同步。類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。java類可能會有很多個對象,但是只有1個Class對象,也就是說類的不同實例之間共享該類的Class對象。Class對象其實也僅僅是1個java對象,只不過有點特殊而已。由於每個java對象都有1個互斥鎖,而類的靜態方法是需要Class對象。所以所謂的類鎖,不過是Class對象的鎖而已。獲取類的Class對象有好幾種,最簡單的就是[類名.class]的方式。
問:怎麽檢測一個線程是否擁有鎖?
答:
java.lang.Thread中有一個方法叫holdsLock(),它返回true如果當且僅當當前線程擁有某個具體對象的鎖。
問:如何實現分布式鎖?
答:
基於數據庫實現分布式鎖
基於緩存(redis,memcached,tair)實現分布式鎖
基於Zookeeper實現分布式鎖
參考:
分布式鎖的實現方式
例子1:
/* * 編寫一個程序,開啟 3 個線程,這三個線程的 ID 分別為 A、B、C,每個線程將自己的 ID 在屏幕上打印 10 遍,要求輸出的結果必須按順序顯示。 * 如:ABCABCABC…… 依次遞歸 */ public class TestABCAlternate { public static void main(String[] args) { AlternateDemo ad = new AlternateDemo(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { ad.loopA(i); } } }, "A").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { ad.loopB(i); } } }, "B").start(); new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 20; i++) { ad.loopC(i); System.out.println("-----------------------------------"); } } }, "C").start(); } } class AlternateDemo{ private int number = 1; //當前正在執行線程的標記 private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); /** * @param totalLoop : 循環第幾輪 */ public void loopA(int totalLoop){ lock.lock(); try { //1. 判斷 if(number != 1){ condition1.await(); } //2. 打印 for (int i = 1; i <= 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3. 喚醒 number = 2; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopB(int totalLoop){ lock.lock(); try { //1. 判斷 if(number != 2){ condition2.await(); } //2. 打印 for (int i = 1; i <= 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3. 喚醒 number = 3; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void loopC(int totalLoop){ lock.lock(); try { //1. 判斷 if(number != 3){ condition3.await(); } //2. 打印 for (int i = 1; i <= 1; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i + "\t" + totalLoop); } //3. 喚醒 number = 1; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
例子2:
/* * ReadWriteLock : 讀寫鎖 * * 寫寫/讀寫 需要“互斥” * 讀讀 不需要互斥 * */ public class TestReadWriteLock { public static void main(String[] args) { ReadWriteLockDemo rw = new ReadWriteLockDemo(); new Thread(new Runnable() { @Override public void run() { rw.set((int)(Math.random() * 101)); } }, "Write:").start(); for (int i = 0; i < 100; i++) { new Thread(new Runnable() { @Override public void run() { rw.get(); } }).start(); } } } class ReadWriteLockDemo{ private int number = 0; private ReadWriteLock lock = new ReentrantReadWriteLock(); //讀 public void get(){ lock.readLock().lock(); //上鎖 try{ System.out.println(Thread.currentThread().getName() + " : " + number); }finally{ lock.readLock().unlock(); //釋放鎖 } } //寫 public void set(int number){ lock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()); this.number = number; }finally{ lock.writeLock().unlock(); } } }
個人筆記--多線程(鎖)