如何实现一个自己的显式锁Lock

如何实现一个自己的显式锁Lock

首先我们通过代码引出一个问题:如果方法上了同步锁并且被一个线程占有了,那么其他线程想执行这个方法就要一直等待下去,如果这个方法死循环或者无限执行下去,等待的线程就会无限等待下去,这里来演示一下这种场景:

/**
 * @program: ThreadDemo
 * @description: synchronized机制导致的一个问题:synchronized不能被打断,导致其他线程抢不到锁。
 * @author: hs96.cn@Gmail.com
 * @create: 2020-09-14
 */
public class SynchronizedProblem {
    public static void main(String[] args) {
        new Thread(SynchronizedProblem::run, "T1").start();
        new Thread(SynchronizedProblem::run, "T2").start();
    }

    private synchronized static void run() {
        System.out.println(Thread.currentThread().getName());
        while (true) {
            //一直执行下去。。。
        }
    }
}

运行效果如下:

使用interrupt()打断一下T2线程:

public class SynchronizedProblem {
    public static void main(String[] args) throws InterruptedException {
        new Thread(SynchronizedProblem::run, "T1").start();
        Thread.sleep(1000);
        Thread t2 = new Thread(SynchronizedProblem::run, "T2");
        t2.start();

        Thread.sleep(2000);
        t2.interrupt();
        System.out.println(t2.isInterrupted());// true,但是并没有打断t2
    }

    private synchronized static void run() {
        System.out.println(Thread.currentThread().getName());
        while (true) {
            //一直执行下去
        }
    }
}

运行效果如下:

因为同步方法里有while循环,接收不到中断异常的,所以基于这个场景我们来定义一个带有超时功能的锁,思路还是和上一个案例差不多,一个while一直检测lock,再结合wait(),notifyAll(),代码如下:

我们先定义一个Lock interface:

package com.thread.thread20;

import java.util.Collection;

public interface Lock {
    class TimeOutException extends Exception {
        public TimeOutException(String message) {
            super(message);
        }
    }

    /**
     * 加锁
     *
     * @throws InterruptedException 打断异常
     */
    void lock() throws InterruptedException;

    /**
     * 加锁
     *
     * @param mills 加锁时间
     * @throws InterruptedException 打断异常
     * @throws TimeOutException     超时异常
     */
    void lock(long mills) throws InterruptedException, TimeOutException;

    /**
     * 解锁
     */
    void unlock();

    /**
     * 获取争抢锁时被阻塞的线程
     *
     * @return 被阻塞线程的集合
     */
    Collection<Thread> getBlockedThread();

    /**
     * 获取争抢锁时被阻塞的线程的数量
     *
     * @return 被阻塞的线程的数量
     */
    int getBlockedSize();
}

定义一个类来实现接口:

package com.thread.thread20;

import java.util.Collection;

/**
 * @program: ThreadDemo
 * @description: 自定义显式锁 Boolean Lock
 * @author: hs96.cn@Gmail.com
 * @create: 2020-09-14
 */
public class BooleanLock implements Lock {
    @Override
    public void lock() throws InterruptedException {
        
    }

    @Override
    public void lock(long mills) throws InterruptedException, TimeOutException {

    }

    @Override
    public void unlock() {

    }

    @Override
    public Collection<Thread> getBlockedThread() {
        return null;
    }

    @Override
    public int getBlockedSize() {
        return 0;
    }
}

我们来定义两个成员变量:

initValuetrue代表正在被占用,false反之。

blockedThreadCollection来存放阻塞的线程。

private boolean initValue;

private Collection<Thread> blockedThreadCollection = new ArrayList<>();

public BooleanLock() {
    this.initValue = true;
}

完善lock方法:

@Override
public synchronized void lock() throws InterruptedException {
    // 锁已经被其他线程使用
    while (initValue) {
        blockedThreadCollection.add(Thread.currentThread());
        this.wait();
    }
    // 锁未被使用,抢到锁立即设置initValue的值
    this.initValue = true;
    blockedThreadCollection.remove(Thread.currentThread());
}

这里和上一篇的数据采集很相似。

完善unlock方法:

@Override
public void unlock() {
    this.initValue = false;
    Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println);
    this.notifyAll();
}

查询方法完善如下:

@Override
public Collection<Thread> getBlockedThread() {
	return Collections.unmodifiableCollection(blockedThreadCollection);
}
@Override
public int getBlockedSize() {
    return blockedThreadCollection.size();
}

returnCollections一定使用unmodifiableCollection因为直接返回的是一个实例,调用者可以随意更改。所以我们把他置为·unmodifiableCollection

测试一下代码:

package com.thread.thread20;

import java.util.Optional;
import java.util.stream.Stream;

/**
 * @program: ThreadDemo
 * @description: 用来调用我们自己写的显示Lock
 * @author: hs96.cn@Gmail.com
 * @create: 2020-09-14
 */
public class LockTest {
    public static void main(String[] args){
        final BooleanLock booleanLock = new BooleanLock();
        Stream.of("T1", "T2", "T3", "T4").forEach(name -> {
            new Thread(() -> {
                try {
                    booleanLock.lock();
                    Optional.of(Thread.currentThread().getName() + " have the lock Monitor.").ifPresent(System.out::println);
                    work();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    booleanLock.unlock();
                }
            }, name).start();
        });
    }

    private static void work() throws InterruptedException {
        Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println);
        Thread.sleep(2_000);
    }
}

运行效果如下:

可以看到报错了,这里为了印象深刻一下,特意留了个彩蛋,其实上面的代码里我也故意留了彩蛋~~。notifyAll需要一个monitor才可以,我们把synchronized加上运行效果如下:

嗯到这里大体效果实现了,但是细想还是有问题的,如果要你编写一个这个方法的测试用例,你会怎么写呢?

unlock()没做任何限制,我们的测试类是可以随意调用的:

try {
    Thread.sleep(100);
} catch (InterruptedException e) {
    e.printStackTrace();
}
booleanLock.unlock();

运行效果如下:

T1线程还工作,T4直接就抢到锁了,那么我们在这个基础上再做一层校验:

private Thread currentThread; //增加一个currentThread代表当前拿到线程的锁
currentThread = Thread.currentThread();
@Override
public synchronized void unlock() {
    // 释放锁
    if (Thread.currentThread() == currentThread) {
        this.initValue = false;
        Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println);
        this.notifyAll();
    }
}

再运行一下:

以这个为主体,增加时间限制来完善我们的Lock:

public static void main(String[] args) throws InterruptedException {
    final BooleanLock booleanLock = new BooleanLock();
    Stream.of("T1", "T2", "T3", "T4").forEach(name -> {
        new Thread(() -> {
            try {
                booleanLock.lock(2000L);
                Optional.of(Thread.currentThread().getName() + " have the lock Monitor.").ifPresent(System.out::println);
                work();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (Lock.TimeOutException e) {
                Optional.of(Thread.currentThread().getName() + " time out.").ifPresent(System.out::println);
            } finally {
                booleanLock.unlock();
            }
        }, name).start();
    });
}
private static void work() throws InterruptedException {
    Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println);
    Thread.sleep(5_000);
}
@Override
public void lock(long mills) throws InterruptedException, TimeOutException {
    if (mills <= 0) {
        lock();
    }
    long hasRemaining = mills;
    long endTime = System.currentTimeMillis() + mills;
    while (initValue) {
        if (hasRemaining <= 0) {
            throw new TimeOutException("Time Out");
        }
        blockedThreadCollection.add(Thread.currentThread());
        this.wait(mills);
        hasRemaining = endTime - System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName() + ">>" + hasRemaining);
    }
    this.initValue = true;
    this.currentThread = Thread.currentThread();
}

运行效果如下:

T1线程进来抢到锁,但是需要5秒才能运行结束,其他线程最多等待2秒,所以其他线程先结束等待超时了,T1才释放锁。这个显式锁就先到这,上面代码我故意贴了一些错误的,不知道细心的朋友有没有发现=.=

posted @ 2020-09-14 00:49  风暴松鼠  阅读(221)  评论(0)    收藏  举报