参考
《java并发编程的艺术》
《七周七并发模型》
多线程并发编程中最常用的就是synchronized和volatile两个关键字。
volatile
通常被描述成一个轻量级锁。
用于声明需要在多线程环境中共享的对象
。 只能在类实例对象上声明。
功能:
保证当前对象对其它线程可见
禁止代码的重排序
重排序 :代码中上下两段代码不存在依赖关系时,jvm会对代码进行优化排序,排序后的顺序不一定是代码的顺序
内存屏障 : 一组cpu指令,用于实现对内存操作的顺序限制
但是volatile并不能保证线程的安全性
q1:volatile 如何保证可见性
volatile声明的变量在转变成汇编语言在底层执行的时候会多出lock的指令。
该指令会引发两个事情:
- 将当前线程的工作内存中的数据写回到主内存中
- 写回内存的操作会让其它线程工作内存中缓存了该字段的数据无效
Synchronized
通常被描述成重量级锁,也可称为内置锁
可用于方法,对象,代码段上
功能 :保证当前操作的线程安全性
保证可见性
底层实现:
synchronized是通过对象的monitor的monitorenter和monitorexit来实现对象锁的持有和释放
每个对象上都有一个java 对象头,用来存放当前对象的锁信息,hashcode, 分代年龄,是否偏向锁,锁标志位等信息。
锁按级别从低到高有四种状态:无锁,偏向锁,轻量级锁,重量级锁
如图所示:
缺点:
- 在获取锁而进入阻塞状态时,是无法中断的
- 尝试获取内置锁时,无法设置超时时间
- 获得内置锁,必须使用Synchronized块
导致的主要问题就是:
死锁而无法恢复,唯一解决方法是 终止jvm
为了解决内置锁的问题,引入reentrantLock(可重入锁)
可重入锁 ReentrantLock
优势:
- 显示的加锁和解锁
Lock lock = new ReentrantLock();
lock.lock();
try{
...
}finally{
lock.unlock()
}
- 可中断 lock.lockInterruptibly()
- 可设置超时时间 lock.tryLock(1000, TimeUnit.MILLISECONDS)
偏向锁
是指被同一线程重复获取的锁
优点是:
进入和退出锁的时候不需要cas操作来加锁,解锁。效率更快
如何关闭偏向锁
java1.6, 1.7是默认启用的,
jvm参数: -XX:BiasedLockingStartupDelay = 0
轻量级锁的获取及膨胀流程
锁的优缺点对比
| 锁 | 优点 | 缺点 | 适用场景
| ------------- |:-------------😐 -----😐
| 偏向锁 | 加锁和解锁不需要额外的消耗,和执行非同步方法相比仅存在纳米级的差距| 如果线程之间存在锁竞争会带来额外的锁撤销的消耗 | 适用于只有一个线程访问同步块的情况
| 轻量级锁 | 竞争的线程不会阻塞,提高线程的响应速度 | 如果始终得不到锁竞争的线程,使用自旋回消耗cpu | 追求响应时间,同步块执行速度非常快
| 重量级锁 | 线程竞争不使用自旋,不会消耗cpu | 线程阻塞,响应时间缓慢 | 追求吞吐量,同步执行时间较长
三者之间的区分,如果只有一个线程重复进入同步块,那是偏向锁,如果多个线程进入同步块,不会阻塞,那是轻量级锁,会阻塞,是重量级锁