可重入锁

锁作为并发共享数据,保证一致性的工具,在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. 使用可重入锁

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

posted on 2017-04-13 20:30  duanxz  阅读(1107)  评论(0编辑  收藏  举报