【Java】JUC并发(synchronized进阶、ReentrantLock可重入锁)
Synchronized进阶
1、锁升级
Java 6之后对 synchronized 进行了改进,引入了锁升级机制,锁会根据线程的竞争情况进行升级。方向为 : 无锁 --> 偏向锁 --> 轻量级锁 --> 重量级锁,可以一定程度减少系统的开销。
无锁:一个线程没有被执行时,就会处于无锁状态。
偏向锁:当只有一个线程执行时,会从无锁升级为偏向锁。
轻量级锁:当有多个线程交替执行同步块时,会从偏向锁升级为轻量级锁。
重量级锁:当多个线程并发时,会从轻量级锁升级为重量级锁。
2、锁升级过程
2.1、无锁 ==> 偏向锁
当只有一个线程执行时,会从无锁升级为偏向锁。会在对象头中的 Mark Word 中存入该线程的ID。此时,锁的状态为偏向锁。
2.2、偏向锁 ==> 轻量级锁
当有多个线程交替执行同步块时,会从偏向锁升级为轻量级锁。这时在线程的栈中创建锁记录(Lock Record),并通过CAS(Compare and Swap)操作将对象头中的 Mark Word 复制到锁记录中,并将 Mark Word 执行锁记录地址。
2.3、轻量级锁 ==> 重量级锁
当多个线程并发时,会从轻量级锁升级为重量级锁。也就是当多个线程开始竞争锁时,就要从轻量级锁升级为重量级锁。
3、锁降级
锁降级,通常是从高级别转变到低级别的过程。一般会发生在:GC垃圾回收的过程中,JVM 会将索状态重置为无锁状态。
4、Synchronized实现原理
synchronized实现原理主要基于对象头和 Monitor (监视器)机制
4.1、对象头
通常将锁的状态保存在对象头中,对象头主要包含两个部分:Mark Word 和 Klass Pointer
4.1.1、Mark Word
- 长度:32位 JVM 中占32位(4字节),64位 JVM 中占64位(8字节)
- 作用:主要存储对象的 哈希值、GC分代年龄、锁状态 等运行时的数据。
- 特点:Mark Word 的位布局会根据对象的锁状态动态改变。
4.1.2、Klass Pointer
- 长度:32位 JVM 中占32位(4字节),64位 JVM 中默认开启指针压缩占32位(4字节),不开启是占64位(8字节)。
- 作用:指向对象所属类的元数据,JVM 通过指针确定对象是那个类的实例。
- 特点:堆内存超过32GB时自动关闭指针压缩,恢复为8字节。
4.2、Monitor (监视器)
通常存在于线程栈的锁记录中
5、线程安全的单例模式
public class Test02 {public static void main(String[] args) {Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();Singleton s3 = Singleton.getInstance();System.out.println(s1.hashCode());System.out.println(s2.hashCode());System.out.println(s3.hashCode());}
}
// 线程安全的单利模式
class Singleton{// 构造函数的私有化private Singleton(){}// 静态的内部类private static class Holder{// 单例对象private static final Singleton singleton = new Singleton();}public static Singleton getInstance() {return Holder.singleton;}
}2003749087
2003749087
2003749087
ReentrantLock可重入锁
1、什么事可重入锁
ReentrantLock是 java.util.concurrent.locks 包下的一个类,它提供了比 Synchronized 更灵活、强大的锁机制。“可重入”意味着同一线程可以多次获取同一把锁,而不会被阻塞。
2、核心方法
2.1、锁操作方法
方法签名 | 描述 |
void lock() | 获取锁,如果锁被其他线程持有,则阻塞当前线程 |
void lockInterruptibly() | 获取锁,但允许等待时中断 |
boolean tryLock() | 尝试以非阻塞的方式获取锁 |
boolean trayLock(long timeout,TimeUnit unit) | 在指定时间内尝试获取锁 |
void unlock() | 释放锁 |
2.2、状态查询方法
方法签名 | 描述 |
boolean isHeldByCurrentThread() | 查询当前线程是否持有锁 |
boolean isLocked() | 查询锁是否被任何线程持有 |
boolean isFair() | 判断锁是否为公平锁 |
2.3、条件变量方法
方法签名 | 描述 |
Condition newCondition() | 创建一个与当前锁绑定的条件变量 |
3、使用案例
3.1、锁的基本使用
public class Test04 {public static void main(String[] args) throws InterruptedException {Counter counter = new Counter();Thread t1 = new Thread(()->{counter.add();});Thread t2 = new Thread(()->{counter.dec();});t1.start();t2.start();t1.join();t2.join();System.out.println(counter.getValue());}
}class Counter{private static int value = 0;// 创建锁对象private final ReentrantLock lock =new ReentrantLock();public static int getValue() {return value;}public void add(){lock.lock();try {for (int i =0 ;i <10000;i++){value++;}} finally {lock.unlock();}} public void dec(){lock.lock();try {for (int i =0 ;i <10000;i++){value--;}} finally {lock.unlock();}}
}
3.2、支持中断锁的获取
public class demo1 {public static void main(String[] args) throws InterruptedException {// 创建ReentrantLock锁对象ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(()->{try {lock.lockInterruptibly();try {System.out.println("线程任务执行!");} finally {lock.unlock();}} catch (InterruptedException e) {System.out.println("线程中断!");return;}System.out.println("子线程结束!");});t1.start();t1.interrupt();System.out.println("主线程结束!");}
}主线程结束!
线程中断!
4、synchronized和synchronized的区别
特性 | Synchronized | synchronized |
锁的获取与释放 | 隐式调用,由JVM自动完成 | 显示调用,手动调用lock()和unlock()方法 |
可中断性 | 不可中断 | 可以中断,使用lockInterruptibly() |
超时机制 | 不支持 | 支持,使用tryLock(timeout) |
公平性 | 非公平 | 非公平和公平都支持 |
条件变量 | 单一wait/notify | 支持条件对象Condition |
锁状态检测 | 无法判断 | 通过方法可以获取 |
锁实现机制 | 监听器 Monitor | AQS |