Synchronized及其实现原理
并發編程中synchronized一直是元老級角色,我們稱之為重量級鎖。主要用在三個地方:
1、修飾普通方法,鎖是當前實例對象。
2、修飾類方法,鎖是當前類的Class對象。
3、修飾代碼塊,鎖是synchronized括號里面的對象。
一、synchronized實現原理
當一個線程試圖訪問同步代碼塊時,必須得到鎖。在退出或拋出異常時必須釋放鎖,JVM是基于進入和退出Monitor來實現方法同步和代碼塊同步。
我們來看下synchronized的字節碼:
public class SynchronizedTest
{ ? ?public void addNum1(String userName)
? ?{
? ?} ? ?
? ?public void addNum2(String userName)
? ?{ ? ? ? ?synchronized(this)
? ? ? ?{
? ? ? ?}
? ?} ? ?
? ?public synchronized void addNum3(String userName)
? ?{
? ?}
}
在字節碼里可以看到,用synchronizde修飾的同步代碼塊多了兩個指令:monitorenter、monitorexit;
代碼塊同步是使用monitorenter、monitorexit指令實現的,而方法同步是使用另外一種方式實現的,但是方法同步也可以使用這兩個指令來實現。
monitorenter指令是編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法的結束和異常位置。任何一個對象都有一個monitor與之關聯。線程執行到monitorenter指令處時,會嘗試獲取對象所對應的monitor所有權,即嘗試獲得對象的鎖。
二、修飾普通方法 鎖是當前實例對象
我們先來看下將實例對象作為鎖的概念:
public class AddNumTest
{ ? ?private int num = 0; ? ?
? ?public synchronized void addNum(String str)
? ?{ ? ? ? ?try
? ? ? ?{ ? ? ? ? ? ?if ("a".equals(str))
? ? ? ? ? ?{
? ? ? ? ? ? ? ?num = 10;
? ? ? ? ? ? ? ?System.out.println("add a");
? ? ? ? ? ? ? ?Thread.sleep(2000);
? ? ? ? ? ?} ? ? ? ? ? ?else
? ? ? ? ? ?{
? ? ? ? ? ? ? ?num = 20;
? ? ? ? ? ? ? ?System.out.println("add b");
? ? ? ? ? ?}
? ? ? ? ? ?System.out.println(str + " num = " + num);
? ? ? ?} ? ? ? ?catch (InterruptedException e)
? ? ? ?{
? ? ? ? ? ?e.printStackTrace();
? ? ? ?}
? ?}
}
public class AddNumThreadOne implements Runnable
{ ? ?private AddNumTest at; ? ?
? ?public AddNumThreadOne(AddNumTest at)
? ?{ ? ? ? ?this.at = at;
? ?}
? ?@Override ? ?public void run()
? ?{
? ? ? ?at.addNum("a");
? ?}
}
public class AddNumThreadTwo implements Runnable
{ ? ?private AddNumTest at; ? ?public AddNumThreadTwo(AddNumTest at)
? ?{ ? ? ? ?this.at = at;
? ?}
? ?
? ?@Override ? ?public void run()
? ?{
? ? ? ?at.addNum("b");
? ?}
}
public class AddNum
{ ? ?public static void main(String[] args)
? ?{ ? ? ? ?//注意,這里傳入同一個實例對象
? ? ? ?AddNumTest at = new AddNumTest(); ? ? ? ?//AddNumTest bt = new AddNumTest();
? ? ? ?Thread t1 = new Thread(new AddNumThreadOne(at));
? ? ? ?Thread t2 = new Thread(new AddNumThreadTwo(at));
? ? ? ?t1.start();
? ? ? ?t2.start();
? ?}
}
執行結果:
add aa num = 10add b
b num = 20
前面解釋過關鍵字synchronized的實現原理是使用對象的monitor來實現的,取的鎖都是對象鎖,而不是把一段代碼或者函數作為鎖。在并發情況下,如果并發情況下多線程競爭的是同一個對象,那么先來的獲取該對象鎖,后面的線程只能排隊,等前面的線程執行完畢釋放鎖。
上面介紹的是同一個對象鎖,我們來觀察下獲取不同的對象鎖會是什么情況:
public static void main(String[] args)
? ?{ ? ? ? ?//注意,這里傳入的不同的實例對象
? ? ? ?AddNumTest at = new AddNumTest();
? ? ? ?AddNumTest bt = new AddNumTest();
? ? ? ?Thread t1 = new Thread(new AddNumThreadOne(at));
? ? ? ?Thread t2 = new Thread(new AddNumThreadTwo(bt));
? ? ? ?t1.start();
? ? ? ?t2.start();
? ?}
執行結果:
add aadd b
b num = 20a num = 10
這里線程1、2搶占的是不同的鎖,盡管線程1先到達同步代碼塊的位置,但是由于monitor不一樣,所以不能阻塞線程2的執行。
三、synchronized鎖重入
鎖重入:當一個線程得到一個對象鎖后,再次請求此對象鎖時可以再次得到該對象的鎖。但是這里有維護一個計數器,同一個線程每次得到對象鎖計數器都會加1,釋放的時候減1,直到計數器的數值為0的時候,才能被其他線程所搶占
public class AgainLock
{ ? ?public synchronized void print1()
? ?{
? ? ? ?System.out.println("do work print1");
? ? ? ?print2();
? ?} ? ?
? ?public synchronized void print2()
? ?{
? ? ? ?System.out.println("do work print2");
? ? ? ?print3();
? ?} ? ?
? ?public synchronized void print3()
? ?{
? ? ? ?System.out.println("do work print3");
? ?}
}
public class AgainLockTest
{ ? ?public static void main(String[] args)
? ?{
? ? ? ?Thread t = new Thread(new Runnable()
? ? ? ?{
? ? ? ? ? ?@Override ? ? ? ? ? ?public void run()
? ? ? ? ? ?{
? ? ? ? ? ? ? ?AgainLock al = new AgainLock();
? ? ? ? ? ? ? ?al.print1();
? ? ? ? ? ?}
? ? ? ?});
? ? ? ?t.start();
? ?}
}
執行結果:
do work print1do work print2do work print3這里面三個同步方法,使用的鎖都是該實例對象的同步鎖,同一個線程在執行的時候,每次都是在鎖沒有釋放的時候,就要重新再去獲取同一把對象鎖。從運行結果可以看出,關鍵字synchronized支持同一線程鎖重入。
四、synchronized同步代碼塊
用synchronized同步方法的粒度過大,有時候一個方法里面的業務邏輯很多,但是我們想對同步的部分進行單獨定制,這時候就可以使用synchronized來同步代碼塊。
public class SynchronizedTest1
{ ? ?public void doWorkTask()
? ?{ ? ? ? ?for(int i=0;i<100;i++)
? ? ? ?{
? ? ? ? ? ?System.out.println("nosynchronized thread name =" + Thread.currentThread().getName() ? ? ? ? ? ? ? ?+ ";i=" + i);
? ? ? ?} ? ? ? ?synchronized(this)
? ? ? ?{ ? ? ? ? ? ?for(int i=0;i<100;i++)
? ? ? ? ? ?{
? ? ? ? ? ? ? ?System.out.println("thread name =" + Thread.currentThread().getName() ? ? ? ? ? ? ? ? ? ?+ ";i=" + i);
? ? ? ? ? ?}
? ? ? ?}
? ?}
}
如果在并發情況下,調用這個類的同一個實例,線程A和B可以同時執行使用synchronized同步之前的代碼邏輯,但是使用關鍵字同步的部分是互斥的,先到達的線程占有對象鎖,后面的線程會被阻塞,直到對象鎖被前面的線程釋放。
轉載于:https://blog.51cto.com/13013666/1949236
總結
以上是生活随笔為你收集整理的Synchronized及其实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: N001-SQL Server 2016
- 下一篇: 《Unity3D-控制角色受伤的时候身体