1. 程式人生 > >【JavaSE】執行緒的同步和死鎖,synchronized物件鎖和全域性鎖,一個生活的例子解釋。

【JavaSE】執行緒的同步和死鎖,synchronized物件鎖和全域性鎖,一個生活的例子解釋。

1.多執行緒為什麼要加鎖?

  • 因為在多執行緒啟動之後,所有執行緒都是無順序任意執行的,甚至幾乎同時訪問同一個資源或者程式碼塊,所以上一個執行緒對資源所做的改變,還沒來得及使用,就有可能被下一個執行緒所覆蓋。
  • 引入鎖的概念,就是為了讓競爭資源在各個執行緒使用的時候可以互不影響。

2.synchronized兩種使用方法

物件鎖

  • 在一個普通類中定義一個synchronized程式碼塊或者方法
  • 程式碼塊使用方法
//在方法中使用
synchronized(物件(一般是this)){
    	...
}
  • 物件鎖方法
//在普通類中使用
public synchronized void sale(
int ticket){ ... }
  • 物件鎖只能對同一物件進行加鎖操作。
  • 不同執行緒用同一個物件訪問物件鎖的時候,後來的執行緒會等待。
  • 如果是不同執行緒拿著不同物件訪問物件鎖,互不影響

全域性鎖

  • 全域性鎖定義的方法就是給物件鎖的程式碼塊或者方法加一個static關鍵字。
  • 這時候鎖子變成了靜態的,不單單隻限制某一個物件,可以類比靜態變數。它的作用域是這個類的所有物件。
  • 所以全域性鎖無論是否是同一個物件,只要多個執行緒執行此程式碼塊或者此方法就會互斥進行訪問

需要注意

  • 不同執行緒用同一個物件訪問物件鎖的時候,執行緒A先訪問西了synchronized的程式碼塊或者方法,只要執行緒B還沒有訪問到synchronized的程式碼塊或者呼叫了其他普通方法,執行緒B依然可以訪問,不受鎖的影響。
  • 不同執行緒用同一個物件訪問物件鎖的時候,如果執行緒A先訪問了synchronized的程式碼塊或者方法,執行緒B就不可能再去訪問別的被鎖住的程式碼塊或者方法,因為現在鎖的鑰匙沒在它手裡,只有等執行緒A執行完畢,執行緒B得到鑰匙才可以訪問。
  • 以上兩條適用於全域性鎖。
class Sync{
    public static synchronized void test(){
        System.out.println("當前執行緒為"+Thread.currentThread().getName());
        try {
            Thread.sleep
(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("當前執行緒結束"+Thread.currentThread().getName()); } public synchronized static void test1(){ //可以將鎖去掉,測測普通方法會不會互斥 System.out.println("可以執行?"); } } class MyThread implements Runnable{ private boolean flag; public MyThread(boolean flag) { this.flag = flag; } public void setFlag(boolean flag) { this.flag = flag; } public void run(){ Sync sync = new Sync(); if(flag == true) { //為了讓兩個執行緒訪問不同的方法 sync.test(); }else{ sync.test1(); } } } public class Test { public static void main(String[] args) { MyThread thread = new MyThread(true); Thread thread1 = new Thread(thread,"執行緒1"); Thread thread2 = new Thread(thread,"執行緒2"); thread1.start(); try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } thread.setFlag(false); thread2.start(); } }
  • 上面的程式碼就是以全域性鎖為例,建立兩個執行緒thread1和thread2,先讓thread1執行緒訪問被鎖的方法,然後讓thread2訪問被鎖的另一個方法。

3.synchronized同步方法底層實現

  • 當使用synchronized標記方法時,位元組碼會出現訪問標記ACC_SYNCHRONIZED。該標記表示在進入該方法時,JVM需要進行monitorenter操作。在退出方法時,無論是否正常返回,JVM均需要進行monitorexit操作
  • 當JVM執行moniterenter時,如果目標物件monitor的計數器為0,表示此時該物件沒有被其他執行緒所持有。此時JVM會將該鎖物件的持有執行緒設定為當前執行緒,並且將monitor計數器+1。

可重入鎖概念

  • 在目標鎖物件的計數器不為0的情況下,如果鎖物件的持有執行緒是當前執行緒。JVM可將計數器再次+1(可重入鎖,比如在加鎖方法test1方法裡呼叫同一類中另一個加鎖方法test2);否則需要等待,直到持有執行緒釋放該鎖。
  • 當執行monitorexit指令時,JVM需要將物件計數器-1。當計數器減為0時,代表該鎖已被釋放掉,喚醒所有正在等待的執行緒去競爭該鎖。

物件鎖機制

  • 是JDK1.6之前synchronized底層原理,又稱為JDK1.6重量級鎖,執行緒的阻塞以及喚醒均需要作業系統由使用者態切換到核心態,開銷非常之大,因此效率很低。

4.synchronized鎖一個有趣的例子

打個比方:一個object就像一個大房子,大門永遠開啟。房子裡有 很多房間(也就是方法)。
這些房間有上鎖的(synchronized方法), 和不上鎖之分(普通方法)。房門口放著一把鑰匙(key),這把鑰匙可以開啟所有上鎖的房間。
另外我把所有想呼叫該物件方法的執行緒比喻成想進入這房子某個 房間的人。所有的東西就這麼多了,下面我們看看這些東西之間如何作用的。
在此我們先來明確一下我們的前提條件。該物件至少有一個synchronized方法,否則這個key還有啥意義。當然也就不會有我們的這個主題了。
一個人想進入某間上了鎖的房間,他來到房子門口,看見鑰匙在那兒(說明暫時還沒有其他人要使用上鎖的 房間)。於是他走上去拿到了鑰匙
,並且按照自己 的計劃使用那些房間。注意一點,他每次使用完一次上鎖的房間後會馬上把鑰匙還回去。即使他要連續使用兩間上鎖的房間,
中間他也要把鑰匙還回去,再取回來。
因此,普通情況下鑰匙的使用原則是:“隨用隨借,用完即還。”
這時其他人可以不受限制的使用那些不上鎖的房間,一個人用一間可以,兩個人用一間也可以,沒限制。但是如果當某個人想要進入上鎖的房
間,他就要跑到大門口去看看了。有鑰匙當然拿了就走,沒有的話,就只能等了。