juc并发包的常用类、线程安全实现方式、锁机制及 JVM 优化策略
- [1. juc包下的常用类:](#1. juc包下的常用类:)
- [2. 怎么保证多线程安全:](#2. 怎么保证多线程安全:)
- [3. Java中常用锁及使用场景:](#3. Java中常用锁及使用场景:)
- [4. 线程同步的方法:](#4. 线程同步的方法:)
- [5. Synchronized锁静态方法和普通方法的区别:](#5. Synchronized锁静态方法和普通方法的区别:)
- [6. Synchronized和ReentrantLock的区别:](#6. Synchronized和ReentrantLock的区别:)
- [7. 怎么理解可重入锁:](#7. 怎么理解可重入锁:)
- [8. Synchronized锁升级过程:](#8. Synchronized锁升级过程:)
- [9. JVM对Synchronized锁优化:](#9. JVM对Synchronized锁优化:)
- [10. 参考](#10. 参考)
1. juc包下的常用类:
线程池:
- ThreadPoolExecutor:最核心的线程池类,用于创建和管理线程池。
- Executors:线程池工厂类,提供了一系列静态方法来创建不同类型的线程池。
并发集合类:
- ConcurrentHashMap:线程安全的哈希表;分段锁技术,允许多个线程同时访问不同的段,提高了并发性能。
- CopyOnWriteArrayList:线程安全的列表,适合读多写少的场景,写操作会创建一个新的底层数组,读操作仍然在旧数组上进行,实现读写分离。
同步工具类:
- CountDownLatch:允许一个或多个线程等待其他一组线程完成操作后再继续执行。通过一个计数器来实现,初始化计数器N,线程完成任务调用countDown方法将计数器减1,直至为0。
- CyclicBarrier:让一组线程相互等待,直至所有线程都到达某个屏障点后,再一起执行。适用于多个线程需协同工作。
- Semaphore:信号量,用于控制同时访问某个资源的线程数量,它维护了一个许可计数器。
原子类:
- AtomicInteger;
- AtomicLong;
- AtomicBoolean;
- AtomicReference;
2. 怎么保证多线程安全:
- synchronized关键字,同步代码块或方法;
- volatile关键字,用于变量,确保所有线程看到的是该变量的最新值;
- Lock接口和ReentrantLock类;
- 原子类;
- 线程局部变量;
- 并发集合:ConcurrentHashMap,CopyOnWriteArrayList;
- JUC工具类:CountDownLatch,CyclicBarrier
3. Java中常用锁及使用场景:
- 内置锁(Synchronized),一般用在简单代码块、方法上
- ReentrantLock,只能用在代码块上,中断、定时、公平锁、控制多个变量、复杂并发场景
- 读写锁,读多写少
- 乐观锁和悲观锁:乐观锁不加锁,用版本号或时间戳来实现,悲观锁在访问数据前就会加锁。
- 自旋锁:不放弃CPU,循环检查锁是否可用,适合锁等待时间很短的情况,否则会过度消耗CPU资源
4. 线程同步的方法:
"同步"强调调用者是否等待结果,"异步"强调调用者可以继续做事不被阻塞。
- Synchronized关键字
- ReentrantLock关键字
- volatile关键字
- Atomic类
5. Synchronized锁静态方法和普通方法的区别:
- 普通方法锁的是当前实例对象this;
- 静态方法锁的是当前类的Class对象(ClassName.class);
- 多个对象之间共享一个资源(如:数据库连接池)用静态同步方法。
6. Synchronized和ReentrantLock的区别:
- Synchronized可以修饰静态方法、普通方法和代码块,ReentrantLock只能修饰代码块;
- Synchronized自动加锁释放锁,ReentrantLock需要手动加锁释放锁;
- Synchronized是非公平锁,Synchronized和ReentrantLock既可以是公平锁也可以是非公平锁;
- Synchronized不可响应中断,ReentrantLock可响应中断,解决死锁问题;
- Synchronized是JVM层面通过监视器实现的,ReentrantLock是基于AQS实现的;
7. 怎么理解可重入锁:
- 可重入锁指同一个线程在获取了锁之后,可以重复获取该锁,而不会造成死锁(阻塞导致)或其他问题。
- ReentrantLock实现可重入锁机制是基于线程持有锁的计数器,每重复获取一次就+1,释放-1,减到0才会完全释放。
- Synchronized是可重入的,当一个线程调用Synchronized方法的同时在其方法内部调用该对象另一个Synchronized方法,即一个线程得到对象锁之后再次请求该对象锁是允许的,底层是操作系统的互斥锁(mutex lock),每个可重入锁会关联一个线程ID和一个锁状态status(也类似于一个计数器)。
8. Synchronized锁升级过程:
无锁->偏向锁->轻量级锁->重量级锁
bash
┌─────────────┐
│ 无锁 │
└─────┬───────┘
↓ (加锁,第一次使用)
┌─────────────┐
│ 偏向锁 │ ←------ 若只有一个线程反复使用,保持偏向锁
└─────┬───────┘
↓(有其他线程竞争)
┌─────────────┐
│ 轻量级锁 │ ←------ 若竞争不激烈,用CAS自旋
└─────┬───────┘
↓(竞争激烈,CAS自旋失败)
┌─────────────┐
│ 重量级锁 │
└─────────────┘
9. JVM对Synchronized锁优化:
- 锁膨胀:没有一开始就用重量级锁,避免获取和释放锁的时候用户态到内核态的转换;
- 锁消除:JVM检测不到某段代码块被共享和竞争的可能性,就将这段的同步锁消掉;
- 锁粗化:将多个连续的加锁、解锁操作连接在一起,扩展成范围更大的锁;
- 自旋锁:避免挂起和恢复的开销,因为挂起和恢复操作都需要从用户态转入内核态;