Synchronized 詳解

爲了方便記憶,將鎖作以下的分類html

1、對象鎖java

包括方法鎖(默認鎖對象爲this,當前實例對象)和同步代碼塊鎖(本身指定鎖對象)jvm

1.代碼塊形式:手動指定鎖定對象,也但是是this,也能夠是自定義的鎖ide

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 同步代碼塊形式——鎖爲this,兩個線程使用的鎖是同樣的,線程1必需要等到線程0釋放了該鎖後,才能執行
        synchronized (this) {
            System.out.println("我是線程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "結束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}

輸出結果:

  我是線程Thread-0
  Thread-0結束
  我是線程Thread-1
  Thread-1結束函數

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();
    // 建立2把鎖
    Object block1 = new Object();
    Object block2 = new Object();

    @Override
    public void run() {
        // 這個代碼塊使用的是第一把鎖,當他釋放後,後面的代碼塊因爲使用的是第二把鎖,所以能夠立刻執行
        synchronized (block1) {
            System.out.println("blocl1鎖,我是線程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("blocl1鎖,"+Thread.currentThread().getName() + "結束");
        }

        synchronized (block2) {
            System.out.println("blocl2鎖,我是線程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("blocl2鎖,"+Thread.currentThread().getName() + "結束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }

輸出結果:

  blocl1鎖,我是線程Thread-0
  blocl1鎖,Thread-0結束
  blocl2鎖,我是線程Thread-0  // 能夠看到當第一個線程在執行完第一段同步代碼塊以後,第二個同步代碼塊能夠立刻獲得執行,由於他們使用的鎖不是同一把
  blocl1鎖,我是線程Thread-1
  blocl2鎖,Thread-0結束
  blocl1鎖,Thread-1結束
  blocl2鎖,我是線程Thread-1
  blocl2鎖,Thread-1結束性能

2.方法鎖形式:synchronized修飾普通方法,鎖對象默認爲thisthis

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    public synchronized void method() {
        System.out.println("我是線程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "結束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence);
        Thread t2 = new Thread(instence);
        t1.start();
        t2.start();
    }
}

輸出結果:

  我是線程Thread-0
  Thread-0結束
  我是線程Thread-1
  Thread-1結束spa

 

2、類鎖線程

指synchronize修飾靜態的方法或指定鎖對象爲Class對象3d

1.synchronize修飾靜態方法

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在普通方法上,默認的鎖就是this,當前實例
    public synchronized void method() {
        System.out.println("我是線程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "結束");
    }

    public static void main(String[] args) {
        // t1和t2對應的this是兩個不一樣的實例,因此代碼不會串行
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

輸出結果:

  我是線程Thread-0
  我是線程Thread-1
  Thread-1結束
  Thread-0結束

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        method();
    }

    // synchronized用在靜態方法上,默認的鎖就是當前所在的Class類,因此不管是哪一個線程訪問它,須要的鎖都只有一把
    public static synchronized void method() {
        System.out.println("我是線程" + Thread.currentThread().getName());
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "結束");
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

輸出結果:

  我是線程Thread-0
  Thread-0結束
  我是線程Thread-1
  Thread-1結束

2.synchronized指定鎖對象爲Class對象

public class SynchronizedObjectLock implements Runnable {
    static SynchronizedObjectLock instence1 = new SynchronizedObjectLock();
    static SynchronizedObjectLock instence2 = new SynchronizedObjectLock();

    @Override
    public void run() {
        // 全部線程須要的鎖都是同一把
        synchronized(SynchronizedObjectLock.class){
            System.out.println("我是線程" + Thread.currentThread().getName());
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "結束");
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(instence1);
        Thread t2 = new Thread(instence2);
        t1.start();
        t2.start();
    }
}

輸出結果:
我是線程Thread-0
Thread-0結束
我是線程Thread-1
Thread-1結束

 

3、思考

1.兩個線程同時訪問1個對象的同步方法

2.兩個線程同時訪問2個對象的同步方法

3.兩個線程訪問的是synchronized靜態方法

4.兩個線程同時訪問同步(被synchronized修飾)和非同步(未被snychronized修飾)方法

5.兩個線程同時訪問1個對象的不一樣的普通同步方法

6.兩個線程同時訪問一個靜態的synchronized方法和非靜態的synchronized方法

7.方法拋出異常後,會釋放鎖嗎?

核心思想:

1.一把鎖只能同時被一個線程獲取,沒有難道鎖的線程只能等待(對應上面的1,5)

2.每一個實例都對應有本身的一把鎖(this),不一樣實例之間互不影響;例外:鎖對象是*.class以及synchronized修飾的是static方法的時候,全部對象公用同一把鎖(對應上面的2,3,4,6)

3.synchronized修飾的方法,不管方法正常執行完畢仍是拋出異常,都會釋放鎖(對應上面的7)

 

4、synchronized的性質

1.可重入性

概念:指同一個線程外層函數獲取到鎖以後,內層函數能夠直接使用該鎖

好處:避免死鎖,提高封裝性(若是不可重入,假設method1拿到鎖以後,在method1中又調用了method2,若是method2沒辦法使用method1拿到的鎖,那method2將一直等待,可是method1因爲未執行完畢,又沒法釋放鎖,就致使了死鎖,可重入正好避免這這種狀況)

粒度:線程而非調用(用3中狀況來講明與pthread的區別)

1)狀況1:證實同一個方法是可重入的(遞歸)

public class SynchronizedDemo2 {
    int a = 0;

    public static void main(String[] args) {
        new SynchronizedDemo2().method1();
    }

    public synchronized void method1() {
        System.out.println("a=" + a);
        if (a == 0) {
            a++;
            method1();
        }
    }
}

輸出結果:

  a=0
  a=1

 

2)狀況2:證實可重入不要求是同一個方法

public class SynchronizedDemo2 {
    public static void main(String[] args) {
        new SynchronizedDemo2().method1();
    }

    public synchronized void method1() {
        System.out.println("method1");
        method2();
    }

    public synchronized void method2() {
        System.out.println("method2");
    }
}

輸出結果:

  method1
  method2

 

3)狀況3:證實可重入不要求是同一個類中

public class SynchronizedDemo2 {
    public synchronized void method1() {
        System.out.println("父類method1");
    }
}

class SubClass extends SynchronizedDemo2 {
    public synchronized void method1() {
        System.out.println("子類method1");
        super.method1();
    }

    public static void main(String[] args) {
        new SubClass().method1();
    }
}

輸出結果:

  子類method1
  父類method1

 

2.不可中斷性

概念:若是這個鎖被B線程獲取,若是A線程想要獲取這把鎖,只能選擇等待或者阻塞,直到B線程釋放這把鎖,若是B線程一直不釋放這把鎖,那麼A線程將一直等待。

相比之下,將來的Lock類,能夠擁有中斷的能力(若是一個線程等待鎖的時間太長了,有權利中斷當前已經獲取鎖的線程的執行,也能夠退出等待)

 

5、深刻原理

1.加鎖和釋放鎖的原理:現象、時機(內置鎖this)、深刻JVM看字節碼(反編譯看monitor指令)

Lock lock = new ReentrantLock();

public synchronized void method1() {
  System.out.println("synchronized method1");
}

public void method2() {
  lock.lock();
  try {
    System.out.println("lock method2");
  } finally {
    lock.unlock();
  }
}
method1與method2等價,
synchronized至關於先獲取鎖,執行結束/拋出異常後,釋放鎖。

深刻JVM看字節碼,建立以下的代碼:

public class SynchronizedDemo2 {
    Object object = new Object();

    public void method1() {
        synchronized (object) {

        }
    }
}
使用javac命令進行編譯生成.class文件

>javac SynchronizedDemo2.java

使用javap命令反編譯查看.class文件的信息

>javap -verbose SynchronizedDemo2.class
獲得以下的信息:

關注紅色方框裏的monitorenter和monitorexit便可。

Monitorenter和Monitorexit指令,會讓對象在執行,使其鎖計數器加1或者減1。每個對象在同一時間只與一個monitor(鎖)相關聯,而一個monitor在同一時間只能被一個線程得到,一個對象在嘗試得到與這個對象相關聯的Monitor鎖的全部權的時候,monitorenter指令會發生以下3中狀況之一:

1)monitor計數器爲0,意味着目前尚未被得到,那這個線程就會馬上得到而後把鎖計數器+1,一旦+1,別的線程再想獲取,就須要等待

2)若是這個monitor已經拿到了這個鎖的全部權,又重入了這把鎖,那鎖計數器就會累加,變成2,而且隨着重入的次數,會一直累加

3)這把鎖已經被別的線程獲取了,等待鎖釋放

monitorexit指令:釋放對於monitor的全部權,釋放過程很簡單,就是講monitor的計數器減1,若是減完之後,計數器不是0,則表明剛纔是重入進來的,當前線程還繼續持有這把鎖的全部權,若是計數器變成0,則表明當前線程再也不擁有該monitor的全部權,即釋放鎖。

 

2.可重入原理:加鎖次數計數器

jvm會負責跟蹤對象被加鎖的次數

線程第一次得到所,計數器+1,當鎖重入的時候,計數器會遞增

當任務離開的時候(一個同步代碼塊的代碼執行結束),計數器會減1,當減爲0的時候,鎖被徹底釋放。

 

3.保證可見性的原理:內存模型

 訪問連接 https://www.cnblogs.com/xyabk/p/10894384.html

 

6、synchronized的缺陷

效率低:鎖的釋放狀況少,只有代碼執行完畢或者異常結束纔會釋放鎖;試圖獲取鎖的時候不能設定超時,不能中斷一個正在使用鎖的線程,相對而言,Lock能夠中斷和設置超時

不夠靈活:加鎖和釋放的時機單一,每一個鎖僅有一個單一的條件(某個對象),相對而言,讀寫鎖更加靈活

沒法知道是否成功得到鎖,相對而言,Lock能夠拿到狀態,若是成功獲取鎖,....,若是獲取失敗,.....

 

7、Lock對synchronized的彌補

Lock類這裏不作過多解釋,主要看上面紅色方框裏面的4個方法

lock():加鎖

unlock():解鎖

tryLock():嘗試獲取鎖,返回一個boolean值

tryLock(long,TimeUtil):嘗試獲取鎖,能夠設置超時

 

8、注意

1.鎖對象不能爲空,由於鎖的信息都保存在對象頭裏

2.做用域不宜過大,影響程序執行的速度,控制範圍過大,編寫代碼也容易出錯

3.避免死鎖

4.在能選擇的狀況下,既不要用Lock也不要用synchronized關鍵字,用java.util.concurrent包中的各類各樣的類,若是不用該包下的類,在知足業務的狀況下,可使用synchronized關鍵,由於代碼量少,避免出錯

 

9、思考

1.多個線程等待同一個snchronized鎖的時候,JVM如何選擇下一個獲取鎖的線程?

2.Synchronized使得同時只有一個線程能夠執行,性能比較差,有什麼提高的方法?

3.我想更加靈活地控制鎖的釋放和獲取(如今釋放鎖和獲取鎖的時機都被規定死了),怎麼辦?

4.什麼是鎖的升級和降級?什麼事JVM裏的偏斜鎖、輕量級鎖、重量級鎖?

相關文章
相關標籤/搜索