锁
Lock
Lock接口提供的synchronized所不具备的主要特性
特性 | 描述 |
---|---|
尝试非阻塞地获取锁 | 当前线程尝试获取锁,如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁 |
能被中断地获取锁 | 与synchronized不同,获取到的锁能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放 |
超时获取锁 | 在指定的时间截止之前获取锁,如果截止时间之前仍旧无法获取锁,则返回 |
队列同步器
队列同步器的实现依赖内部的同步队列来完成同步状态的管理。它是一个FIFO的双向队列,当线程获取同步状态失败时,同步器会将当前线程和等待状态等信息包装成一个节点并将其加入同步队列,同时会阻塞当前线程。当同步状态释放时,会把首节点中的线程唤醒,使其再次尝试获取同步状态。
队列同步器的结构
执行流程
同步器包含两个节点的引用,一个指向头节点,一个指向尾节点。
-
当一个线程没有获取到同步状态时,将其包装成节点,通过一个CAS的方法讲其设置到队列的尾节点
-
同步队列遵循FIFO,首节点时获取同步状态成功的节点,当首节点释放同步状态时,会唤醒后继节点,后继节点获取同步状态成功且自己的前驱节点时头节点时,就会把自己设置为头节点。
流程图如下:
可重入锁
ReentrantLock支持重入,在调用lock方法时,已经获取到锁的线程能够再次调用lock()方法而不被阻塞。
实现方式:
-
线程再次获取锁:锁需要去识别获取锁的线程是否为当前占据锁的线程,如果是,则再次获取成功
-
锁的释放:线程获取了N次锁,随后在第n次释放该锁后,其他线程才能获取到锁,锁的最终释放要求锁对于获取进行自增,计数器标识当前锁被重复获取的次数,锁释放时,计数自减,当计数等于0时标识锁已经成功释放。
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) { //先判断当前线程是否持有锁
int nextc = c + acquires; //计数器增加获取的acquires
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc); //设置为新状态
return true;
}
return false;
}
}
读写锁
ReentrantReadWriteLock 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁,描述如下:
线程进入读锁的前提条件:
-
没有其他线程的写锁,
-
没有写请求或者有写请求,但调用线程和持有锁的线程是同一个。
线程进入写锁的前提条件:
-
没有其他线程的读锁
-
没有其他线程的写锁
而读写锁有以下三个重要的特性:
-
公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
-
重进入:读锁和写锁都支持线程重进入。
-
锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
原理分析:
通过把一个32位的整形变量进行切分高16位标识读,低16位标识写,操作时通过&操作进行位运算把不相关的数位抹去
Condition接口
Condition的作用是对锁进行更精确的控制。Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的wait(),notify(),notifyAll()方法是和"同步锁"(synchronized关键字)捆绑使用的;而Condition是需要与"互斥锁"/"共享锁"捆绑使用的。