synchronized关键字用于为Java对象、方法、代码块提供线程安全的操作。synchronized属于独占式的悲观锁,同时也属于可重入锁。
使用synchronized修饰对象时,同一时刻只能有一个线程对该对象进行访问;
使用synchronized修饰方法、代码块时,同一个时刻只能有一个线程执行该方法或代码块;
Java中的每个对象都有一个monitor对象,加锁就是在竞争monitor对象。对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方法是否加锁是通过一个标志位来判断的。
synchronized的作用范围
-
用synchronized修饰成员变量或非静态方法,锁的是对象的实例,即this对象
-
用synchronized修饰静态方法,锁的是Class实例,因为静态方法是属于Class对象的
-
用synchronized修饰代码块,锁的是所有代码快()中配置的对象
轻量级锁加锁流程:
-
当Thread-1过来获取锁时,就会在当前的线程的栈帧中创建一个锁记录(Lock Record)
-
让锁记录中的Object Reference指向锁对象(Object),并尝试用CAS替换掉Object的Mark Word,将Mark Word的值存入锁记录
-
如果CAS替换成功,就将Mark Word中的HashCode、Age等信息替换成锁记录的地址,并且将锁标志位修改为00(表示轻量级锁),表示由改线程给对象加锁
-
如果CAS替换失败,有两种情况:
-
如果是当前线程持有锁,那就进行锁重入,
-
在栈帧中添加一个锁记录作为重入的计数,然后让锁记录中的Object Reference指向Object,
-
进行CAS替换Mark Word,但是这次会失败,因为锁已经被自己拿到了,所以新添加的锁记录中就不存放Mark Word信息了,而是null
-
解锁的时候,如果锁记录中的Mark Word为null,表明有重入,这个时候就把锁记录清掉,表示重入计数减1,到了最后一个锁记录的时候,Mark Word不为null,就用CAS将栈帧中的Mark Word还原回去Object的Mark Word,然后将锁标志位修改为01
-
如果解锁的过程中失败了,说明轻量级锁进行了锁膨胀升级为重量级锁,这个时候进入重量级锁解锁流程
-
-
如果是其他线程持有锁,这表明有竞争,就会进入锁膨胀过程
-
锁膨胀
当Thread-2进行轻量级加锁时,Thread-1已经对该对象加了轻量级锁了,这个时候Thread-1加轻量级锁失败,就进入锁膨胀流程
-
首先为Object申请Monitor锁,让Object中的Mark Word指向重量级锁(Monitor)地址,锁标志位修改为10
-
然后Thread-2就进入Monitor的EntryListBLOCKED(阻塞队列),Monitor的Owner就指向Thread-1
-
当Thread-1退出同步代码块进行解锁时,用CAS将Mark Word的值恢复给Object,会发现失败了,说明轻量级锁已经升级为了重量级锁了,这个时候就进行重量级锁的解锁过程:
-
获取Object中的Mark Word的Monitor地址,并找到Monitor对象
-
设置Monitor中的Owner为null,唤醒EntryList中阻塞的线程Thread-2
-
偏向锁
轻量级锁在重入的时候,每次都要进行CAS操作,会消耗CPU性能。
在Java 6中引入了偏向锁来做进一步的优化:只有第一次使用CAS的时候将线程ID设置到对象头的Mark Word中,之后重入的时候,发现线程ID是自己就表示是自己,不用重新CAS。
偏向锁流程:
- 一个锁对象刚刚开始创建的时候,没有任何线程来访问它,它是可偏向的,它现在认为只可能有一个线程来访问它,所以当第一个线程来访问他的时候,它会偏向这个线程。此时线程状态为无锁状态,锁标志位为 01,此时Mark Word如下图:
- 当一个线程(线程 A)来获取锁的时,会首先检查所标志位,此时锁标志位为 01,然后检查是否为偏向锁,此时不为偏向锁,所以当前线程会修改对象头状态为偏向锁,同时将对象头中的 ThreadID 改成自己的 Thread ID
- 如果再有一个线程(线程 B)过来,此时锁状态为偏向锁,该线程会检查 Mark Word 中记录的线程 ID 是否为自己的线程 ID,如果是,则获取偏向锁,执行同步代码块。如果不是,则利用 CAS 尝试替换 Mark Word 中的 Thread ID,成功,表示该线程(线程 B)获取偏向锁,执行同步代码块,此时 Mark Word 如下图:
-
如果CAS失败,说明当前存在竞争情况,进行偏向锁撤销流程(需要注意的是:偏向锁的释放并不是主动,而是被动的,执行同步代码块后并不会主动释放偏向锁,而是等待其他线程来竞争才会释放锁)
-
撤销偏向锁的操作需要等到全局安全点才会执行,然后暂停持有偏向锁的线程,同时检查该线程的状态:
-
如果该线程不处于活动状态或者已经退出同步代码块,则设置为无锁状态(线程 ID 为空,是否为偏向锁为 0 ,锁标志位为01)重新偏向,同时恢复该线程
-
若该线程活着,则会遍历该线程栈帧中的锁记录,检查锁记录的使用情况,如果仍然需要持有偏向锁,则撤销偏向锁,升级为轻量级锁
-
在升级为轻量级锁之前,持有偏向锁的线程(线程 A)是暂停的,JVM 首先会在原持有偏向锁的线程(线程 A)的栈中创建一个名为锁记录的空间(Lock Record),用于存放锁对象目前的 Mark Word 的拷贝,然后拷贝对象头中的 Mark Word 到原持有偏向锁的线程(线程 A)的锁记录中(官方称之为 Displaced Mark Word ),这时线程 A 获取轻量级锁,此时 Mark Word 的锁标志位为 00,指向锁记录的指针指向线程 A 的锁记录地址
-
网友评论