synchronized关键字的底层原理
基础版
当我们对代码就行反编译,会发现其实synchronized就是monitor
Monitor
假如现在有一个线程过来了,要执行当前代码,会执行到synchronized (lock),lock是一个对象锁。首先会让这个lock对象和monitor进行关联,然后判断Owner是否为null,如果为null则让当前线程直接拥有对象锁。
并且Owner只能关联一个线程,如果此时线程2来了,就会到EntryList中等待,无论来了多少线程,只要Owner不为努力了,就都要到EntryList中等待,这些线程都会处于block状态。而当线程1执行完成,Owner为null了,就会唤醒EntryList中阻塞的这些线程,让这些线程争抢Owner的所有权
当一个线程调用了wait方法之后,就会处于等待状态,他会把这些线程放到WaitSet当中
总结
进阶版
Monitor实现的锁属于重量级锁,你了解过锁升级吗?
对象的内存结构
对象怎么关联上的Monitor
轻量级锁
在很多的情况下,在Java程序运行时,同步块中的代码都是不存在竞争的,不同的线程交替的执行同步块中的代码。这种情况下,用重量级锁是没必要的。因此JVM引入了轻量级锁的概念。
首先我们可以看到obj的内存结构,当来了一个线程来执行method方法,在这个线程执行的时候,就会创建一个锁记录,叫做Lock Record,每个线程的栈帧都包含一个锁记录的结构.此时他内部就可以存储锁对象的mark word,首先会让Object reference去指向锁对象,就是obj。
当线程去执行锁的时候,会修改对象的mw,用cas的方式交换数据,如果发现了重入锁,则会再添加一个LR作为重入的计数,线程中包含几个LR就说明他重入了这个锁几次
当代码执行完成,也就是解锁的情况,这时候我们要判断当前的锁记录是否为null,如果是null则说明有重入,就会删掉这个LR,这个时候,第一个LR也要退出,他的锁记录可不为null,这个时候会再来一次cas操作。把这些值再交换回来,这时就代表这解锁成功了
偏向锁
轻量级锁在没有竞争时(就自己这个线程),每次重入仍然需要执行 CAS 操作。
Java 6 中引入了偏向锁来做进一步优化:只有第一次使用 CAS 将线程 ID 设置到对象的 Mark Word 头,之后发现
这个线程 ID 是自己的就表示没有竞争,不用重新 CAS。以后只要不发生竞争,这个对象就归该线程所有
在轻量级锁不同的是,偏向锁会记录某一个线程的id,并且把偏向锁的标志改为1,并且直接把当前线程的线程id写到Obj的MW当中
当我们执行m2,会再去添加一个锁记录,然后不会进行CAS操作,而是来看一些MW中的线程id是否是自己的,如果是自己的,则只是记录一下当前重入的次数。m3也是,只是会添加一条记录,而不是进行cas操作
锁升级
Java中的synchronized有偏向锁、轻量级锁、重量级锁三种形式,分别对应了锁只被一个线程持有、不同线程交替持有锁、多线程竞争锁三种情况。
JMM(Java内存模型)
JMM(Java Memory Model)Java内存模型,定义了共享内存中多线程程序读写操作的行为规范,通过这些规则来规范对内存的读写操作从而保证指令的正确性
共享内存:我们定义的成员变量,创建的对象或者是数组
CAS
CAS的全称是: Compare And Swap(比较再交换),它体现的一种乐观锁的思想,在无锁情况下保证线程操作共享数据的原子性。
在JUC( java.util.concurrent )包下实现的很多类都用到了CAS操作
AbstractQueuedSynchronizer(AQS框架)
AtomicXXX类
CAS数据交换流程
假如线程A先执行完成
线程B执行
CAS底层实现
CAS 底层依赖于一个 Unsafe 类来直接调用操作系统底层的 CAS 指令
被native修饰,也就是说都是由系统提供的,是由c或c++实现的
总结
volatile
一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:
① 保证线程间的可见性
② 禁止进行指令重排序
保证线程间的可见性
那为什么线程2可以读到线程1的值,而线程3不行呢