• 可重入锁


    锁作为并发共享数据,保证一致性的工具,在JAVA平台有多种实现(如 synchronized 和 ReentrantLock等等 ) 。这些已经写好提供的锁为我们开发提供了便利,但是锁的具体性质以及类型却很少被提及。

    四、可重入锁:

    本文里面讲的是广义上的可重入锁,而不是单指JAVA下的ReentrantLock。

    可重入锁,也叫做递归锁,指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。
    在JAVA环境下ReentrantLock 和synchronized 都是可重入锁。

    下面是synchronized的可重入锁的实例:

    package com.dxz.sync3;
    
    public class Test implements Runnable {
    
        public synchronized void get() {
            System.out.println(Thread.currentThread().getId());
            set();
        }
    
        public synchronized void set() {
            System.out.println(Thread.currentThread().getId());
        }
    
        @Override
        public void run() {
            get();
        }
    
        public static void main(String[] args) {
            Test ss = new Test();
            new Thread(ss).start();
            new Thread(ss).start();
            new Thread(ss).start();
        }
    }

    下面是ReentrantLock的可重入锁的实例:

    package com.dxz.sync4;
    
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Test implements Runnable {
        ReentrantLock lock = new ReentrantLock();
    
        public void get() {
            lock.lock();
            System.out.println(Thread.currentThread().getId());
            set();
            lock.unlock();
        }
    
        public void set() {
            lock.lock();
            System.out.println(Thread.currentThread().getId());
            lock.unlock();
        }
    
        @Override
        public void run() {
            get();
        }
    
        public static void main(String[] args) {
            Test ss = new Test();
            new Thread(ss).start();
            new Thread(ss).start();
            new Thread(ss).start();
        }
    }

    两个例子最后的结果都是正确的,即 同一个线程id被连续输出两次。

    结果如下:

    Threadid: 8
    Threadid: 8
    Threadid: 10
    Threadid: 10
    Threadid: 9
    Threadid: 9

    可重入锁最大的作用是避免死锁。


    我们以自旋锁作为例子,

    package com.dxz.sync4;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    public class SpinLock {
        private AtomicReference<Thread> owner = new AtomicReference<>();
    
        public void lock() {
            Thread current = Thread.currentThread();
            while (!owner.compareAndSet(null, current)) {
            }
        }
    
        public void unlock() {
            Thread current = Thread.currentThread();
            owner.compareAndSet(current, null);
        }
    }

    对于自旋锁来说,
    1、若有同一线程两调用lock() ,会导致第二次调用lock位置进行自旋,产生了死锁。说明这个锁并不是可重入的。(在lock函数内,应验证线程是否为已经获得锁的线程)
    2、若1问题已经解决,当unlock()第一次调用时,就已经将锁释放了。实际上不应释放锁。
    (采用计数次进行统计)
    修改之后,如下:

    package com.dxz.sync4;
    
    import java.util.concurrent.atomic.AtomicReference;
    
    public class SpinLock1 {
        private AtomicReference<Thread> owner = new AtomicReference<>();
        private int count = 0;
    
        public void lock() {
            Thread current = Thread.currentThread();
            if (current == owner.get()) {
                count++;
                return;
            }
    
            while (!owner.compareAndSet(null, current)) {
    
            }
        }
    
        public void unlock() {
            Thread current = Thread.currentThread();
            if (current == owner.get()) {
                if (count != 0) {
                    count--;
                } else {
                    owner.compareAndSet(current, null);
                }
    
            }
    
        }
    }

    该自旋锁即为可重入锁。

    可重入锁也支持在父子类继承的环境中,示例:

    package com.dxz.sync;
    
    public class Main {
        public int i = 10;
    
        public synchronized void operateMainMethod() {
            try {
                i--;
                System.out.println("main print i = " + i);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
        }
    }

    子类:

    package com.dxz.sync;
    
    public class Sub extends Main {
        public synchronized void operateISubMethod() {
            try {
                while (i > 0) {
                    i--;
                    System.out.println("sub print i=" + i);
                    Thread.sleep(100);
                    this.operateMainMethod();
                }
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
        }
    }

    线程:

    package com.dxz.sync;
    
    public class MyThread extends Thread {
        @Override
        public void run() {
    
            Sub sub = new Sub();
            sub.operateISubMethod();
        }
    }

    入口类:

    package com.dxz.sync;
    
    public class Test {
    
        public static void main(String[] args) {
    
            MyThread thread = new MyThread();
            thread.start();
        }
    
    }

    结果:

    sub print i=9
    main print i = 8
    sub print i=7
    main print i = 6
    sub print i=5
    main print i = 4
    sub print i=3
    main print i = 2
    sub print i=1
    main print i = 0

    可重入锁也支持在父子类继承的环境中。

    下面这个锁的实现是不可重入的:

    package com.dxz.sync3;
    
    public class Lock {
        private boolean isLocked = false;
    
        public synchronized void lock() throws InterruptedException {
            while (isLocked) {
                wait();
            }
            isLocked = true;
        }
    
        public synchronized void unlock() {
            isLocked = false;
            notify();
        }
    }

    如果一个线程在两次调用lock()间没有调用unlock()方法,那么第二次调用lock()就会被阻塞,这就出现了重入锁死。

    避免重入锁死有两个选择:

    1. 编写代码时避免再次获取已经持有的锁
    2. 使用可重入锁

    至于哪个选择最适合你的项目,得视具体情况而定。可重入锁通常没有不可重入锁那么好的表现,而且实现起来复杂,但这些情况在你的项目中也许算不上什么问题。无论你的项目用锁来实现方便还是不用锁方便,可重入特性都需要根据具体问题具体分析。

  • 相关阅读:
    Microsoft Word 段前距设置和页眉设置
    JavaScript 跨域小总结
    JavaScript 闭包系列二(匿名函数及函数的闭包)

    JavaScript 变量作用域
    JavaScript 闭包系列一
    JavaScript 继承
    JavaScript 对象的创建
    MySQL(2): 窗口函数备份与恢复性能优化权限管理
    组建LAN
  • 原文地址:https://www.cnblogs.com/duanxz/p/6705886.html
Copyright © 2020-2023  润新知