Java实现同步有3个基础手段,synchronized,AQS+CAS配合实现的对象锁,cas
synchronized修饰的代码块中,在java文件被编译成class文件后,生成了字节码,在代码块之前,会多出来一条monitor enter指令。在代码块之后,会多出来一条monitor exit指令。
同时reference需要被指定,因为这是对象锁
这里总结下synchronized和ReentranLock默认实现的区别
都是悲观等待的
都是可重入
都是非公平
都是可以有条件休眠的,但是后者可以实现多条件
后者可以响应中断,先修改中断标记位,在等待的时候被唤醒会检测
乐观锁
正常修改一个值的操作
1.从主内存读取值到私有内存(寄存器)
2.修改这个值
3.可能再次复用这个值,进行修改
4.把这个值从私有内存刷新回主内存
加了volatile
1.从主内存读取值到私有内存
2.修改这个值
3.把这个值从私有内存刷新回主内存
4.再次读取主内存的值
所以volatile是不顶用的。
cas:
1.从主内存读取值到私有内存
2.修改这个值
3.尝试把这个值从私有内存刷新回主内存,如果旧值和现值不一致,失败
这3个操作被打包成一条指令
volatile
1.强制从主内存读取(结合java内存模型)
2.不保证原子性
3.指令重排序引起的内存屏障。比如
config();
boolean init = true;
这个init被赋值为true可能会先被执行。
可重入代码
比如不依赖外部变量,只依赖方法内的局部变量表里的变量。同时多个线程执行他会得到相同的结果。
线程本地存储
ThreadLocal。比如Looper在初始化的时候会被设置到ThreadLocal里面,其原理是以当前线程为键,获取一个map,再根据ThreadLocal为键,获取ThreadLocal的值。使得一个成员变量,在不同的线程会有不同的实例。
自旋
由于线程从用户态到内核态的切换需要时间,可以在发现资源被占用的时候,可以在内核态自旋几次,如果还没有,那就再睡眠。
如果之前成功通过自旋的方式等到锁了,那么下次自旋的时候,自旋次数会被增加。
锁清除
如果没必要的锁,JVM会替我们清除
锁合并
许多锁放在一起不太好,可以考虑合并
Mark Word
对象头。存储了gc年龄、hashcode、锁标志。
偏心锁、轻量级锁、重量级锁
偏心锁认为只有一个线程会来访问这个对象。一开始对象是可偏向状态。当第一次加锁的时候,会把ThreadID记录在Mark Work中。
接下来如果有别的线程来访问,就会去检查ThreadID的那个线程死了没有、或者这个对象是否还会被那个线程用到。如果没有,那么这个新的线程就被偏心了。如果有,那么就会膨胀程轻量级锁。
轻量级锁认为竞争是有的,但是很轻。倾向于通过自旋来解决问题。如果自旋次数太多,或者在自旋等待的时候,又有新的线程到来,那么就膨胀为重量级锁(不会再自旋,直接休眠之)。