JDK中可以使用ReentrantLock类来实现可重入锁,其功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。下面来自己实现以下可重入锁。原理一样。
2. ReentrantLock
- 不可重入锁
不可重入锁,锁不可延续使用。

public class LockTest1 {
    Lock lock=new Lock();
    public void a() throws InterruptedException {
        lock.lock();
        doSomething();
        lock.unlock();
    }

    //不可重入锁
    public void doSomething() throws InterruptedException {
        lock.lock();
        //
        lock.unlock();
    }

    public static void main(String[] args) throws InterruptedException {
        LockTest1 test1=new LockTest1();
        test1.a();
        test1.doSomething();
    }
}
class Lock{
    //是否占用
    private boolean isLock=false;
    //使用锁
    public synchronized  void lock() throws InterruptedException {
        while (isLock){
            wait();
        }
        isLock=true;
    }
    //释放锁
    public synchronized  void unlock(){
        isLock=false;
        notify();
    }
}

如此代码中,方法a()首先得到得到锁,然后调用方法doSomething(),方法doSomething()中有lock.lock(),也需要上锁,然后就等待a()方法执行完,方法a()必须执行完doSomething()方法才会运行下一条语句unlock()方法释放锁,同时方法doSomething()等方法a()运行完释放锁,就形成了死循环。永远不会停止,永远等待。上述就是不可重入锁。

- 可重入锁
锁可以延续使用

public class LockTest2 {
    ReLock lock=new ReLock();
    public void a() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        lock.lock();
        System.out.println(lock.getHoldCount());
        doSomething();
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }
    //不可重入锁
    public void doSomething() throws InterruptedException {
        System.out.println(Thread.currentThread().getName());
        lock.lock();
        System.out.println(lock.getHoldCount());
        //
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }
    public static void main(String[] args) throws InterruptedException {
        LockTest2 test1=new LockTest2();
        test1.a();
        Thread.sleep(1000);
        System.out.println(test1.lock.getHoldCount());
    }
}

class ReLock{
    //是否占用
    private boolean isLock=false;
    Thread lockedBy=null;//储存线程//如果自身就不等了
    private int holdCount=0;//计数器

    public int getHoldCount() {
        return holdCount;
    }
    //使用锁
    public synchronized  void lock() throws InterruptedException {
        Thread thread = Thread.currentThread();//加锁时识别当前线程
        System.out.println(Thread.currentThread().getName());
        while (isLock &&lockedBy!=thread){//不是自己的线程就等待
            wait();
        }
        isLock=true;
        lockedBy=thread;
        holdCount++;
    }
    //释放锁
    public synchronized  void unlock(){
        System.out.println(Thread.currentThread().getName());
        if(Thread.currentThread()==lockedBy){//当前线程等于自身的时候释放
            holdCount--;//计数器减一
            if(holdCount==0) {
                isLock = false;
                notify();
                lockedBy=null;
            }
        }
    }
}

运行结果:

java 实现不可重入_System


可重入锁中引入了计数器,上锁就会加一,解除锁减一,直到计数器为零,完全释放锁。

  • 上锁的时候先识别当前的线程,一开始isLock为false,则不进行while循环,下一步isLock改为true,计数器加一,此时就完成了第一次上锁;等第二次上锁的时候,isLock已经是true了,此时判断线程是不是当前线程,是当前线程则继续上锁,计数器加一,不用等待。后面的同理。在此例子中通过输出当前线程名,都是main线程,线程相同。所以可以完成加锁减锁。
  • 释放锁的时候同理判断是否是当前锁,是则计数器减一,释放锁,并通过notify()方法唤醒被wait()等待的线程,将lockBy置为空。

它和不可重入锁的设计不同之处:

  • 不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待。实现简单
  • 可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。
  • 设计了加锁次数,以在解锁的时候,可以确保所有加锁的过程都解锁了,其他线程才能访问。不然没有加锁的参考值,也就不知道什么时候解锁?解锁多少次?才能保证本线程已经访问完临界资源了可以唤醒其他线程访问了。实现相对复杂。