锁的具体实现
先上图再上代码验证
偏向锁demo
public class LockDemo {
Object o=new Object();
public static void main(String[] args) {
LockDemo demo=new LockDemo(); //o这个对象,在内存中是如何存储和布局的。
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
System.out.println("加锁之后。。。。。。");
synchronized (demo){
System.out.println(ClassLayout.parseInstance(demo).toPrintable());
}
}
}
未加锁之前的打印
第一个字节的最后三位就是001,对应上图无锁状态。
加锁后的打印
第一个字节的最后三位是000,对应上图轻量级锁,不应该是偏向锁吗。
奇怪了?怎么会是轻量级锁。因为默认情况下偏向锁是关闭状态。因为main方法启动时,会有很多隐藏的jvm线程在执行,很容易升级为轻量级锁,所以很多时候没必要开启。
开启偏向锁再打印
-XX:BiasedLockingStartupDelay=0
发现未同步之前就已经获得偏向锁了,原因还是JVM的一些匿名对象在主线程执行之前就已经获得了偏向锁
重量级锁Demo
public class SynchronizedDemo {
public static void main(String[] args) {
Demo testDemo = new Demo();
Thread t1 = new Thread(() -> {
synchronized (testDemo){
System.out.println("t1 lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
});
t1.start();
synchronized (testDemo){
System.out.println("main lock ing");
System.out.println(ClassLayout.parseInstance(testDemo).toPrintable());
}
}
}
第一个字节最后3位010,对应开始的图重量级锁的状态。
CAS(有点类似于数据库通过version来实现乐观锁的感觉)
CAS这个在Synchronized底层用得非常多,它的全称有两种
- Compare and swap
- Compare and exchange
就是比较并交换的意思。它可以保证在多线程环境下对于一个变量修改的原子性。
CAS的原理很简单,包含三个值
- 当前内存值(V)、
- 预期原来的值(E)
- 期待更新的值(N)
如果V==E,那么就修改V为N。
CAS底层实现
不难想到其实还是会用到锁的机制
JVM源码
可以看到LCOK_IF_MP就是判断是否是多核,多核情况下CAS才可能有问题,如果是的话那么就加锁。
可以理解为这样的代码最后
public synchronized int compareAndSwap(int expectedValue, int newValue) {
//内存值V
int oldValue = value;
//预期值A等于内存值V
if (oldValue == expectedValue) {
//将修改值B赋值给内存值V
value = newValue;
}
//返回内存值V
return oldValue;
}
ABA问题
内存值V被其他线程多次修改,最终又改回了预期值A,但是这种预期值其实不是预期值了。
- 例:预期值为5,内存值最初为5,被另一个线程改为7,然后又被改成5,此时预期值与内存值进行值的比较发现没有变化,就认为中途没有其他线程操作
- 优化方案,对数据加上版本号