常见的锁策略
1.公平锁:
(锁的获取顺序必须和线程方法的先后顺序保持一致)
公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,ReentrantLock 在构造函数中提供了是(true)否(false)公平锁的初始化方式来定义公平锁。
2.非公平锁:
(锁的获取顺序和线程获取锁的前后顺序无关)
JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
3.自旋锁:
自旋锁原理:如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋(死循环)),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
自旋锁的优点:
自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!
自旋锁的缺点:
但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu 做无用功,占着 XX 不 XX,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup 的线程又不能获取到 cpu,造成 cpu 的浪费。所以这种情况下要关闭自旋锁.(缺点:如果发生死锁则会- -直自旋(循环),所以会带来一定的额外开销)。
自旋锁时间阈值(1.6 引入了适应性自旋锁):
自旋锁的目的是为了占着 CPU 的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用 CPU 资源,进而会影响整体系统的性能。因此自旋的周期选择格外重要!JVM 对于自旋周期的选择,jdk1.5 这个限度是一定的写死的,在 1.6 引入了适应性自旋锁, 适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定, 基本认为一个线程上下文切换的时间是最佳的一个时间, 同时 JVM 还针对当前 CPU 的负荷情况做了较多的优化,如果平均负载小于 CPUs 则一直自旋,如果有超过(CPUs/2)个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner 发生了变化则延迟自旋时间(自旋计数)或进入阻塞,如果 CPU 处于节电模式则停止自旋,自旋时间的最坏情况是CPU的存储延迟(CPU A 存储了一个数据,到 CPU B 得知这个数据直接的时间差),自旋时会适当放弃线程优先级之间的差异。
自旋锁的开启:
JDK1.6 中-XX:+UseSpinning 开启;
-XX:PreBlockSpin=10 为自旋次数;
JDK1.7 后,去掉此参数,由 jvm 控制;
4.可重入锁:
当一个线程获取一个锁之后,可以重复的进入。
(在 JAVA 环境下 ReentrantLock 和 synchronized 都是可重入锁)
public class ThreadDemo97 {
// 创建锁
private static Object lock = new Object();
public static void main(String[] args) {
// 第一次进入锁
synchronized (lock) {
System.out.println("第一次进入锁");
synchronized (lock) {
System.out.println("第二次进入锁");
}
}
}
}