日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Synchronized及其实现原理

發布時間:2025/3/20 编程问答 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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 a
a 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 a
add 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及其实现原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。