Juc并发编程04——可重入锁、公平锁与非公平锁

1.ReentrantLock使用介绍

之前我们一直使用的Lock实例都用的是ReentrantLock,实际上,这是一种可重入锁。简单来说,就是对同一个线程可以进行多次的加锁操作。

public class Demo11 {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        lock.lock();
        new Thread(() -> {
            System.out.println("thread2 try to lock");
            lock.lock();
            System.out.println("thread2 lock successfully");
        }).start();
        lock.unlock();
        System.out.println("thread1 unlock one time");
        lock.unlock();
        System.out.println("thread2 unlock twice");
    }
}

其输出如下。

thread1 unlock one time
thread2 unlock twice
thread2 try to lock
thread2 lock successfully

也可能如下。其共同点是,只有线程1两层锁都被释放了线程2才能成功的获取到锁。

thread1 unlock one time
thread2 try to lock
thread2 unlock twice
thread2 lock successfully

ReentrantLock还提供了一些工具方法,介绍如下。

public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        Thread t1 = new Thread(lock::lock);
        Thread t2 = new Thread(lock::lock);
        t1.start();
        t2.start();
        Thread.sleep(1);
        System.out.println(lock.getQueueLength());
        System.out.println(lock.hasQueuedThread(t1));
        System.out.println(lock.hasQueuedThread(t2));
        System.out.println(lock.hasQueuedThread(Thread.currentThread()));
    }
}

如果一把锁被一个线程所持有,在其它线程获取锁时,是会进入等待队列的。可以使用getQueueLength()获取等待锁的线程数的预估值。

public class Demo13 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        lock.lock();
        Thread t1 = new Thread(lock::lock);
        Thread t2 = new Thread(lock::lock);
        t1.start();
        t2.start();
        Thread.sleep(1);
        System.out.println(lock.getQueueLength());
        System.out.println(lock.hasQueuedThread(t1));
        System.out.println(lock.hasQueuedThread(t2));
        System.out.println(lock.hasQueuedThread(Thread.currentThread()));
    }
}

其结果如下。

2
true
true
false

同样的,Condition类也可以做类似判断。输出结果依次是1,0.这种api多用才能记住,很枯燥。

public class Demo14 {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        new Thread(() -> {
            lock.lock();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }).start();
        TimeUnit.SECONDS.sleep(1);
        lock.lock();
        System.out.println(lock.getWaitQueueLength(condition));
        condition.signal();
        System.out.println(lock.getWaitQueueLength(condition));
        lock.unlock();
    }

2.公平锁与非公平锁

线程通过lock()来获取锁,如果获取不到会暂时进入等待队列中。那么,多个等待的线程获取锁的先后顺序是否与调用lock()的时间顺序是一致的呢?

读一读ReentrantLock的源码来一探究竟吧。

public ReentrantLock() {
        sync = new NonfairSync();
    }

sync是什么?

abstract static class Sync extends AbstractQueuedSynchronizer {...}
1

原来是它自己的一个静态内部类,继承了AbstractQueuedSynchronizer,我们后面将其简称为AQS.AQS里面的源码其实比较复杂,同时它也是我们的Lock锁机制的核心之一。比如我们的lock()操作其实就是调用的它的lock()方法。

public void lock() {
        sync.lock();
    }

后面我们会对AQS详细的进行介绍。这里我们再回过头看看ReentrantLock的构造方法。从名字上可以看出新建了一个非公平锁对象NonfairSync。

公平锁:获取锁的线程根据获取锁的顺序在队列中排队,先到先服务。

非公平锁:多个线程在获取锁的时候,调用lock()时会直接尝试获取锁,如果获取不到再进入等待队列,如果获取到锁则直接拥有锁。

实际上它还有重载方法,可以指定使用公平锁还是非公平锁。

public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
}

这里写个demo做简单的功能测试。先看看公平锁。

public class Demo15 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(true);
        Runnable action = () -> {
            System.out.println("thread" + Thread.currentThread().getName() + "try to lock");
            lock.lock();
            System.out.println("thread" + Thread.currentThread().getName() + "lock successfully");
            lock.unlock();
        };
        for (int i = 0; i < 10; i++) {
            new Thread(action).start();
        }
    }
}

输出如下。

public class Demo15 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(true);
        Runnable action = () -> {
            System.out.println("thread" + Thread.currentThread().getName() + "try to lock");
            lock.lock();
            System.out.println("thread" + Thread.currentThread().getName() + "lock successfully");
            lock.unlock();
        };
        for (int i = 0; i < 10; i++) {
            new Thread(action).start();
        }
    }
}

输出如下,细心的同学可能发现2先获取锁,但是1先获取锁,除此之外是公平的。可见公平锁不一定完全公平,后面我们将详细介绍这一点。

threadThread-0try to lock
threadThread-0 lock successfully
threadThread-1try to lock
threadThread-2try to lock
threadThread-3try to lock
threadThread-4try to lock
threadThread-2 lock successfully
threadThread-5try to lock
threadThread-6try to lock
threadThread-8try to lock
threadThread-7try to lock
threadThread-1 lock successfully
threadThread-3 lock successfully
threadThread-4 lock successfully
threadThread-5 lock successfully
threadThread-9try to lock
threadThread-6 lock successfully
threadThread-8 lock successfully
threadThread-7 lock successfully
threadThread-9 lock successfully

非公平锁

public class Demo15 {
    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock(false);
        Runnable action = () -> {
            System.out.println("thread" + Thread.currentThread().getName() + "try to lock");
            lock.lock();
            System.out.println("thread" + Thread.currentThread().getName() + " lock successfully");
            lock.unlock();
        };
        for (int i = 0; i < 10; i++) {
            new Thread(action).start();
        }
    }
}

输出如下。

threadThread-1try to lock
threadThread-3try to lock
threadThread-2try to lock
threadThread-0try to lock
threadThread-1 lock successfully
threadThread-6try to lock
threadThread-6 lock successfully
threadThread-5try to lock
threadThread-5 lock successfully
threadThread-4try to lock
threadThread-4 lock successfully
threadThread-3 lock successfully
threadThread-7try to lock
threadThread-9try to lock
threadThread-8try to lock
threadThread-7 lock successfully
threadThread-2 lock successfully
threadThread-0 lock successfully
threadThread-9 lock successfully
threadThread-8 lock successfully