synchronized 使用:
同步代码块
- synchronized(this | 任意一个 Object 子类对象 | 当前类. class) {
- }
同步方法
修饰普通对象方法 锁当前对象 this
修饰类的静态方法 锁当前类. class
保护的是什么? 几个锁?
使用一把锁锁住了两个毫无关系的对象
如何保护毫无关系的资源?
使用多把锁锁住不同的资源
- class Account {
- // 余额
- int sal;
- // 密码
- String password;
- // 余额资源的锁
- private Object salLock = new Object();
- // 密码资源的锁
- private Object passLock = new Object();
- public int getMoney() {
- synchorinzed(salLock) {}
- }
- public void setMoney() {
- synchorinzed(salLock) {}
- }
- public String getPassword() {
- synchorinzed(passLock) {}
- }
- public void setPassword() {
- synchorinzed(passLock) {}
- }
- }
转账
- A -> B 100
- A -= 100;
- B += 100;
如何保护有关联关系的对象:
使用同一把锁
- public void zhuangzhang(Account target) {
- synchroinzed(Account.class) {
- this.sal -= 100;
- target.sal += 100;
- }
- }
由于转账涉及两个账户间的 sal 操作, 因此需要将两个账户同时锁定.
由于方法的 synchrond 只能锁一个对象, 因此锁不住转账操作
这个时候要看 synchronized 底层实现
synchronized 底层实现:
在使用 synchronized 时必须保证锁定对象必须为 Object 以及其子类对象.
synchronized 使用的是 JVM 层级别的 MonitorEnter 与 MonitorExit 实现.
这两个指令都必须获取对象的同步监视器 Monitor
对象锁 Monitor 机制
monitorenter:
检查 obj 对象的 Monitor 计数器值是否为 0, 为 0 表示此监视器还未被任意一个线程获取, 此时线程可以进入同步代码块并且将 Monitor 值 + 1, 将 Monitor 的持有线程标记为当前线程
当 Monitor 计数器值不为 0 且持有线程不是当前线程, 表示 Monitor 已经被别的线程占用, 当前线程只能阻塞等待.
当 Monitor 计数器值不为 0 但是持有线程恰好是当前线程,
monitorexit:
Monitor 计数器值 - 1
可重入锁:
当执行 MonitorEnter 时. 对象的 Monitor 计数器值不为 0, 但是持有线程恰好是当前线程,
此时将 Monitor 计数器值再次 + 1, 当前线程继续进入同步方法或代码块, 就好比上面第一个代码, 我调用 getMoney()方法的时候, getMoney()方法里面也可以调用 getPassword()方法
CAS 操作(无锁实现的同步 - 乐观锁)- 自旋
- class Test {
- int i = 0;
- // 线程 1
- synchronized(this) {
- i = 10;
- }
- }
- CompareAndSwap(O,V,N)
O: 当前线程存储的变量值 0
V: 内存中该变量的具体值 10
N: 希望修改后的变量值 10
当 O==V 时, 此时表示还没有线程修改共享变量的值, 此时可以成功的将内存中的值修改为 N
当 O!=V 时, 表示此时内存中的共享变量值已被其他线程修改, 此时返回内存中的最新值 V, 再次尝试修改变量
线程挂起阻塞: 车熄火
自旋: 脚踩刹车, 车不熄火
1.ABA 问题:
解决 ABA 问题添加版本号
2. 自旋在 CPU 上跑无用指令, 会浪费 CPU 资源
自适应自旋
JVM 尝试自旋一段时间, 若在此时间内, 线程成功获取到锁, 再下次获取锁时, 适当延长自旋时间.
若在此时间内, 线程没有获取到锁, 再下次获取锁时, 适当缩短自旋时间.
3. 公平性问题
处于阻塞态线程可能会一直无法获取到锁
Lock 锁可以实现公平性, synchronized 无法实现公平锁
偏向锁: JDK1.6 之后默认 synchronized
最乐观的锁: 进入同步快或同步方法的始终是一个线程
当出现另一个线程也尝试获取锁 (在不同时刻) 时, 偏向锁会升级为轻量级锁
轻量级锁
不同时刻有不同的线程尝试获取锁,"量黄灯策略"
同一时刻有不同线程尝试获取锁, 会将偏向锁升级为重量锁
重量级锁
JDK1.6 之前 synchronized 都是重量级锁, 将线程阻塞挂起(JDK1.6 自适应自旋)
锁只有升级过程没有降级过程
锁粗化
当出现多次连续的加锁与解锁过程, 会将多次加减锁过程粗化为一次的加锁与解锁过程
死锁:
死锁产生条件: 以下四种条件同时满足才会导致死锁
1. 互斥
共享资源只能同时被一个线程占用
2. 占用且等待
3. 不可抢占
线程 T1 拿到了资源 X 的锁, 其他线程不能抢占 X 锁
4. 循环等待
线程
来源: http://www.bubuko.com/infodetail-3088831.html