正在加载中,请稍后

synchronized 关键字和volatile关键字

synchronized 关键字

其实“锁”本身就是个对象,synchronized这个关键字不是锁,而是在加上synchronized时,仅仅是相当于“加锁”这个操作。

jdk1.6之前

synchronized可以用在修饰方法和代码块,保证线程安全,修饰静态方法时,作用于Class的所有实例对象。修饰普通方法时,作用于该方法上,锁住的是this对象。修饰代码块时,也是作用于该代码块。
synchronized修饰方法和代码块的底层实现还是有些许不同的
我们知道一个对象,它是由对象头、实例数据和对齐填充数据组成的

image
负责对象线程安全的monitor隐式对象就在对象头里,monitor是一个由c++实现的对象,

当我们在方法上加synchronized时,这时进入方法时,线程就会去检查monitor的ACC_SYNCHRONIED标识符,如果存在,说明该锁已被占用,则其他线程就获取不到锁对象的monitor对象,当方法被当前线程执行完后,就会释放monitor对象,其他线程就可以获取到monitor对象,进入方法。

当在代码块上加synchronized时,jvm会根据monitor的monitorentermonitorexit指令来完成的。这两个指令是相对的,monitor中有个计数器count,当线程进入代码块后,检查count是否为0,count不为0时,线程阻塞;count为0时执行monitorenter指令,count=count+1,其他线程进入代码快是阻塞,当执行完毕释放时,
执行monitorexit指令,count=count-1,当前线程退出代码块,这时其他线程可以进入。

jdk1.6之前synchronized是一个重量级操作,由于线程竞争上下文切换频繁导致资源利用不充分。

synchronized jdk1.6优化:

在对象头中,有MarkWord专门用来记录锁状态,来减少线程切换的频率

image

锁状态升级过程:无锁->偏向锁->轻量级锁->重量级锁

偏向锁

偏向锁是比轻量级锁更轻量的锁。轻量级锁,每次获取锁的时候,都会使用 CAS 判断是否可以加锁,不管有没有别的线程竞争。

当线程要进入synchronized修饰的方法或代码块时,jvm会判断对象头中的MarkWord中有没有偏向锁指向当前线程ID,如果有,若此时无其他线程竞争,保持偏向锁状态。当该线程重复进入方法或代码块时(重入),直接在MarkWord中判断有没有偏向锁指向它的线程ID,就不用通过 CAS 操作获取偏向锁了。
当有其他线程加入竞争后,线程会暂停检查,若果该线程执行完了,则撤销锁,其他线程占有该锁,如果该线程还未执行完还需要该锁,则将锁升级为轻量级锁。
自旋锁:就是轻量级锁的自旋状态,说白了就是线程就是干等,消耗cpu资源,所以要设置一个最大的自旋次数。
自旋策略:jdk1.6 jvm设置默认开启自旋,最大自旋数是10次。
轻量级锁:就是偏向锁升级来的。该线程还未执行完,继续占有资源,其他线程等待,这是其他线程就会自旋,等待资源释放。

重量级锁:如果这是其他线程自旋失败,一直等不到资源,轻量级锁就升级为重量级锁。这些线程就会进入monitor,进入内核调度状态,之后加入阻塞队列

其他优化:

锁消除:JIT编译时,检测到共享数据区存在不可能出现竞争情况,就会进行锁消除。例如同步方法内的局部变量,不可能被其他线程使用,就会进行锁消除

锁粗化:把多次锁请求合并成一个锁请求,降低性能消耗。

volatile 关键字

一般用于修饰变量,保证变量的可见性和有序性,但不能保证原子性,通俗的讲,就是线程每次获得的变量都是最新的。
线程运行时,会将内存中的数据(变量)拷贝到线程运行的缓存中,每次需要用到变量时直接从缓存中读取,这样能够加快运行速度,所以外部内存中的变量发生改变,线程的缓存中的变量不会发生改变。当我们在变量前加上volatile关键字时,这样线程每次都会去内存中读取变量,而不是从缓存中读取,这样能变量每次都是最新的。

volatile工作原理

有volatile修饰的共享变量进行写操作的时候会多出Lock前缀的指令,执行该指令在多核处理器下会引发两件事情。

  1. 将当前处理器缓存行数据刷写到系统主内存。
  2. 这个刷写回主内存的操作会使其他CPU缓存的该共享变量内存地址的数据无效。

这样就保证了多个处理器的缓存是一致的,对应的处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器缓存行设置无效状态,当处理器对这个数据进行修改操作的时候会重新从主内存中把数据读取到缓存里

并发的三大性质

可见性:多个线程访问一个变量时,变量改变后,能被其他线程看见。
有序性:由于指令重排,导致代码执行顺序不一致,进而导致出异常
原子性:要么全执行,要么全不执行。volatile并不能保证原子性,保证原子性要用到synchronized、atomic关键字或者锁。例如多线程情况下,private static volatile int counter = 0,count++,对于共享变量count++的操作,实际上会执行三个步骤

1.读取变量count的值

2.count的值+1

3.将值赋予变量count。

这三个操作中任何一个操作过程中,count的值被其他线程篡改,那么都会出现我们不希望出现的结果,所以volatile保证不了原子性。

总结:volatile可以保证可见性和有序性,不能保证原子性。synchronized三个性质都可以保证。

posted @ 2021-05-08 11:19  wode虎纹猫  阅读(99)  评论(0)    收藏  举报
Live2D