天天看點

淺談synchronized和ReentrantLock的差別

文章首發于個人部落格,歡迎通路關注:https://www.lin2j.tech

本文會簡單對比一下

synchronized

關鍵字和

Lock

的差別,不會講到底層原理。

synchronized

Lock

有什麼差別?使用Lock有什麼好處,舉例說明。

  1. 底層結構不同(所屬層面

    JVM

    vs

    API

  • synchronized

    是關鍵字,屬于

    JVM

    層面的。
    • monitorenter

      (底層是通過

      monitor

      對象來完成,其實

      await

      notify

      方法都依賴于

      monitor

      對象,

      await

      notify

      隻能再同步塊或者同步方法調用 )
    • monitorexit

  • Lock

    是具體的類,屬于

    API

    層面的鎖。
  1. 使用方法不同(手動釋放和自動釋放的差別)
  • synchronized

    不需要手動釋放鎖,當

    synchronized

    代碼執行完成以後,系統會自動讓線程釋放對所得占用。
  • ReentrantLock

    則需要使用者去手動釋放鎖,如果沒有主動釋放鎖,就有可能導緻出現死鎖現象。需要

    lock()

    unlock()

    方法配合

    try/finally

    語句塊來完成。
  1. 等待是否可中斷 (可否中斷)
  • synchronized

    不可中斷,除非抛異常或者正常運作完成。
  • ReentrantLock

    可中斷:
    • 設定逾時方法

      tryLock(long timeout, TimeUnit unit)

    • lockInterruptibly()

      放代碼塊中,調用

      interrupt()

      方法可中斷。
  1. 加鎖是否公平(公平鎖和非公平鎖)
  • Synchronized

    是非公平鎖。
  • ReentrantLock

    兩者都可以,預設非公平鎖,構造方法可以傳入

    boolean

    值,

    true

    為公平鎖,

    false

    為非公平鎖。
  1. 鎖綁定多個條件(精确喚醒)
  • Synchronized

    沒有
  • ReentrantLock

    用來實作分組喚醒需要喚醒的線程們,可以精确喚醒,而不是像

    synchronized

    ,要麼随機喚醒一個線程,要麼喚醒全部線程。

    newCondition()

    方法。

ReentrantLock

多條件精确控制線程 Demo

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author linjinjia [email protected]
 * @date 2021/3/30 21:38
 */
public class SyncAndReentrantLockDemo {
    public static void main(String[] args) {
        ShareResource shareResource = new ShareResource();

        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                shareResource.print1();
            }
        }, "AA").start();
        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                shareResource.print2();
            }
        }, "BB").start();
        new Thread(()->{
            for (int i = 0; i < 3; i++) {
                shareResource.print3();
            }
        }, "CC").start();
    }
}

/**
 * 多線程之間按照順序調用,實作 A->B->C三個線程啟動,要求如下:
 * AA 列印 1 次,BB 列印 2次,CC列印 3次
 * 來3輪
 */
class ShareResource {
    /**
     * flag = 1,線程AA啟動
     * flag = 2,線程BB啟動
     * flag = 3,線程CC啟動
     */
    private int flag = 1;
    private Lock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print1(){
        lock.lock();
        try{
            // 1. 判斷
            while(flag != 1){
                c1.await();
            }
            // 2. 幹活
            print(Thread.currentThread().getName(), 1);
            // 3. 通知
            flag = 2;
            c2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print2(){
        lock.lock();
        try{
            // 1. 判斷
            while(flag != 2){
                c2.await();
            }
            // 2. 幹活
            print(Thread.currentThread().getName(), 2);
            // 3. 通知
            flag = 3;
            c3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void print3(){
        lock.lock();
        try{
            // 1. 判斷
            while(flag != 3){
                c3.await();
            }
            // 2. 幹活
            print(Thread.currentThread().getName(), 3);
            // 3. 通知
            flag = 1;
            c1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    private void print(String name, int count){
        for(int i = 0; i < count; i++){
            System.out.println(name + "\t " + i);
        }
    }
}
           

輸出

AA	 0
BB	 0
BB	 1
CC	 0
CC	 1
CC	 2
AA	 0
BB	 0
BB	 1
CC	 0
CC	 1
CC	 2
AA	 0
BB	 0
BB	 1
CC	 0
CC	 1
CC	 2
           

繼續閱讀