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即可,不必大费周章定义自己的显示锁。

 

    原文作者:java锁
    原文地址: https://blog.csdn.net/mryang125/article/details/83280548
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞