使用synchronized和wait实现一个显示锁Lock

2020-01-15  本文已影响0人  herohua

首先明确一次显示锁Lock需要的功能:

  1. 线程加锁;
  2. 线程超时加锁,超时报超时异常;
  3. 线程解锁;
  4. 获取阻塞的线程集合;
  5. 获取阻塞的线程个数。
    根据以上需求定义Lock接口:
public interface Lock {

    /**
     * 超时异常
     */
    class TimeoutException extends Exception {
        public TimeoutException(String message) {
            super(message);
        }
    }

    void lock() throws InterruptedException;

    void lock(long mills) throws InterruptedException, TimeoutException;

    void unlock();

    /**
     * 获取阻塞的线程集合
     */
    Collection<Thread> getBlockedThread();

    /**
     * 获取阻塞的线程个数
     */
    int getBlockedSize();
}

定义一个保存线程的队列和一个当前锁状态的标志:

/**
 * initValue=true:表示锁已经被某线程占用
 * initValue=false:表示锁没有被某线程占用,其它线程可以争抢
 */
private boolean initValue;

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

加锁功能的实现:

首先判断锁的状态,如果可以加锁,从阻塞队列中移除当前线程,将锁的状态置为true,如果锁已经被其它线程还在占用,则将当前线程加入到阻塞队列,并且调用wait方法阻塞当前线程。

@Override
public synchronized void lock() throws InterruptedException {
    while (initValue) {
        // 如果锁已经被占用,就将当前线程加入到阻塞队列,进入等待状态
        blockedThreadCollection.add(Thread.currentThread());
        wait();
    }
    // 如果获取到锁,从阻塞队列中移除当前线程,锁标志置为true
    blockedThreadCollection.remove(Thread.currentThread());
    Optional.of(Thread.currentThread().getName() + " get the lock monitor.").ifPresent(System.out::println);
    initValue = true;
    // 防止unlock和lock的不是同一个线程
    this.currentThread = Thread.currentThread();
}

超时加锁功能的实现:

如果传入的时间不是正数,直接抛出加锁超时异常。如果当前锁已经被其它线程占用,则wait传入的时间。如果抢占到锁,则变更锁的状态,并将当前上锁的线程保存到一个变量中,释放锁的时候需要校验。

@Override
public synchronized 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(Thread.currentThread().getName() + "get lock time out");
        }
        blockedThreadCollection.add(Thread.currentThread());
        this.wait(mills);
        hasRemaining = endTime - System.currentTimeMillis();
    }
    //blockedThreadCollection.remove(Thread.currentThread());
    initValue = true;
    this.currentThread = Thread.currentThread();
}

释放锁功能的实现:

这里需要注意的是,当锁已经被线程A占有,线程B是不能调用 unlock的,只能被线程A unlock,所以需要定义一个变量currentThread保存当前占有锁的线程,在释放锁的时候进行校验。

// 保存当前持有锁的线程
private Thread currentThread;

@Override
public synchronized void unlock() {
    // unlock保护,其它线程不能解锁加锁的线程
    if (currentThread == Thread.currentThread()) {
        initValue = false;
        Optional.of(Thread.currentThread().getName() + " release the lock monitor.").ifPresent(System.out::println);
        notifyAll();    // 唤醒其它线程
    }
} 

功能测试:

public class LockTest {

    public static void main(String[] args) throws InterruptedException {
        BooleanLock lock = new BooleanLock();

        Stream.of("T1", "T2", "T3", "T4")
                .forEach(name -> new Thread(() -> {
                    try {
                        lock.lock(100L);
                        work();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (Lock.TimeoutException e) {
                        Optional.of(Thread.currentThread().getName() + " get lcok time out!").ifPresent(System.out::println);
                    } finally {
                        lock.unlock();
                    }

                }, name).start());

        // 测试其它线程是否能unlock加锁线程
        // lock、unlock方法里应该做保护
        //Thread.sleep(100);
        //lock.unlock();

    }

    private static void work() throws InterruptedException {
        Optional.of(Thread.currentThread().getName() + " is working...").ifPresent(System.out::println);
        Thread.sleep(10_000);
    }
}
  1. 加锁测试结果:


    加锁测试结果.png
  2. 超时加锁测试结果


    超时加锁测试结果.png
上一篇 下一篇

猜你喜欢

热点阅读