并发编程之ReentrantLock

正文

ReentrantLock 是Java并发包中提供的一个可重入的互斥锁。ReentrantLock 和 synchronized 在基本用法和行为语义上基本相同,一样具备可重入性。只是 ReentrantLock 增长了一些高级扩展功能,好比,能够实现公平锁,同时也能够实现绑定多个 Condition。并发

可重入性 / 公平锁 / 非公平锁

  1. 可重入性: 一个线程在获取一段使用锁的代码时,能够再次进入这段代码。例如,一个被可重入锁修饰的递归程序,能够重复的获取锁,而不会出现把本身锁死的状况。synchronized 和 ReentrantLock 都具备可重入性。ide

  2. 公平锁 / 非公平锁
    所谓公平锁,指锁的获取策略相对公平,当多个线程在获取同一个锁时,必须按照锁的申请时间来依次得到锁;非公平锁则不一样。synchronized 是非公平锁,ReentrantLock 默认也是非公平的,可是能够经过带 boolean 参数的构造方法指定使用公平锁,但非公平锁的性能通常要优于公平锁。
    synchronized 是 Java 原生的互斥同步锁,使用方便,对于 synchronized 修饰的方法或同步块,无需再显式释放锁。而 ReentrantLock 作为 API 层面的互斥锁,须要显式地去加锁解锁。采用 Lock ,必须主动去释放锁,而且在发生异常时,不会自动释放锁。所以通常来讲,使用 Lock 必须在 try{}catch{} 块中进行,而且将释放锁的操做放在 finally 块中进行,以保证锁必定被被释放,防止死锁的发生。函数

class Demo{

    private final ReentrantLock lock = new ReentrantLock();

    public void method(){

        lock.lock(); // 加锁

        try{
            // to do...

        }finally {
            lock.unlock(); // 释放锁
        }
    }
}

源码分析

  1. 无参构造函数,默认是非公平锁
/**
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
        sync = new NonfairSync();
    }
  1. 带 Boolean 参数的构造器
/**
     * Creates an instance of {@code ReentrantLock} with the
     * given fairness policy.
     *
     * @param fair {@code true} if this lock should use a fair ordering policy
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }
  1. lock()
public void lock() {
        sync.acquire(1); //代理到Sync的lock方法上
    }

Sync的lock方法是抽象的,实际的lock会代理到FairSync或是NonFairSync上(根据用户的选择来决定,公平锁仍是非公平锁)oop

  1. unlock()
public void unlock() {
        sync.release(1);
    }

释放锁,调用sync的release方法。源码分析

  1. tryLock()
public void method(){

        if(lock.tryLock()){

            try{

                // to do...

            }finally {
                lock.unlock();
            }
        }else {

            // 若是不能获取锁,则作其余事情
        }
    }

tryLock()方法是有返回值的,它表示用来尝试获取锁,若是获取成功,则返回true,若是获取失败(即锁已被其余线程获取),则返回false。性能

  1. newCondition()
public Condition newCondition() {
        return sync.newCondition();
}

获取一个conditon,ReentrantLock支持多个Conditionui

示例

/*
* 编写一个程序,开启 3 个线程,这三个线程的 ID 分别为 A、B、C,每一个线程将本身的 ID 在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。
*    如:ABCABCABC…… 依次递归
*/
class AlternateDemo{

    private int num = 1; // 标记当前正在执行的线程

    private ReentrantLock lock = new ReentrantLock();

    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    /**
     *
     * @param totalLoops 循环第几轮
     */
    public void loopA(int totalLoops){

        lock.lock();

        try{
            if(num != 1){
                condition1.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 2;
            condition2.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void loopB(int totalLoops){

        lock.lock();

        try{
            if(num != 2){
                condition2.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 3;
            condition3.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }

    public void loopC(int totalLoops){

        lock.lock();

        try{
            if(num != 3){
                condition3.await();
            }

            System.out.println(Thread.currentThread().getName() + "\t" + totalLoops);

            num = 1;
            condition1.signal();

        }catch (Exception e){

        }finally {
            lock.unlock();
        }
    }
}


public class Main {

    public static void main(String[] args) {

        AlternateDemo alternateDemo = new AlternateDemo();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopA(i);
                }
            }
        }, "A").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopB(i);
                }
            }
        }, "B").start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    alternateDemo.loopC(i);
                    System.out.println("----------------------");
                }
            }
        }, "C").start();
    }
}

代码分析:三个线程A、B、C分别调用10次打印,只需想办法控制三个现成的执行顺序。
若线程A先获取锁,直接打印。
若线程B先获取锁,B会被阻塞,释放锁后由A、C争夺。
若线程C先获取锁,C会被阻塞,释放锁后由A、B争夺。
使用一个变量来维持获取锁的顺序,分别是线程A、线程B、线程Cthis

总结

  1. Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2. synchronized在发生异常时,会自动释放线程占有的锁,所以不会致使死锁现象发生;而Lock在发生异常时,若是没有主动经过unLock()去释放锁,则极可能形成死锁现象,所以使用Lock时须要在finally块中释放锁;
  3. Lock类能够建立Condition对象,Condition对象用来是线程等待和唤醒线程,须要注意的是Condition对象的唤醒的是用同一个Condition执行await方法的线程,因此也就能够实现唤醒指定类的线程
相关文章
相关标签/搜索