可重入锁

作者: 大海孤了岛 | 来源:发表于2017-05-08 22:05 被阅读129次

    定义

    可重入锁,也叫做递归锁,指的是在同一线程内,外层函数获得锁之后,内层递归函数仍然可以获取到该锁。换一种说法:同一个线程再次进入同步代码时,可以使用自己已获取到的锁。

    作用

    防止在同一线程中多次获取锁而导致死锁发生。

    如下,我们通过自旋锁来判断是否会发生死锁:

    public class SpinLock {
        private AtomicReference<Thread> sign = new AtomicReference<>();
        public void lock(){
            Thread current = Thread.currentThread();
            while (!sign.compareAndSet(null,current)){
            }
        }
        public void unlock(){
            Thread cur = Thread.currentThread();
            sign.compareAndSet(cur,null);
        }
    }
    
    public class SpinLockDemo {
        private SpinLock lock =  new SpinLock();
        class Widget{
            public void doSomething(){
                lock.lock();
                System.out.println("Widget calling doSomething");
                lock.unlock();
            }
        }
    
        class LoggingWidget extends Widget {
            @Override
            public void doSomething() {
                lock.lock();
                System.out.println("LoggingWidget calling doSomething");
                super.doSomething();
                lock.unlock();
            }
        }
    
        public static void main(String[] args){
            SpinLockDemo spinLockDemo = new SpinLockDemo();
            SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget();
            widget.doSomething();
        }
    }
    
    输出结果:
    LoggingWidget calling doSomething
    

    我们可以看到在LoggingWidget类中doSomething方法时,通过锁进入临界区,并在临界区中调用了父类的该方法,而父类的方法要获取到同一个锁,被阻塞,导致死锁发生。

    常见的可重入锁

    • Synchronized关键字:
    public class SynchronizedDemo {
         class Widget{
            public synchronized void doSomething(){
                System.out.println("Widget calling doSomething...");
            }
        }
    
         class LoggingWidget extends Widget{
            @Override
            public synchronized void doSomething() {
                System.out.println("LoggingWidget calling doSomething");
                super.doSomething();
            }
        }
    
        public static void main(String[] args){
             SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
            SynchronizedDemo.Widget widget = synchronizedDemo.new LoggingWidget();
            widget.doSomething();
        }
    }
    
    输出结果:
    LoggingWidget calling doSomething
    Widget calling doSomething...
    

    根据结果,我们可以看到Synchronized关键字是可重入锁。

    • ReetrantLock:
    public class ReentrantLockDemo {
        private Lock lock =  new ReentrantLock();
        class Widget{
            public void doSomething(){
                lock.lock();
                System.out.println("Widget calling doSomething");
                lock.unlock();
            }
        }
    
        class LoggingWidget extends Widget {
            @Override
            public void doSomething() {
                lock.lock();
                System.out.println("LoggingWidget calling doSomething");
                super.doSomething();
                lock.unlock();
            }
        }
    
        public static void main(String[] args){
            ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
            Widget widget = reentrantLockDemo.new LoggingWidget();
            widget.doSomething();
        }
    
    }
    
    输出结果:
    LoggingWidget calling doSomething
    Widget calling doSomething
    

    根据结果,我们可以看出ReetrantLock锁时可重入的。

    实现可重入锁

    为每个锁关联一个获取计数器和一个所有者线程,当计数值为0时,这个锁就被认为是没有被任何线程所占有的。当线程请求一个未被持有的锁时,计数值将会递增。而当线程退出同步代码时,计数器会相应地递减。当计数值为0时,则释放该锁。

    如下:我们通过修改自旋锁来实现一个可重入的自旋锁

    public class SpinLockDemo {
        private MySpinLock lock =  new MySpinLock();
        class Widget{
            public void doSomething(){
                lock.lock();
                System.out.println("Widget calling doSomething");
                lock.unlock();
            }
        }
    
        class MySpinLock{
            private AtomicReference<Thread> owner = new AtomicReference<>();
            private int count = 0;
            public void lock(){
                Thread cur = Thread.currentThread();
                if (cur == owner.get()){
                    count ++;
                    return;
                }
                while (! owner.compareAndSet(null,cur)){
    
                }
            }
    
            public void unlock(){
                Thread cur = Thread.currentThread();
                if (cur == owner.get()){
                    if (count != 0){
                        count --;
                    } else {
                        owner.compareAndSet(cur,null);
                    }
                }
            }
        }
    
        class LoggingWidget extends Widget {
            @Override
            public void doSomething() {
                lock.lock();
                System.out.println("LoggingWidget calling doSomething");
                super.doSomething();
                lock.unlock();
            }
        }
    
        public static void main(String[] args){
            SpinLockDemo spinLockDemo = new SpinLockDemo();
            SpinLockDemo.Widget widget = spinLockDemo.new LoggingWidget();
            widget.doSomething();
        }
    }
    
    输出结果:
    LoggingWidget calling doSomething
    Widget calling doSomething
    

    相关文章

      网友评论

      本文标题:可重入锁

      本文链接:https://www.haomeiwen.com/subject/rqpvtxtx.html