天天看點

java多線程學習 ReentrantLock類的使用

ReentrantLock類

為了保證任何時刻隻有一個線程能進入臨界區,通常需要給臨界區上鎖,隻有獲得鎖的線程才能進入臨界區。為了達到上鎖的目的,我們通常使用synchronized關鍵字。

在Java SE 5.0之後,java引入了一個ReentrantLock類,也可以實作給代碼塊上鎖和釋放鎖的效果。

lock方法 和unlock方法

  • lock() 申請獲得鎖
    • 如果獲得鎖,該線程可以繼續往下執行
    • 如果該鎖已被其他線程擷取,目前線程停止運作并進入阻塞狀态,等待其他線程釋放鎖
  • unlock() 釋放鎖
  • ReentrantLock和synchronized鎖一樣是可重入鎖,
    • 一個線程可以重複的獲得已經持有的鎖,鎖保持一個持有計數(holdcount) 來跟蹤對 lock 方法的嵌套調用。
    • 線程在每一次調用 lock 都要調用 unlock 來釋放鎖。
    • 由于這一特性, 被一個可重入鎖保護的代碼可以調用另一個使用相同的鎖的方法。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class myThread9 {
    //ReentrantLock類實作了Lock接口
    private static final Lock lock = new ReentrantLock();
    private int count = 0;

    public int getCount() {
        return count;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public void add() {
        lock.lock();//申請獲得鎖
        try {
            //一般為了安全,一般用try把同步代碼塊的内容包裹起來
            //保證即使目前線程運作過程中出現異常,也能正常釋放鎖
            for (int i = 0; i < 10000; i++) {
                count++;
            }
        } finally {
            //一般在finally裡釋放鎖
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        myThread9 m = new myThread9();
        List<Thread> threads = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            //建立10個線程,加入threads中
            Thread thread = new Thread(() -> {
                m.add();
            }, "thread-" + i);
            threads.add(thread);
        }
        //逐一啟動線程
        threads.forEach((thread -> thread.start()));
        //在主線程中利用join方法,等待所有方法完成
        threads.forEach(thread -> {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        //所有子線程運作結束,列印count值
        System.out.println(m.getCount());
    }
}
           
java多線程學習 ReentrantLock類的使用

trylock方法(限時等待)

  • trylock():嘗試獲得鎖,如果獲得鎖,傳回true.如果沒有獲得鎖,傳回false(目前線程不會進入阻塞狀态)
  • trylock(long time,TimeUnit unit):在指定時間時間内嘗試獲得鎖,如果成功擷取鎖,傳回true,如果沒有鎖,傳回false(目前線程不會進入阻塞狀态)
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class myThread9_1{
    //ReentrantLock類實作了Lock接口
    private static final Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        myThread9_1 m = new myThread9_1();
        //啟動第一個線程,執行add方法
        new Thread(()->m.add(),"t1").start();
        //過1s後啟動第二個線程執行testTryLock,確定第一個線程能先啟動獲得鎖
        Thread.sleep(1000);
        new Thread(()->m.testTryLock(),"t2").start();
    }

    public void add(){
        lock.lock();
        try {
            //該線程獲得鎖後需要5s才能運作完畢釋放鎖
        for (int i = 0; i <5 ; i++) {
            System.out.println(Thread.currentThread().getName()+":目前i的值為"+i);
            Thread.sleep(1000);
        }}catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void testTryLock(){
        boolean flag=false;
        try {
            //可以嘗試不同等待時間對比運作結果
            flag = lock.tryLock();//嘗試立即獲得鎖
 //          flag = lock.tryLock(2,TimeUnit.SECONDS);//等待2s,嘗試獲得鎖
//           flag = lock.tryLock(6,TimeUnit.SECONDS);//等待6s,嘗試獲得鎖
            if (flag){
                //獲得鎖
                System.out.println(Thread.currentThread().getName()+"獲得鎖,執行同步代碼塊裡内容");
            }else {
                System.out.println(Thread.currentThread().getName()+"沒有在規定時間内獲得鎖,執行非同步代碼塊内容");
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (flag){
                //如過獲得鎖,要釋放鎖
                lock.unlock();
            }
        }
    }
}
           
java多線程學習 ReentrantLock類的使用

公平鎖

含義:誰等的時間最長,誰就先擷取鎖。

預設情況下,ReentrantLock是非公平鎖,可以通過構造方法參數把ReentrantLock聲明為公平鎖

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

public class myThread9_2 {
    //在new ReentrantLock時傳入true就可以把lock設定為公平鎖(預設時false)
    private static final Lock lock = new ReentrantLock(true);

    public void test() {
        for (int i = 0; i < 3; i++) {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "獲得鎖");
                Thread.sleep(1000);

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        myThread9_2 m = new myThread9_2();
        for (int i = 1; i <= 5; i++) {
            //m::test等價于()->m.test();
            new Thread(m::test, "線程" + i).start();
        }

    }
}
           
java多線程學習 ReentrantLock類的使用
java多線程學習 ReentrantLock類的使用

ReentrantLock和synchronized的對比

  • ReentrantLock和synchronized都是獨占鎖(排他鎖),一個時刻都隻能有一個線程持有該鎖
  • ReentrantLock和synchronized都是可重入鎖,一個線程可以重複的獲得已經持有的鎖
  • synchronized可以自動獲得鎖和釋放鎖,ReentrantLock需要手動獲得鎖并手動在finally裡釋放鎖
  • 在性能上,ReentrantLock是輕量級鎖;但jdk 1.5之後,synchronized加入了鎖更新的概念(偏向鎖,輕量級鎖,重量級鎖),是以二者性能相當.
  • synchronized不可響應中斷,一個線程擷取不到鎖就一直等着;ReentrantLock可以響應中斷