爲了方便記憶,將鎖作以下的分類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裏的偏斜鎖、輕量級鎖、重量級鎖?