并發編程總結之synchronized細節問題

brucewu01 9年前發布 | 9K 次閱讀 并發 Java開發

摘要:本節主要介紹了并發編程下怎么避免數據臟讀和什么是synchronized的可重入鎖,synchronized的可重入鎖的幾種使用場景下,是線程安全的。

臟讀

什么是臟讀

對于對象的同步和異步方法,我們在設計程序,一定要考慮問題的整體性,不然會出現數據不一致的錯誤,最經典的錯誤就是臟讀(DirtyRead)。

示例Code

/**
 * 業務整體需要使用完整的synchronized,保持業務的原子性。
 * 
 * @authorxujin
 *
 */
publicclassDirtyRead{

privateString username ="xujin";
privateString password ="123";

publicsynchronizedvoidsetValue(String username, String password){
this.username = username;
try{
 Thread.sleep(2000);
 } catch(InterruptedException e) {
 e.printStackTrace();
 }

this.password = password;

 System.out.println("setValue最終結果:username = "+ username +" , password = "+ password);
 }
//①這里getValue沒有加synchronized修飾
publicvoidgetValue(){
 System.out.println("getValue方法得到:username = "+this.username +" , password = "+this.password);
 }

publicstaticvoidmain(String[] args)throwsException{

finalDirtyRead dr =newDirtyRead();
 Thread t1 = newThread(newRunnable() {
@Override
publicvoidrun(){
 dr.setValue("張三","456");
 }
 });
 t1.start();
 Thread.sleep(1000);

 dr.getValue();
 }

}

上面的Code中,getValue沒有加synchronized修飾,打印結果如下,出現臟讀

getValue方法得到:username = 張三 , password = 123
setValue最終結果:username = 張三 , password = 456

只需在getValue加synchronized修飾,如下:

publicsynchronizedvoidgetValue(){
 System.out.println("getValue方法得到:username = "+this.username +" , password = "+this.password);
}

運行結果如下,沒有造成數據臟讀

setValue最終結果:username = 張三 , password = 456
getValue方法得到:username = 張三 , password = 456

小結

在我們對對象中的一個方法加鎖的時候,需要考慮業務的或程序的整體性,也就是為程序中的set和get方法同時加鎖synchronized同步關鍵字,保證業務的(service層)的原子性,不然會出現數據錯誤,臟讀。

synchronized的重入

什么是synchronized的重入鎖

  1. synchronized,它擁有強制原子性的內置鎖機制,是一個重入鎖,所以在使用synchronized時,當一個線程請求得到一個對象鎖后再次請求此對象鎖,可以再次得到該對象鎖,就是說在一個synchronized方法/塊的內部調用本類的其他synchronized方法/塊時,是永遠可以拿到鎖。
  2. 當線程請求一個由其它線程持有的對象鎖時,該線程會阻塞,而當線程請求由自己持有的對象鎖時,如果該鎖是重入鎖,請求就會成功,否則阻塞.

簡單的說:關鍵字synchronized具有 鎖重入 的功能,也就是在使用 synchronized時 , 當一個線程 得到一個 對象鎖 的 鎖后 , 再次請求此對象時 可以 再次 得到該 對象對應的鎖 。

嵌套調用關系synchronized的重入

嵌套調用關系synchronized的重入也是線程安全的,下面是method1,method2,method3都被synchronized修飾,調用關系method1–>method2–>method3,也是線程安全的。

/**
 * synchronized的重入
 * 
 * @authorxujin
 *
 */
publicclassSyncReenTrant{

publicsynchronizedvoidmethod1(){
 System.out.println("method1..");
 method2();
 }

publicsynchronizedvoidmethod2(){
 System.out.println("method2..");
 method3();
 }

publicsynchronizedvoidmethod3(){
 System.out.println("method3..");
 }

publicstaticvoidmain(String[] args){
finalSyncReenTrant sd =newSyncReenTrant();
 Thread t1 = newThread(newRunnable() {
@Override
publicvoidrun(){
 sd.method1();
 }
 });
 t1.start();
 }

運行結果如下:

method1..
method2..
method3..

繼承關系的synchronized的重入

簡單 Code1:

public class Son extends Father {

 public synchronized void doSomething() {
 System.out.println("child.doSomething()");
 // 調用自己類中其他的synchronized方法
 doAnotherThing();

 }

 private synchronized void doAnotherThing() {
 // 調用父類的synchronized方法
 super.doSomething();
 System.out.println("child.doAnotherThing()");
 }

 public static void main(String[] args) {
 Son child = new Son();
 child.doSomething();
 }
}

class Father {
 public synchronized void doSomething() {
 System.out.println("father.doSomething()");
 }

}

運行結果:

child.doSomething()
father.doSomething()
child.doAnotherThing()
  1. 這里的對象鎖只有一個,就是child對象的鎖,當執行child.doSomething時,該線程獲得child對象的鎖,在doSomething方法內執行doAnotherThing時再次請求child對象的鎖,因為synchronized是重入鎖,所以可以得到該鎖,繼續在doAnotherThing里執行父類的doSomething方法時第三次請求child對象的鎖,同理可得到,如果不是重入鎖的話,那這后面這兩次請求鎖將會被一直阻塞,從而導致死鎖。
  2. 所以在Java內部,同一線程在調用自己類中其他synchronized方法/塊或調用父類的synchronized方法/塊都不會阻礙該線程的執行,就是說同一線程對同一個對象鎖是可重入的,而且同一個線程可以獲取同一把鎖多次,也就是可以多次重入。因為java線程是基于“每線程(per-thread)”,而不是基于“每調用(per-invocation)”的(java中線程獲得對象鎖的操作是以每線程為粒度的,per-invocation互斥體獲得對象鎖的操作是以每調用作為粒度的)

我們再來看看重入鎖是怎么實現可重入性的,其實現方法是為每個鎖關聯一個線程持有者和計數器,當計數器為0時表示該鎖沒有被任何線程持有,那么任何線程都可能獲得該鎖而調用相應的方法;當某一線程請求成功后,JVM會記下鎖的持有線程,并且將計數器置為1;此時其它線程請求該鎖,則必須等待;而該持有鎖的線程如果再次請求這個鎖,就可以再次拿到這個鎖,同時計數器會遞增;當線程退出同步代碼塊時,計數器會遞減,如果計數器為0,則釋放該鎖。

publicclassSyncExtends{

// 父類
staticclassFather{
publicinti =10;

publicsynchronizedvoidoperationSup(){
try{
 i--;
 System.out.println("Father print i = "+ i);
 Thread.sleep(100);
 } catch(InterruptedException e) {
 e.printStackTrace();
 }
 }
 }

// 子類繼承父類
staticclassSonextendsFather{
publicsynchronizedvoidoperationSub(){
try{
while(i >0) {
 i--;
 System.out.println("Son print i = "+ i);
 Thread.sleep(100);
this.operationSup();
 }
 } catch(InterruptedException e) {
 e.printStackTrace();
 }
 }
 }

publicstaticvoidmain(String[] args){

 Thread t1 = newThread(newRunnable() {
@Override
publicvoidrun(){
 Son sub = newSon();
 sub.operationSub();
 }
 });

 t1.start();
 }

}

運行結果如下:

Son print i = 9
Father print i = 8
Son print i = 7
Father print i = 6
Son print i = 5
Father print i = 4
Son print i = 3
Father print i = 2
Son print i = 1
Father print i = 0

參考文章:

http://blog.csdn.net/aigoogle/article/details/29893667

 

 

來自:http://blog.xujin.org/2016/11/26/bf/bf-synchronized/

 

 本文由用戶 brucewu01 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!