这是我参与更文挑战的第9天,活动详情查看: 更文挑战
jvm锁升级
在jvm里锁的升级过程是:无锁->偏向锁->轻量级锁(自旋锁)->重量级锁(synconized)
无锁:单个线程执行不需要锁 偏向锁:少量线程执行时,在markword中最后两位标记锁状态(待修改) 轻量级锁:又叫自旋锁或者CAS,没有获得锁的线程会自旋一定次数,等待并尝试修改锁标记位置,来获取锁 重量级锁:即synconized,是系统级锁,使用底层的互斥量,依靠monitor来实现加锁 锁的膨胀升级过程是不可逆的。
CAS的理解和底层原理
cas全称是compare and set,首先在底层的硬件级别一定是保证是原子性的,这就保证了在同一时刻只有一个线程可以执行CAS,同时其他线程去执行CAS操作就会失败。在当前线程执行完成后,会对比旧值,如果相同就会写值。
synchronized关键字的底层原理
它是一个可重入的重量级锁。它的加锁和释放锁会对管程对象monitor里的计数器进行加一和减一操作,加锁会执行monitorEnter,释放锁会执行monitorExit。
对JDK中AQS的理解,实现原理是什么
AQS的实现是state状态值+CAS+等待队列。线程进来了通过CAS修改state的值,如果修改失败说明有线程正在执行,则会进入等待队列,又因为它默认是非公平锁,所以后面进来还没在等待队列的线程可以竞争锁,但是可以设置为公平锁。这样获取不到锁的线程就会进入等待队列,顺序执行。
volatile关键字的理解
volatile是用来保证变量在多个线程之间的可见性和有序性的。它是通过使用M(修改)E(独占)S(共享)I(失效)缓存一致性协议来实现的。当一个线程拿到数据后,会将变量标记为S(共享)状态,当该线程操作变量的时候会将变量标记为M(修改)状态,其他线程会将自己工作内存中的变量副本标记为I(失效)状态
volatile和有序性的关系
happens-before原则,volatile增加了内存屏障禁止了指令重排
volatile和可见性的关系
在线程执行被volatile修饰的变量的write操作的时候,jvm会发送一条lock指令给cpu。cpu会在执行完后立马写会主内存,同时因为mesi缓存一致性协议,其他各个cpu都会对总线进行嗅探,保证自己工作内存中的值是最新的。
乐观锁和悲观锁
乐观锁:默认资源不会被修改,在每次读取数据的时候都不会上锁。但是在更新的时候会去判断数据是否改变。可以使用版本号或者CAS实现。例如JUC.Atomic包下的原子操作类。
被关锁:默认资源会被修改,在每次那数据的时候就会上锁,别的线程被阻塞直到拿到锁为止。例如数据库中的行锁,表锁,Java中的Synchronized和ReentrantLock等独占锁。
乐观锁的缺点
ABA问题:假设变量V在读取的时候是A值,变更为B值后写入,写入时读取比较值还是A然后写入,但是此时的A不一定是原先的A,也有可能是别的线程修改过后的A。这就产生了ABA问题。解决ABA问题的一个方法是version。
在读取变量V的时候一起取出version。在写入的时候比较变量V的同时比较version。
循环时间长开销大,CAS长时间不成功,会给CPU带来极大的消耗。
只能保证一个共享变量的原子操作,CAS只对单个共享变量有效。JDk1.5之后添加了AtomicReference类来保证引用对象的原子性。可以将多个共享变量组合成一个共享对象来操作。
乐观锁和悲观锁的使用场景
乐观锁适用于读多写少的场景。对于资源的竞争较小
被关锁适用于写多杜少的场景。对于资源的竞争较大
资源竞争较小的情况,使用Synchornized同步锁进行线程阻塞和唤醒以及用户态和内核态的切换额外消耗CPU资源,而CAS不需要进入内核,不需要切换线程操作自旋几率小,资源消耗少。
资源竞争较大情况下,使用CAS,则会导致大量线程处在自旋状态,会严重浪费CPU资源。