轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/75126630 本文出自 "【趙彥軍的博客】" 1、前言 在多線程併發編程中Synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖,但是隨著Java SE1.6對Sync ...
轉載請標明出處:http://blog.csdn.net/zhaoyanjun6/article/details/75126630
本文出自【趙彥軍的博客】
1、前言
在多線程併發編程中Synchronized一直是元老級角色,很多人都會稱呼它為重量級鎖,但是隨著Java SE1.6對Synchronized進行了各種優化之後,有些情況下它並不那麼重了,本文詳細介紹了Java SE1.6中為了減少獲得鎖和釋放鎖帶來的性能消耗而引入的偏向鎖和輕量級鎖,以及鎖的存儲結構和升級過程。
2、同步的基礎
java中的每一個對象都可以作為鎖。
對於同步方法,鎖是當前實例對象。
對於靜態同步方法,鎖是當前對象的Class對象。
對於同步方法塊,鎖是Synchonized括弧里配置的對象。
當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。那麼鎖存在哪裡呢?鎖裡面會存儲什麼信息呢?
3、同步的原理
JVM規範規定JVM基於進入和退出Monitor對象來實現方法同步和代碼塊同步,但兩者的實現細節不一樣。代碼塊同步是使用monitorenter和monitorexit指令實現,而方法同步是使用另外一種方式實現的,細節在JVM規範里並沒有詳細說明,但是方法的同步同樣可以使用這兩個指令來實現。monitorenter指令是在編譯後插入到同步代碼塊的開始位置,而monitorexit是插入到方法結束處和異常處, JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有一個 monitor 與之關聯,當且一個monitor 被持有後,它將處於鎖定狀態。線程執行到 monitorenter 指令時,將會嘗試獲取對象所對應的 monitor 的所有權,即嘗試獲得對象的鎖。
4、synchronized 關鍵字
java語言的關鍵字,當它用來修飾一個方法或者一個代碼塊的時候,能夠保證在同一時刻最多只有一個線程執行該段代碼。
一:當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以後才能執行該代碼塊。
二:然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。
三:尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。
四、第三個例子同樣適用其它同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其它線程對該object對象所有同步代碼部分的訪問都被暫時阻塞。
五、以上規則對其它對象鎖同樣適用.
5、類鎖和對象鎖
java的對象鎖和類鎖:java的對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區別的。
對象鎖是用於對象實例方法,或者一個對象實例上的,類鎖是用於類的靜態方法或者一個類的class對象上的。我們知道,類的對象實例可以有很多個,但是每個類只有一個class對象,所以不同對象實例的對象鎖是互不幹擾的,但是每個類只有一個類鎖。但是有一點必須註意的是,其實類鎖只是一個概念上的東西,並不是真實存在的,它只是用來幫助我們理解鎖定實例方法和靜態方法的區別的。
- 類鎖和對象鎖
package com;
public class Person {
static Person person ;
/**
* 類鎖
*/
public void run1(){
synchronized( Person.class ) {
System.out.println( Thread.currentThread().getName() + " 11--> " + "a" );
}
}
/**
* 類鎖
*
* 靜態方法鎖,是類鎖
*/
public synchronized static void run11(){
}
/**
* 類鎖
*/
public static void run(){
synchronized ( Person.class ) {
}
}
/**
* 對象鎖
*/
public void run2(){
synchronized( this ) {
System.out.println( Thread.currentThread().getName() + " 22--> " + "a" );
}
}
/**
* 對象鎖
* 普通方法鎖是對象鎖
*/
public synchronized void run3(){
System.out.println( Thread.currentThread().getName() + " 33--> " + "a" );
}
}
其實,類鎖修飾方法和代碼塊的效果和對象鎖是一樣的,因為類鎖只是一個抽象出來的概念,只是為了區別靜態方法的特點,因為靜態方法是所有對象實例共用的,所以對應著synchronized修飾的靜態方法的鎖也是唯一的,所以抽象出來個類鎖。類鎖和對象鎖是兩個不一樣的鎖,控制著不同的區域,它們是互不幹擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
這時有一個疑問,既然有了synchronized修飾方法的同步方式,為什麼還需要synchronized修飾同步代碼塊的方式呢?而這個問題也是synchronized的缺陷所在。
synchronized的缺陷:當某個線程進入同步方法獲得對象鎖,那麼其他線程訪問這裡對象的同步方法時,必須等待或者阻塞,這對高併發的系統是致命的,這很容易導致系統的崩潰。如果某個線程在同步方法裡面發生了死迴圈,那麼它就永遠不會釋放這個對象鎖,那麼其他線程就要永遠的等待。這是一個致命的問題。
當然同步方法和同步代碼塊都會有這樣的缺陷,只要用了synchronized關鍵字就會有這樣的風險和缺陷。既然避免不了這種缺陷,那麼就應該將風險降到最低。這也是同步代碼塊在某種情況下要優於同步方法的方面。
例如在某個類的方法裡面:這個類裡面聲明瞭一個對象實例,SynObject so=new SynObject();在某個方法裡面調用了這個實例的方法so.testsy();但是調用這個方法需要進行同步,不能同時有多個線程同時執行調用這個方法。
這時如果直接用synchronized修飾調用了so.testsy();代碼的方法,那麼當某個線程進入了這個方法之後,這個對象其他同步方法都不能給其他線程訪問了。假如這個方法需要執行的時間很長,那麼其他線程會一直阻塞,影響到系統的性能。
如果這時用synchronized來修飾代碼塊:synchronized(so){so.testsy();},那麼這個方法加鎖的對象是so這個對象,跟執行這行代碼的對象沒有關係,當一個線程執行這個方法時,這對其他同步方法時沒有影響的,因為他們持有的鎖都完全不一樣。
一個類的對象鎖和另一個類的對象鎖是沒有關聯的,當一個線程獲得A類的對象鎖時,它同時也可以獲得B類的對象鎖。
個人微信號:zhaoyanjun125
, 歡迎關註