首頁 >Java >java教程 >Java 並發程式設計學習筆記之Synchronized簡介

Java 並發程式設計學習筆記之Synchronized簡介

高洛峰
高洛峰原創
2017-01-05 16:20:511210瀏覽

一、Synchronized的基本使用

  Synchronized是Java中解決並發問題的一種最常用的方法,也是最簡單的一種方法。 Synchronized的功能主要有三:(1)確保執行緒互斥的存取同步程式碼(2)確保共享變數的修改能夠及時可見(3)有效解決重排序問題。從語法上講,Synchronized總共有三種用法:

  (1)修飾普通方法

  (2)修飾靜態方法

  (3)修飾代碼塊🎀

〜接下來幾個例子三種使用方式(為了方便比較,三段程式碼除了Synchronized的使用方式不同以外,其他基本上保持一致)。

1、沒有同步的情況:

程式碼段一:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

執行結果如下,執行緒1和執行緒2同時進入執行狀態,執行緒2執行速度比執行緒1快,所以執行緒2先執行完成,這個過程中線程1和線程2是同時執行的。

Method 1 start

Method 1 execute
Method 2 start
Method 2 execute
Method 2 end
Method 1 end

 2、對普通方法同步:以下程式碼同步段一比較,可以很明顯的看出,執行緒2需要等待執行緒1的method1執行完成才能開始執行method2方法。

Method 1 start

Method 1 execute

Method 1 end

Method 2 start

Method 2 execute

Method 2 end


3、靜態方法(類)同步化靜態方法的同步本質上是對類別的同步(靜態方法本質上是屬於類別的方法,而不是對像上的方法),所以即使test和test2屬於不同的對象,但是它們都屬於SynchronizedTest類別的實例,所以也只能順序的執行method1和method2,不能並發執行。

Method 1 start
Method 1 execute

Method 1 end

Method 2 start

Method 2 execute

Method 2 end

rr

4、代碼塊同步

Method 2 end

線程2都進入了對應的方法開始執行,但是線程2在進入同步區塊之前,需要等待線程1中同步區塊執行完成。

Method 1 start
Method 1 execute
Method 2 start

Method 1 end

Method 2 execute

Method 2 end

、Synchronized 問題還是急先來了解Synchronized的原理,再回頭上面的問題就一目了然了。我們先透過反編譯下面的程式碼來看看Synchronized是如何實現對程式碼區塊進行同步的:

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public synchronized void method1(){
    System.out.println("Method 1 start");
    try {
      System.out.println("Method 1 execute");
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public synchronized void method2(){
    System.out.println("Method 2 start");
    try {
      System.out.println("Method 2 execute");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

反編譯結果:




關於這兩條指令的作用,我們直接參考JVM規範中描述:

monitorenter :

package com.paddx.test.concurrent;
  
 public class SynchronizedTest {
   public static synchronized void method1(){
     System.out.println("Method 1 start");
     try {
       System.out.println("Method 1 execute");
       Thread.sleep(3000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 1 end");
   }
  
   public static synchronized void method2(){
     System.out.println("Method 2 start");
     try {
       System.out.println("Method 2 execute");
       Thread.sleep(1000);
     } catch (InterruptedException e) {
       e.printStackTrace();
     }
     System.out.println("Method 2 end");
   }
  
   public static void main(String[] args) {
     final SynchronizedTest test = new SynchronizedTest();
     final SynchronizedTest test2 = new SynchronizedTest();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test.method1();
       }
     }).start();
  
     new Thread(new Runnable() {
       @Override
       public void run() {
         test2.method2();
       }
     }).start();
   }
 }

   

這段話的大概意思為:

Java 并发编程学习笔记之Synchronized简介每個物件都有監視器鎖定(monitor)。當monitor被佔用時就會處於鎖定狀態,線程執行monitorenter指令時嘗試取得monitor的所有權,過程如下:

1、如果monitor的進入數為0,則該線程進入monitor,然後將進入數設為1 ,該線程即為monitor的所有者。

2、如果線程已經佔有該monitor,只是重新進入,則進入monitor的進入數加1.

3.如果其他線程已經佔用了monitor,則該線程進入阻塞狀態,直到monitor的進入數為0 ,再重新嘗試取得monitor的所有權。

monitorexit: 

package com.paddx.test.concurrent;
 
public class SynchronizedTest {
  public void method1(){
    System.out.println("Method 1 start");
    try {
      synchronized (this) {
        System.out.println("Method 1 execute");
        Thread.sleep(3000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 1 end");
  }
 
  public void method2(){
    System.out.println("Method 2 start");
    try {
      synchronized (this) {
        System.out.println("Method 2 execute");
        Thread.sleep(1000);
      }
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    System.out.println("Method 2 end");
  }
 
  public static void main(String[] args) {
    final SynchronizedTest test = new SynchronizedTest();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method1();
      }
    }).start();
 
    new Thread(new Runnable() {
      @Override
      public void run() {
        test.method2();
      }
    }).start();
  }
}

   

這段話的大概意思是:

執行monitorexit的執行緒必須是objectref所對應的monitor的擁有者。

指令執行時,monitor的進入數減1,如果減1後進入數為0,那線程退出monitor,不再是這個monitor的所有者。其他被這個monitor阻塞的線程可以嘗試去取得這個 monitor 的所有權。

  透過這兩段描述,我們應該能很清楚的看出Synchronized的實現原理,Synchronized的語義底層是透過一個monitor的物件來完成,其實wait/notify等方法也依賴於monitor對象,這就是為什麼只有monitor在同步的區塊或方法中才能呼叫wait/notify等方法,否則會拋出java.lang.IllegalMonitorStateException的異常的原因。

  我們再來看看同步方法的反編譯結果:

原始碼:

package com.paddx.test.concurrent;
 
public class SynchronizedMethod {
  public synchronized void method() {
    System.out.println("Hello World!");
  }
}

  从反编译的结果来看,方法的同步并没有通过指令monitorenter和monitorexit来完成(理论上其实也可以通过这两条指令来实现),不过相对于普通方法,其常量池中多了ACC_SYNCHRONIZED标示符。JVM就是根据该标示符来实现方法的同步的:当方法调用时,调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor。在方法执行期间,其他任何线程都无法再获得同一个monitor对象。 其实本质上没有区别,只是方法的同步是一种隐式的方式来实现,无需通过字节码来完成。

三、运行结果解释

  有了对Synchronized原理的认识,再来看上面的程序就可以迎刃而解了。

1、代码段2结果:

  虽然method1和method2是不同的方法,但是这两个方法都进行了同步,并且是通过同一个对象去调用的,所以调用之前都需要先去竞争同一个对象上的锁(monitor),也就只能互斥的获取到锁,因此,method1和method2只能顺序的执行。

2、代码段3结果:

  虽然test和test2属于不同对象,但是test和test2属于同一个类的不同实例,由于method1和method2都属于静态同步方法,所以调用的时候需要获取同一个类上monitor(每个类只对应一个class对象),所以也只能顺序的执行。

3、代码段4结果:

  对于代码块的同步实质上需要获取Synchronized关键字后面括号中对象的monitor,由于这段代码中括号的内容都是this,而method1和method2又是通过同一的对象去调用的,所以进入同步块之前需要去竞争同一个对象上的锁,因此只能顺序执行同步块。

四 总结

  Synchronized是Java并发编程中最常用的用于保证线程安全的方式,其使用相对也比较简单。但是如果能够深入了解其原理,对监视器锁等底层知识有所了解,一方面可以帮助我们正确的使用Synchronized关键字,另一方面也能够帮助我们更好的理解并发编程机制,有助我们在不同的情况下选择更优的并发策略来完成任务。对平时遇到的各种并发问题,也能够从容的应对。


更多Java 并发编程学习笔记之Synchronized简介相关文章请关注PHP中文网!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn