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 Demo12 { | |
public static void main(String[] args) { | |
ReentrantLock lock = new ReentrantLock(); | |
lock.lock(); | |
lock.lock(); | |
System.out.println("lock count:" + lock.getHoldCount() + ", isLock: " + lock.isLocked()); | |
lock.unlock(); | |
System.out.println("lock count:" + lock.getHoldCount() + ", isLock: " + lock.isLocked()); | |
lock.unlock(); | |
System.out.println("lock count:" + lock.getHoldCount() + ", isLock: " + lock.isLocked()); | |
} | |
} |
如果一把锁被一个线程所持有,在其它线程获取锁时,是会进入等待队列的。可以使用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 {...}
原来是它自己的一个静态内部类,继承了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 |