Java模擬ReentrantLock實現自己的顯示鎖BooleanLock

一、前言

Java通過synchronized關鍵字來爲我們提供線程安全的保證,大多數情況下使用synchronized是沒有問題的,然而synchronized有自身的缺陷。例如:當其它線程持有鎖時,當前請求獲取鎖的線程必須等待。等待的時長是無法控制的,而且等待過程中無法響應中斷。

正是爲了解決synchronized這些的缺陷,Java提供了一個顯示鎖ReentrantLock來實現線程安全,在滿足重入性,獨佔性的條件下,爲我們的加鎖,解鎖和鎖獲取提供了靈活的操作。

ReentrantLock的常用方法如下:

lockInterruptibly():提供響應中斷的鎖獲取操作。

tryLock(long timeout, TimeUtil unit):指定時間內響應中斷和指定時間內的鎖獲取操作。

tryLock():以非阻塞的方法嘗試獲取鎖,無論是否獲取鎖都立刻返回,當成功獲取鎖返回true,否則返回false。

new ReentrantLock(boolean):構造方法可指定鎖獲取的公平性,即先申請獲得鎖的線程將先獲得鎖。synchronized是非公平的。

lock():以阻塞的形式獲取鎖,和使用synchronized一樣的效果。

unlock():解鎖。

isHeldByCurrentThread():持有鎖的線程是否爲當前線程。

本文將以ReentrantLock的這些特性爲目標,通過synchronized+wait+notify來模擬一個自己的顯示鎖。代碼靈感來源於 汪文君 老師的一書《高併發編程詳解:多線程與架構設計》。

二、代碼部分

1.定義自己的顯示鎖,它是一個接口,滿足的功能參照備註:

import java.util.List;
import java.util.concurrent.TimeoutException;

public interface MyLock {

    /**
     * 實現可中斷的鎖獲取操作
     *
     * @throws InterruptedException
     */
    void lock() throws InterruptedException;

    /**
     * 實現限時的鎖獲取操作
     *
     * @param timeMillis
     * @throws InterruptedException
     * @throws TimeoutException
     */
    void lock(long timeMillis) throws InterruptedException, TimeoutException;

    /**
     * 解鎖
     */
    void unLock();

    /**
     * 返回等待獲取鎖的線程集合
     *
     * @return
     */
    List<Thread> getBlockedThreads();

    /**
     * 嘗試獲取鎖,獲取成功立刻返回true,否則返回false
     *
     * @return
     */
    boolean tryLock();

    /**
     * 判斷當前線程是否持有鎖
     *
     * @return
     */
    boolean isHeldByCurrentThread();
}

MyLock接口模擬了ReentrantLock的部分方法:MyLock的lock()方法模擬ReentrantLock的lockInterruptibly(),MyLock的lock(long timeMillis)方法模擬ReentrantLock的tryLock(long timeout, TimeUtil unit),MyLock的其餘方法模擬ReentrantLock的同名方法。

2.定義BooleanLock,作爲MyLock的實現類,代碼如下:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeoutException;

public class BooleanLock implements MyLock {

    private boolean locked;
    private Thread currentThread; //當前持有鎖的線程
    private final List<Thread> blockedThreads = new ArrayList<>(); //等待獲取鎖的線程

    @Override
    public void lock() throws InterruptedException {
        synchronized (this) {
            while (locked) {
                if (!blockedThreads.contains(Thread.currentThread())) {
                    blockedThreads.add(Thread.currentThread());
                }
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    blockedThreads.remove(Thread.currentThread());
                    throw e;
                }
            }
            locked = true;
            currentThread = Thread.currentThread();
            blockedThreads.remove(currentThread);
        }
    }

    @Override
    public void lock(long timeMillis) throws InterruptedException, TimeoutException {
        synchronized (this) {
            if (timeMillis <= 0) {
                this.lock();
            } else {
                long remainMills = timeMillis;
                long endTime = System.currentTimeMillis() + remainMills; //截至時刻
                while (locked) {
                    if (remainMills <= 0) { //剩餘等待時間
                        throw new TimeoutException("cannot get lock during " + remainMills);
                    }
                    if (!blockedThreads.contains(Thread.currentThread())) {
                        blockedThreads.add(Thread.currentThread());
                    }
                    this.wait(remainMills); //等待一定時間
                    remainMills = endTime - System.currentTimeMillis(); //重新計算等待時間:當前時間點與while執行之前的時間點之差。
                }
                locked = true;
                currentThread = Thread.currentThread();
                blockedThreads.remove(currentThread);
            }
        }
    }

    @Override
    public void unLock() {
        synchronized (this) {
            if (currentThread == Thread.currentThread()) {
                locked = false;
                this.notifyAll();
            }
        }
    }

    @Override
    public List<Thread> getBlockedThreads() {
        return Collections.unmodifiableList(blockedThreads);
    }

    @Override
    public boolean tryLock() {
        synchronized (this) {
            if (!locked) {
                locked = true;
                currentThread = Thread.currentThread();
                blockedThreads.remove(currentThread);
                return true;
            }
            return false;
        }
    }

    @Override
    public boolean isHeldByCurrentThread() {
        synchronized (this) {
            if (Thread.currentThread() == currentThread) {
                return true;
            }
            return false;
        }
    }
}

三.測試代碼

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class TestMyLock {

    private Random random = new Random();
    private MyLock myLock = new BooleanLock();

    public void syncMethod() {
        try {
            myLock.lock();
            int rand = random.nextInt(10);
            System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
            TimeUnit.SECONDS.sleep(rand);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }

    public void syncTryMethod() {
        try {
            if (myLock.tryLock()) {
                int rand = random.nextInt(10);
                System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
                TimeUnit.SECONDS.sleep(rand);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }

    public void syncMethodOut() {
        try {
            myLock.lock(1000);
            int rand = random.nextInt(10);
            System.out.println(Thread.currentThread().getName() + " hold lock, and will sleep " + rand + " seconds");
            TimeUnit.SECONDS.sleep(rand);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (TimeoutException e) {
            e.printStackTrace();
        } finally {
            if (myLock.isHeldByCurrentThread()) {
                myLock.unLock();
                System.out.println(Thread.currentThread().getName() + " release lock");
            }
        }
    }


    static void testSync() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncMethod();
            }, i + "");
            thread.start();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testMyLock.myLock.getBlockedThreads());
    }

    static void testTrySync() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncTryMethod();
            }, i + "");
            thread.start();
        }
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(testMyLock.myLock.getBlockedThreads());
    }

    static void testInterrupt() {
        final TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                testMyLock.syncMethod();
            }, i + "");
            thread.start();
            if (i % 2 == 0) {
                thread.interrupt();
            }
        }
    }

    static void testTimeOut() throws InterruptedException {
        TestMyLock blt = new TestMyLock();
        new Thread(blt::syncMethod, "Tl").start();
        TimeUnit.MILLISECONDS.sleep(2);
        Thread t2 = new Thread(blt::syncMethodOut, "T2");
        t2.start();

    }

    public static void main(String[] args) throws InterruptedException {
        testSync();
//        testTrySync();
//        testInterrupt();
//        testTimeOut();
    }
}

1.運行testSync(),看到控制檯有條不紊的打印出加鎖和解鎖的操作,說明MyLock實現了線程同步的功能。

0 hold lock, and will sleep 5 seconds
[Thread[1,5,main], Thread[2,5,main], Thread[5,5,main], Thread[4,5,main], Thread[3,5,main], Thread[6,5,main], Thread[7,5,main], Thread[8,5,main], Thread[9,5,main]]
0 release lock
9 hold lock, and will sleep 3 seconds
9 release lock
1 hold lock, and will sleep 3 seconds
1 release lock
8 hold lock, and will sleep 8 seconds
8 release lock
2 hold lock, and will sleep 6 seconds
2 release lock
7 hold lock, and will sleep 6 seconds
7 release lock
5 hold lock, and will sleep 8 seconds
5 release lock
6 hold lock, and will sleep 5 seconds
6 release lock
4 hold lock, and will sleep 3 seconds
4 release lock
3 hold lock, and will sleep 7 seconds
3 release lock

2.運行testTrySync(),控制檯打印如下,可以看出使用tryLock時,若線程0持有鎖,其它線程無法獲取鎖,故等待獲取鎖的集合爲空集合。

0 hold lock, and will sleep 4 seconds
[]
0 release lock

3.運行testInterrupt(),控制檯打印如下

java.lang.InterruptedException
0 hold lock, and will sleep 8 seconds
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
0 release lock
	at BooleanLock.lock(BooleanLock.java:20)
9 hold lock, and will sleep 4 seconds
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at TestMyLock.syncMethod(TestMyLock.java:15)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
java.lang.InterruptedException
	at java.lang.Object.wait(Native Method)
	at java.lang.Object.wait(Object.java:502)
	at BooleanLock.lock(BooleanLock.java:20)
	at TestMyLock.syncMethod(TestMyLock.java:12)
	at TestMyLock.lambda$testInterrupt$2(TestMyLock.java:98)
	at java.lang.Thread.run(Thread.java:745)
9 release lock
1 hold lock, and will sleep 1 seconds
1 release lock
7 hold lock, and will sleep 0 seconds
7 release lock
3 hold lock, and will sleep 7 seconds
3 release lock
5 hold lock, and will sleep 6 seconds
5 release lock

這裏的打印有點亂,要注意e.printStackTrace方法本質上是調用System.err對象的println方法,而System.out.println方法屬於System.out對象,故它們是屬於不同的對象,也就是線程異步的!可以看出0,2,4,6線程都響應了中斷!

4.運行testTimeOut(),控制檯打印如,可以看出當線程T1已經獲取鎖,並且睡眠3s,線程T2嘗試在1s內獲取鎖,結果失敗。

Tl hold lock, and will sleep 3 seconds
java.util.concurrent.TimeoutException: cannot get lock during 0
	at BooleanLock.lock(BooleanLock.java:42)
	at TestMyLock.syncMethodOut(TestMyLock.java:47)
	at java.lang.Thread.run(Thread.java:745)
Tl release lock

四、小結

本文演示的案例僅僅是爲了更好的理解顯示鎖特有的性質,ReentrantLock自身的實現運用了大量的AQS和CAS操作,其細節和原理遠比本文複雜的多。實際項目開發我們直接使用ReentrantLock即可,不必大費周章定義自己的顯示鎖。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章