一 Java对象内存与Synchronized
(1)对象组成
(2)对象头组成
(3)锁升级
(4)synchronized使用场景
(5)synchronized底层原理
①同步代码块
同步代码块使用的是monitorenter和monitorexit指令,进入代码块时,执行monitor enter指令,就会获取当前对象的所有权,获取成功计数器+1,重入则把计数器+1,执行完后执行monitor exit,计数器-1,直到计数器为0,才可以被其他线程持有。
②同步方法
当执行方法的时候,先判断是否是同步方法,然后ACC_SYNCHRONIZED会隐式的调用monitorenter和monitorexit指令。
(6)jdk1.6后对synchronized的优化
①偏向锁:当锁对象第一次被线程获取时,虚拟机会把对象头的标志位设置为01,偏向模式设置为1,表示进入偏向模式,同时使用CAS把获取到锁的线程ID记录在对象头的Mark Word中。如果CAS成功,持有偏向锁的线程以后每次进入这个锁相关的同步块时,虚拟机都可以不再进行任何同步操作。
②轻量级锁:代码进入同步块时,对象无锁,虚拟机将在线程的栈帧建立一个Lock Record,用于存储对象Mark Word的拷贝,然后把Lock Record中的owner指向对象,虚拟机将通过CAS把对象的Mark Word更新为指向Lock Record的指针。CAS更新成功则加锁成功修改标志位00,否则意味着存在竞争,虚拟机判断Mark word是否指向栈帧,如果是则表示线程已经拥有了对象锁,否则继续锁升级,修改锁的标志位10,后面的线程阻塞。 解锁时,通过CAS把对象当前的Mark Word与栈帧中的Lock Record替换回来,如果成功,同步过程顺利完成,否则表示其他线程尝试过获取该锁,就要在释放锁的同步唤醒挂起的线程。
③自旋锁和自适应自旋锁:Java线程与操作系统线程一一对应,当线程获取锁失败后,会由用户态切换到内核态挂起,线程被唤醒后,再有内核态切换到用户态,在一定程度影响并发性能。自旋锁是使用cpu时间换线程阻塞与调度开销,当线程获取锁失败后,不会马上阻塞自己,而是多次尝试获取锁资源,当达到获取次数上限才会被阻塞。自适应意味着线程自旋次数不再固定,如果在同一个锁对象上,自旋刚刚成功获取了锁,那么虚拟机会认为本次自旋也能成功获取锁,进而允许自旋更多次数。
锁消除:对于一些代码要求同步,但是被检测到不可能存在共享数据的竞争,因此可以消除锁。
锁粗化:如果一系列的连续操作都对同一个对象反复加锁解锁,则将把加锁同步的范围扩展到整个操作序列的外部。
二 Lock
(1)AQS概述
AQS里面维护了一个FIFO的双向队列和状态信息state。state=0表示锁空闲,当state=1表示锁被占有。
(2)ReentrantLock
ReentrantLock底层是使用AQS实现的可重入独占锁。state=0表示当前锁空闲,state>1表示锁已被占用。
(3)ReentrantReadWriteLock
ReentrantReadWriteLock使用state高16位表示获取到读锁的个数,低16位表示写锁的可重入次数,通过CAS操作实现读写分离。写锁是独占可重入锁,写锁是共享可重入锁。
三 synchronized与Lock区别
①synchronized是JVM层面的,Lock是JDK层面的。
②synchronized会自动释放锁,Lock需要手动释放锁。
③synchronized不可中断的,Lock可中断可不中断。
④Lock可以知道线程有没有拿到锁,synchronized不可以。
⑤synchronized可锁方法和代码块,Lock只能锁代码块。
⑥Lock可以使用读锁提高多线程读效率。
⑦synchronized是非公平锁,ReentrantLock可以设置是否为公平锁。