自己实现可重入锁

  • Post author:
  • Post category:其他




什么是可重入锁

假设现在有一个线程A 他获取了锁,然后他再一次去尝试获取锁,如果能成功,就说这个锁是可以重入的,在java中

ReentrantLock.class

就是一个可重入的锁,关于这个类的源码解析,将在后面的章节讲到,现在我们看一下一个可重入锁的基本使用。



使用ReentrantLock

首先我们写一个简单的例子,这个例子是线程不安全的:

public class NoteSafe {
   private static Integer n = 5;
   public static void main(String[] args) {
       //新建一个线程池
       ExecutorService exe = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
      //在线程池中启动 50个线程
       for (int i = 0; i < 50; i++) {
           exe.execute(() -> {
               try {
                   if (n > 0) {
                       Thread.sleep(500);
                       n--;
                   }
               } catch (Exception e) {
                   e.printStackTrace();
               }
           });
       }
       exe.shutdown();
       while (!exe.isTerminated()){
       }
       System.out.println(n);
   }
}

上面的程序的逻辑就是 ,我们设置了一个参数

n

,然后同时启动50个线程去并发访问这个n,去执行 -1 的操作。

最后查看结果,当然结果肯定就是 一个负数了。

现在我们使用可重入锁来改造这个不安全的类

通过一个创建一个ReentrantLock ,调用他的

lock()

方法,将资源上锁,使用

unlock()

方法进行解锁。

public class NoteSafe {
    private static Integer n = 5;
    public static void main(String[] args) {
        ExecutorService exe = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS, new LinkedBlockingDeque<>());
        ReentrantLock reentrantLock = new ReentrantLock();
        for (int i = 0; i < 50; i++) {
            exe.execute(() -> {
                try {
                    reentrantLock.lock();
                    if (n > 0) {
                        Thread.sleep(500);
                        n--;
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    reentrantLock.unlock();
                }
            });
        }
        exe.shutdown();
        while (!exe.isTerminated()){
        }
        System.out.println(n);
    }
}

这里我们创建了一个 可重入的锁,然后利用lock 锁住,运行的结果一定是0了

可以看到和synchronized 不一样的是

ReentrantLock

需要我们自己手动的上锁和解锁,斜体样式所以在使用的时候应该把解锁也就是

unlock

放入到

finally

的代码块里去,以免出现死锁这点很重要



自己去实现一个可重入锁

通过上面的这个例子我们已经掌握了,可重入锁的使用方法,他的特点就是手动的上锁和解锁。

下面我们将自己去实现一个可重入的锁。

实现Lock 接口

首先我们看一下java中有哪些类实现了这个接口。

在这里插入图片描述

可以看到 我们一些常见的可重入的锁,读写锁还有我们上面用到的ReentrantLock 都实现了这个接口。那么我们也只要去实现这个接口就行。

然后去重写 他的

lock()



unlock()

方法。

具体代码实现

public class DemoReentryLock implements Lock {
    /**
     * 用来标记是否已经有线程获取了锁
     * 是的话 值为true 否则为 false
     */
    private boolean isHold = false;
    /**
     * 用来记录当前获取锁的线程
     */
    private Thread nowHoldThread = null;
    /**
     * 记录锁重入的次数
     */
    private int holdNum = 0;

    @Override
    public synchronized void lock() {
        /**
         * 判断是否已经有线程获取了锁,
         * 如果已经有人获取了锁,再判断当前操作锁的线程是不是获取了锁的线程
         */
        if (isHold && Thread.currentThread() != nowHoldThread) {
            //说明当前操作锁的线程,不是获取锁的线程,将当前线程挂起
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        //没有人获取锁,或者被挂起的线程被唤醒了,将获取锁的线程,变成当前操作锁的线程
        nowHoldThread = Thread.currentThread();
        //将重入的次数加一,同时设置 标志位位true,表示锁已经被获取
        holdNum++;
        isHold = true;
    }

    @Override
    public synchronized void unlock() {
        //解锁的时候,首先判断当前操作的线程是不是获取了锁的线程
        if (Thread.currentThread() == nowHoldThread) {
            //将记录锁重入的次数 -1 ,在判断其值是不是0,是0说明重入的次数已经全部出来了,就可以通知其他线程来获取锁
            holdNum--;
            if (holdNum == 0) {
                isHold = false;
                notify();
            }
        }
    }
  //这里省略了其他要重写的方法,为了节省空间
}

将我们自己实现的锁去替代上一个例子中的,ReentrantLock ,重新运行程序,结果一直是0 ,说明这个锁已经生效

验证我们写的锁是否能重入

到上面为止,我们已经成功实现了一个锁,至于他是不是可重入的,还需要程序验证一下,也很简单

我们只要新建一个线程,在里面上锁后,不要解锁直接再上锁一次,看看会不会报错就可以

public class ReentDemo {
    public static void main(String[] args) {
        DemoReentryLock demoReentryLock = new DemoReentryLock();
        new Thread(() -> {
            System.out.println("我开始上锁了");
            demoReentryLock.lock();
            demoReentryLock.lock();
            demoReentryLock.unlock();
            demoReentryLock.unlock();
            System.out.println("已经全部解锁");
        }).start();

    }
}

运行后程序正常输出,说明我们的锁是可以重入的。



版权声明:本文为zjsxxzh原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。