JUC之Lock接口以及Synchronized回顧


Lock接口

Synchronized關鍵字回顧:

多線程編程步驟(上):

  1. 創建資源類,在資源類創建屬性和操作方法
  2. 創建多個線程,調用資源類的操作方法

創建線程的四種方式:

  1. 繼承Thread
  2. 實現Runnable接口
  3. 使用Callable接口
  4. 使用線程池

使用synchronized同步實現售票問題:

只有當資源是空閑的時候,線程才能訪問。

 /**
  * 創建資源
  */
 class ticket{
     private  int number = 30;
     public synchronized void sell(){
         if(number >0){
             System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
         }
 ​
     }
 }
 ​
 /**
  * 創建多個線程,調用資源類的操作方法
  */
 public class sellTicket {
     public static void main(String[] args) {
         /**
          * 創建資源
          */
         ticket ticket = new ticket();
         /**
          * 創建三個線程(售票員)
          */
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "aaa").start();
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "bbb").start();
         new Thread(new Runnable() {
             public void run() {
                 for (int i = 0; i < 40; i++) {
                     ticket.sell();
                 }
             }
         }, "ccc").start();
     }
 }
 ​

學習《Java並發編程的藝術》一節是synchronized的實現原理與應用

Java中的每一個對象都可以作為鎖:

  1. 對於同步方法,鎖的是當前的對象。
  2. 對於靜態方法,鎖的是當前類的class對象。
  3. 對於靜態代碼塊,Synchonized括號里配置的對象。

當一個線程試圖訪問同步代碼塊時,它首先必須得到鎖,退出或拋出異常時必須釋放鎖。

那么鎖到底存在哪里呢?鎖里面會存儲什么信息呢?

這個問題是該書中的一個問題,對個人來說有點抽象,主要點是:JVM基於進入和退出Monitor(監視器)對象來實現方法同步和代碼塊同步,但兩者的實現細節不一樣。

代碼塊同步是使用monitorenter 和monitorexit指令實現的,而方法同步是使用另外一種方式實現的,細節在JVM規范里並沒有 詳細說明。但是,方法的同步同樣可以使用這兩個指令來實現。

monitorenter指令是在編譯后插入到同步代碼塊的開始位置,而monitorexit是插入到方法結 束處和異常處,JVM要保證每個monitorenter必須有對應的monitorexit與之配對。任何對象都有 一個monitor與之關聯,當且一個monitor被持有后,它將處於鎖定狀態。線程執行到monitorenter 指令時,將會嘗試獲取對象所對應的monitor的所有權,即嘗試獲得對象的鎖。

其中monitorenter主要是獲取監視器鎖,monitorexit主要是釋放監視器鎖。

synchonized中的一個概念是Java對象頭:

synchronized用的鎖是存放在Java對象頭里面的。

非數組類型 數組類型
虛擬機 3個字寬 2個字寬

需要注意的是:在32位虛擬機中:1字寬=4字節,即:32bit

Java對象頭里的Mark Word里面默認存儲對象HashCode、分代年齡和鎖標記位。

在運行期間,Mark Word里面存儲的數據會隨着鎖標志位的變化而變化。

32位JVM的Mark Word的默認存儲結構與在64位JVM不相同

Lock接口並創建實例:

Java並發編程的藝術

在JavaSE5之前使用的是synchronized關鍵字實現鎖功能,5v之后並發包中新增Lock接口以及相關實現類用來實現鎖功能,提供與synchronized類似的同步關系,但是比其更具有靈活性和擴展性(擁有了鎖獲取與釋放的可操作性、可中斷的獲取鎖以及超時獲取鎖)。

查看Lock的文檔得知:

使用 lock 塊來調用 try,在之前/之后的構造中,最典型的代碼如下:

  class X {
    private final ReentrantLock lock = new ReentrantLock();
    // ...
 ​
    public void m() { 
      lock.lock();  // block until condition holds
      try {
        // ... method body
      } finally {
        lock.unlock()
      }
    }
  }
  

根據上述的代碼,實現買票代碼:

 import java.util.concurrent.locks.ReentrantLock;
 class ticket2{
     private int number = 30;
 ​
     //創建一個重入鎖
     private ReentrantLock lock = new ReentrantLock();
 ​
     public void sale(){
         //在內容之前加鎖
         lock.lock();
         if(number >0){
             System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
         }
         //在內容之后解鎖
         lock.unlock();
     }
 }
 ​
 public class lockCase {
     public static void main(String[] args) {
         ticket2 tk = new ticket2();
         //創建線程
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"aaa").start();
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"bbb").start();
         new Thread(()->{
             for (int i = 0; i < 40; i++) {
                 tk.sale();
             }
         },"bbb").start();
     }
 }

上述代碼中,在ticket2類中存在一個問題就是,當lock中間內容如果出現報錯,那么后面的代碼無法執行,也就是鎖無法釋放。所以我們需要將unlock()放到finally中。

目的是保證在獲取到鎖之后,最終能夠被釋放。

 public void sale(){
         //在內容之前加鎖
         lock.lock();
         try{
             if(number >0){
                 System.out.println(Thread.currentThread().getName()+":賣出"+number--+"剩余:"+number);
             }
             //在內容之后解鎖
         }finally{
             lock.unlock();
         }
 ​
     }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM