ReentrantLock
在处理线程安全问题上,在JDK5以后引入了 Lock ,synchronized和Lock都可以保证线程安全问题!而Lock比synchronized使用更加灵活,也更适合复杂的并发场景。本文主要讲解Lock的子类ReentrantLock。
一.ReentrantLock与synchronized 比较
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。
二.ReentrantLock 原理
这里我们从源码进行分析学习,分析过程:
1.ReentrantLock 类结构
public class ReentrantLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = 7373984872572414699L;
private final Sync sync;
ReentrantLock 实现了Lock接口,实现了锁的机制,Sync 源码对他的描述是同步器提供所有实施机制,Sync 也是ReentrantLock的核心;
2.ReentrantLock 两种初始化状态
2.1 非公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
2.2 公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
公平锁与非公平锁的最大区别就是公平锁很尽可能的将等待时间最长的线程,具有最高的执行权重
无论是公平还是非公锁的实现都是继承了上面说到的 Sync,话题又回到了Sync;
3.Sync 设计
// 内部类,自定义同步器
private static class Sync extends AbstractQueuedSynchronizer {
// 是否处于占用状态
protected boolean isHeldExclusively() {
}
// 当状态为0的时候获取锁
public boolean tryAcquire(int acquires) {
}
// 释放锁,将状态设置为0
protected boolean tryRelease(int releases) {
}
}
从源码我们可以看出Sync继承了AbstractQueuedSynchronizer,AbstractQueuedSynchronizer也是传说的AQS,AQS的设计如下图:
在同步器中就包含了sync队列。同步器拥有三个成员变量:sync队列的头结点head、sync队列的尾节点tail和状态state。对于锁的获取,请求形成节点,将其挂载在尾部,而锁资源的转移(释放再获取)是从头部开始向后进行。对于同步器维护的状态state,多个线程对其的获取将会产生一个链式的结构。
4.ReentrantLock API
Modifier and Type | Method and Description |
---|---|
int | getHoldCount() 查询当前线程对此锁的暂停数量。 |
protected Thread | getOwner() 返回当前拥有此锁的线程,如果不拥有,则返回 null 。 |
protected Collection | getQueuedThreads() 返回包含可能正在等待获取此锁的线程的集合。 |
int | getQueueLength() 返回等待获取此锁的线程数的估计。 |
protected Collection | getWaitingThreads(Condition condition) 返回包含可能在与此锁相关联的给定条件下等待的线程的集合。 |
int | getWaitQueueLength(Condition condition) 返回与此锁相关联的给定条件等待的线程数的估计。 |
boolean | hasQueuedThread(Thread thread) 查询给定线程是否等待获取此锁。 |
boolean | hasQueuedThreads() 查询是否有线程正在等待获取此锁。 |
boolean | hasWaiters(Condition condition) 查询任何线程是否等待与此锁相关联的给定条件。 |
boolean | isFair() 如果此锁的公平设置为true,则返回 true 。 |
boolean | isHeldByCurrentThread() 查询此锁是否由当前线程持有。 |
boolean | isLocked() 查询此锁是否由任何线程持有。 |
void | lock() 获得锁。 |
void | lockInterruptibly() 获取锁定,除非当前线程是 |
Condition | newCondition() 返回[Condition ]用于这种用途实例[Lock ]实例。 |
String | toString() 返回一个标识此锁的字符串以及其锁定状态。 |
boolean | tryLock() 只有在调用时它不被另一个线程占用才能获取锁。 |
boolean | tryLock(long timeout, TimeUnit unit) 如果在给定的等待时间内没有被另一个线程 [占用] ,并且当前线程尚未被 [保留,]则获取该锁。 |
void | unlock() 尝试释放此锁。 |
三.实战
这里模拟售票,通过ReentrantLock的方式实现线程的安全
public class LockMain {
public static void main(String[] args) {
Window window = new Window();
Thread thread1 = new Thread(window);
Thread thread2 = new Thread(window);
Thread thread3 = new Thread(window);
thread1.start();
thread2.start();
thread3.start();
}
}
/**
* 售票窗口
*/
class Window implements Runnable{
private volatile int num = 100;
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.lock();
try {
if (num > 0){
System.out.println(Thread.currentThread().getName()+"窗口在售票,票号为"+ num);
num --;
}else {
break;
}
}finally {
lock.unlock();
}
}
}
}
添加小编微信:372787553 ,带你进入技术交流区,记得备注进群