java同步锁synchronized_Java对象锁和类锁全面解析(多线程synchronized关键字)
本文主要是將synchronized關鍵字用法作為例子來去解釋Java中的對象鎖和類鎖。特別的是希望能幫大家理清一些概念。
一、synchronized關鍵字
synchronized關鍵字有如下兩種用法:
1、 在需要同步的方法的方法簽名中加入synchronized關鍵字。
上面的代碼修飾的synchronized是非靜態方法,如果修飾的是靜態方法(static)含義是完全不一樣的。具體不一樣在哪里,后面會詳細說清楚。
2、使用synchronized塊對需要進行同步的代碼段進行同步。
上面的代碼塊是synchronized (this)用法,還有synchronized (非this對象)以及synchronized (類.class)這兩種用法,這些使用方式的含義也是有根本的區別的。我們先帶著這些問題繼續往下看。
二、Java中的對象鎖和類鎖
小寶鴿似乎并沒有辦法用清晰簡短的語言來描述對象鎖和類鎖的概念。即便能用簡單的語句概況,也會顯得抽象。猿友們耐心看完自然會明白。
之前網上有找一些相關資料,有篇博客是這樣描述的(看的是轉載的,原創連接我也不知道):
一段synchronized的代碼被一個線程執行之前,他要先拿到執行這段代碼的權限,在Java里邊就是拿到某個同步對象的鎖(一個對象只有一把鎖);如果這個時候同步對象的鎖被其他線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池等待隊列中)。 取到鎖后,他就開始執行同步代碼(被synchronized修飾的代碼);線程執行完同步代碼后馬上就把鎖還給同步對象,其他在鎖池中等待的某個線程就可以拿到鎖執行同步代碼了。這樣就保證了同步代碼在統一時刻只有一個線程在執行。這段話,除了最后一句,講得都是挺合理的。”這樣就保證了同步代碼在統一時刻只有一個線程在執行。”這句話顯然不對,synchronized并非保證同步代碼同一時刻只有一個線程執行,同步代碼同一時刻應該可以有多個線程執行。
上面提到鎖,這里先引出鎖的概念。先來看看下面這些啰嗦而必不可少的文字。
多線程的線程同步機制實際上是靠鎖的概念來控制的。
在Java程序運行時環境中,JVM需要對兩類線程共享的數據進行協調:
1)保存在堆中的實例變量
2)保存在方法區中的類變量
這兩類數據是被所有線程共享的。
(程序不需要協調保存在Java 棧當中的數據。因為這些數據是屬于擁有該棧的線程所私有的。)
方法區(Method Area)與Java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。雖然Java虛擬機規范把方法區描述為堆的一個邏輯部分,但是它卻有一個別名叫做Non-Heap(非堆),目的應該是與Java堆區分開來。
棧:在Java中,JVM中的棧記錄了線程的方法調用。每個線程擁有一個棧。在某個線程的運行過程中,如果有新的方法調用,那么該線程對應的棧就會增加一個存儲單元,即幀(frame)。在frame中,保存有該方法調用的參數、局部變量和返回地址。
堆是JVM中一塊可自由分配給對象的區域。當我們談論垃圾回收(garbage collection)時,我們主要回收堆(heap)的空間。
Java的普通對象存活在堆中。與棧不同,堆的空間不會隨著方法調用結束而清空。因此,在某個方法中創建的對象,可以在方法調用結束之后,繼續存在于堆中。這帶來的一個問題是,如果我們不斷的創建新的對象,內存空間將最終消耗殆盡。
在java虛擬機中,每個對象和類在邏輯上都是和一個監視器相關聯的。
對于對象來說,相關聯的監視器保護對象的實例變量。
對于類來說,監視器保護類的類變量。
(如果一個對象沒有實例變量,或者一個類沒有變量,相關聯的監視器就什么也不監視。)
為了實現監視器的排他性監視能力,java虛擬機為每一個對象和類都關聯一個鎖。代表任何時候只允許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。
但是如果線程獲取了鎖,那么在它釋放這個鎖之前,就沒有其他線程可以獲取同樣數據的鎖了。(鎖住一個對象就是獲取對象相關聯的監視器)
類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會創建一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。
一個線程可以多次對同一個對象上鎖。對于每一個對象,java虛擬機維護一個加鎖計數器,線程每獲得一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值為0時,鎖就被完全釋放了。
java編程人員不需要自己動手加鎖,對象鎖是java虛擬機內部使用的。
在java程序中,只需要使用synchronized塊或者synchronized方法就可以標志一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。
三、synchronized關鍵字各種用法與實例
看完了”二、Java中的對象鎖和類鎖”,我們再來結合”一、synchronized關鍵字”里面提到的synchronized用法。
事實上,synchronized修飾非靜態方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象,線程想要執行對應同步代碼,需要獲得對象鎖。
synchronized修飾非靜態方法以及同步代碼塊的synchronized (類.class)用法鎖的是類,線程想要執行對應同步代碼,需要獲得類鎖。
因此,事實上synchronized關鍵字可以細分為上面描述的五種用法。
本文的實例均來自于《Java多線程編程核心技術》這本書里面的例子。
1、我們先看看非線程安全實例(Run.java):
運行結果為:
修改HasSelfPrivateNum如下,方法用synchronized修飾如下:
運行結果是線程安全的:
實驗結論:兩個線程訪問同一個對象中的同步方法是一定是線程安全的。本實現由于是同步訪問,所以先打印出a,然后打印出b
這里線程獲取的是HasSelfPrivateNum的對象實例的鎖——對象鎖。
2、多個對象多個鎖
就上面的實例,我們將Run改成如下:
運行結果為:
這里是非同步的,因為線程athread獲得是numRef1的對象鎖,而bthread線程獲取的是numRef2的對象鎖,他們并沒有在獲取鎖上有競爭關系,因此,出現非同步的結果
這里插播一下:同步不具有繼承性
3、同步塊synchronized (this)
我們先看看代碼實例(Run.java)
運行結果:
這樣也是同步的,線程獲取的是同步塊synchronized (this)括號()里面的對象實例的對象鎖,這里就是ObjectService實例對象的對象鎖了。
需要注意的是synchronized (){}的{}前后的代碼依舊是異步的
4、synchronized (非this對象)
我們先看看代碼實例(Run.java)
不難看出,這里線程爭奪的是anyString的對象鎖,兩個線程有競爭同一對象鎖的關系,出現同步
現在有一個問題:一個類里面有兩個非靜態同步方法,會有影響么?
答案是:如果對象實例A,線程1獲得了對象A的對象鎖,那么其他線程就不能進入需要獲得對象實例A的對象鎖才能訪問的同步代碼(包括同步方法和同步塊)。不理解可以細細品味一下!
5、靜態synchronized同步方法
我們直接看代碼實例:
運行結果:
兩個線程在爭奪同一個類鎖,因此同步
6、synchronized (class)
對上面Service類代碼修改成如下:
運行結果:
兩個線程依舊在爭奪同一個類鎖,因此同步
需要特別說明:對于同一個類A,線程1爭奪A對象實例的對象鎖,線程2爭奪類A的類鎖,這兩者不存在競爭關系。也就說對象鎖和類鎖互補干預內政
靜態方法則一定會同步,非靜態方法需在單例模式才生效,但是也不能都用靜態同步方法,總之用得不好可能會給性能帶來極大的影響。另外,有必要說一下的是Spring的bean默認是單例的。
總結
以上是生活随笔為你收集整理的java同步锁synchronized_Java对象锁和类锁全面解析(多线程synchronized关键字)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 委托_动态代理:Java开发必
- 下一篇: python版本升级后编译_python