首頁  >  文章  >  Java  >  Java多執行緒中Lock怎麼使用

Java多執行緒中Lock怎麼使用

PHPz
PHPz轉載
2023-05-12 14:46:061523瀏覽

Jdk1.5 以後,在java.util.concurrent.locks 套件下,有一組實現線程同步的接口和類,說到線程的同步,可能大家都會想到synchronized 關鍵字,

這是java 內建的關鍵字,用來處理線程同步的,但這個關鍵字有很多的缺陷,使用起來也不是很方便和直觀,所以就出現了Lock,下面,我們

就來對比著講解Lock。

通常我們在使用 synchronized 關鍵字的時候會遇到下面這些問題:

(1)不可控性,無法做到隨心所欲的加鎖和釋放鎖定。

(2)效率比較低下,例如我們現在並發的讀兩個文件,讀與讀之間是互不影響的,但如果給這個讀的對象使用synchronized 來實現同步的話,

那麼只要有一個執行緒進入了,那麼其他的執行緒都要等待。

(3)無法知道執行緒是否取得到了鎖。

而上面synchronized 的這些問題,Lock 都可以很好的解決,並且jdk1.5 以後,還提供了各種鎖,例如讀寫鎖,但有一點需要注意,使用synchronized

關鍵時,無須手動釋放鎖,但使用Lock 必須手動釋放鎖。下面我們就來學習 Lock 鎖。

Lock 是一個上層的接口,其原型如下,總共提供了 6 個方法:

public interface Lock {
  // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
   void lock();
  // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
    调用线程的Thread.interrupted()方法,来中断线程的等待过程
  void lockInterruptibly() throws InterruptedException;
  // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
   boolean tryLock();
  // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  // 释放锁
   void unlock();
  // 实现线程通信,相当于wait和notify,后面会单独讲解
   Condition newCondition();
}

那麼這幾個方法該如何使用了?前面我們說到,使用Lock 是需要手動釋放鎖的,但是如果程式中拋出了異常,那麼就無法做到釋放鎖,有可能引起死鎖,

所以我們在使用Lock 的時候,有一個固定的格式,如下:

Lock l = ...;
      l.lock();
      try {
        // access the resource protected by this lock
      } finally {// 必须使用try,最后在finally里面释放锁
        l.unlock();
      }

下面我們來看一個簡單的例子,程式碼如下:

/**
 * 描述:Lock使用
 */
public class LockDemo {
    // new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类
   Lock lock = new ReentrantLock();
   public void readFile(String fileMessage){
      lock.lock();// 上锁
      try{
         System.out.println(Thread.currentThread().getName()+"得到了锁,正在读取文件……");
         for(int i=0; i<fileMessage.length(); i++){
            System.out.print(fileMessage.charAt(i));
         }
         System.out.println();
         System.out.println("文件读取完毕!");
      }finally{
         System.out.println(Thread.currentThread().getName()+"释放了锁!");
         lock.unlock();
      }
   }
   public void demo(final String fileMessage){
      // 创建若干个线程
      ExecutorService service = Executors.newCachedThreadPool();
      // 提交20个任务
      for(int i=0; i<20; i++){
         service.execute(new Runnable() {
            @Override
            public void run() {
               readFile(fileMessage);
               try {
                  Thread.sleep(20);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         });
      }
    // 释放线程池中的线程
      service.shutdown();
   }
}

Lock與synchronized的比較

1 、作用

lock 和synchronized 都是Java 中去用來解決執行緒安全問題的一個工具。

2、來源

sychronized 是 Java 中的關鍵字。

lock 是 JUC 套件裡面提供的一個接口,這個接口有很多實現類,其中就包括我們最常用的 ReentrantLock(可重入鎖)。

3、鎖的力度

sychronized 可以用兩種方式去控制鎖的力度:

把 sychronized 關鍵字修飾在方法層面。
修飾在程式碼區塊上。
鎖定對象的不同:

鎖定對象為靜態物件或是class對象,那麼這個鎖屬於全域鎖。
鎖定對象為普通實例對象,那麼這個鎖的範圍取決於這個實例的生命週期。
lock鎖的力度是透過 lock()與unlock()兩個方法決定的。在兩個方法之間的程式碼能保證其線程安全。 lock的作用域取決於lock實例的生命週期。

4、靈活性

lock鎖定比sychronized的彈性更高。

lock可以自主的去決定什麼時候加鎖與釋放鎖。只需要呼叫lock 的lock()和unlock()這兩個方法就可以。

sychronized 由於是關鍵字,所以他無法實作非阻塞競爭鎖的方法,一個執行緒取得鎖之後,其他鎖只能等待那個執行緒釋放之後才能有取得鎖的機會。

5、公平鎖定與非公平鎖定

公平鎖定:多個執行緒依照申請鎖定的順序去拿鎖,執行緒會直接進入佇列去排隊,永遠都是隊列的第一位才能得到鎖。

優點:所有的執行緒都能得到資源,不會餓死。
缺點:吞吐量低,佇列裡面除了第一個線程,其他的線程都會阻塞,cpu喚醒阻塞線程的開銷大。
非公平鎖:多個執行緒去獲取鎖的時候,會直接去嘗試獲取,獲取不到,再去進入等待隊列,如果能獲取到,就直接獲取到鎖。

優點:可以減少CPU喚醒線程的開銷,整體的吞吐效率會高點,CPU也不必取喚醒所有線程,會減少喚起線程的數量。
缺點:可能導致佇列中間的執行緒一直取得不到鎖定或長時間取得不到鎖,最終餓死。
lock提供了公平鎖和非公平鎖兩種機制(預設為非公平鎖)。

sychronized是非公平鎖。

6、異常是否釋放鎖定

synchronized鎖定的釋放是被動的,當sychronized同步程式碼區塊執行結束或出現異常的時候才會被釋放。

lock鎖定發生異常的時候,不會主動釋放佔有的鎖,必須手動unlock()來釋放,所以我們通常都是將同步程式碼區塊放進try-catch裡面,finally中寫入unlock ()方法,避免死鎖發生。

7、判斷是否能取得鎖定

synchronized不能。

lock提供了非阻塞競爭鎖的方法trylock(),回傳值是Boolean型別。它表示的是用來嘗試取得鎖:成功取得則回傳true;取得失敗則回傳false,這個方法無論如何都會立即回傳。

8、調度方式

synchronized使用的是object物件本身的wait、notify、notifyAll方法,而lock使用的是Condition進行執行緒之間的調度。

9、是否能中斷

synchronized只能等待鎖的釋放,不能回應中斷。

lock等待鎖定過程中可以用interrupt()來中斷。

10、效能

如果競爭不激烈,效能差不多;競爭激烈時,lock的效能會更好。

lock鎖定還能使用readwritelock實現讀寫分離,並提高多執行緒的讀取操作效率。

11、sychronized鎖定升級

synchronized 程式碼區塊是由一對 monitorenter/monitorexit 指令實現的。 Monitor的實作完全是依賴作業系統內部的互斥鎖,因為需要進行使用者態到核心態的切換,所以同步操作是無差別的重量級運算。

所以現在JVM提供了三種不同的鎖:偏向鎖、輕量級鎖、重量級鎖。

偏向鎖定:
當沒有競爭出現時,預設使用偏向鎖定。線程會利用 CAS 操作在物件頭上設定線程 ID ,以表示物件偏向當前線程。

目的:在許多應用場景中,大部分物件生命週期最多會被一個執行緒鎖定,使用偏向鎖定可以降低無競爭時的開銷。

輕量級鎖定:
JVM比較目前執行緒的threadID 和Java 物件頭中的threadID是否一致,如果不一致(例如執行緒2要競爭鎖定物件),那麼需要查看Java 物件頭中記錄的線程1是否存活(偏向鎖不會主動釋放因此還是存儲的線程1的threadID),如果沒有存活,那麼鎖對象還是為偏向鎖(對象頭中的threadID為線程2的);如果存活,那麼撤銷偏向鎖,升級為輕量級鎖。

當有其他執行緒想存取加了輕量級鎖的資源時,會使用自旋鎖優化,來進行資源存取。

目的:競爭鎖定物件的執行緒不多,而且執行緒持有鎖的時間也不長的情境。因為阻塞線程需要CPU從用戶態轉到內核態,開銷大,如果剛剛阻塞不久這個鎖就被釋放了,就得不償失了,因此這個時候就乾脆不阻塞這個線程,讓它自旋這等待鎖釋放。

重量級鎖:
自旋失敗,很大機率 再一次自選也是失敗,因此直接升級成重量級鎖,進行執行緒阻塞,減少cpu消耗。

當鎖升級為重量級鎖定後,未搶到鎖的執行緒都會被阻塞,進入阻塞佇列。

以上是Java多執行緒中Lock怎麼使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除