一、synchronized的特性
①对于“悲观乐观锁”,是自适应的。
②对于“重量轻量锁”,是自适应的。
③对于“自旋挂起等待锁”,是自适应的。
④不是读写锁。
⑤是可重入锁。
⑥是非公平锁。
初始情况下,synchronized如果预测到当前锁冲突的概率不大,就会以乐观锁的模式来运行(即轻量级锁,基于自旋锁的方式来实现),实际使用过程中,如果发现锁冲突的情况比较多,synchronized会升级为悲观锁(即重量级锁,基于挂起等待锁的方式来实现)。
二、synchronized的使用
synchronized要依赖锁对象同时搭配代码块使用。
Object lock = new Object();
synchronized (lock) {
//working
}
三、synchronized的锁机制
3.1锁升级
锁升级:无锁→偏向锁→自旋锁→重量级锁。
锁升级的过程是单向的,是JVM在性能和线程安全之间尽量做出的权衡。
偏向锁不是真的 “加锁”, 只是给锁的对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程。
如果后续没有其他线程来竞争该锁, 那么就不用进行其它操作了(避免了加锁解锁的开销)。
如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入轻量级锁状态。
这样就可以既能保证效率又能保证线程安全,偏向锁是“懒汉模式”的一种体现。
3.2锁消除
锁消除是一种编译器优化的手段,编译器会自动针对加锁的代码做出判断,如果编译器觉得某个场景下不需要加锁,此时就会把synchronized给优化掉,编译器只会在非常有把握的情况下才会进行锁消除(触发锁消除的概率不是很高)。
比如StringBuilder不带synchronized,StringBuffer带synchronized,如果是在单线程环境下使用StringBuffer,此时编译器会自动把synchronized优化掉。
锁消除是在编译阶段触发的,这时还没到运行阶段。偏向锁是发生在运行阶段的事情,如果在编译阶段无法判断这个锁是否需要消除,则这个锁只能保留到运行阶段。
3.3锁粗化
锁粗化也是一种编译器优化的手段。
synchronized代码块里面的代码越多锁的粒度就越粗,代码越少锁的粒度就越细。
粒度细的锁能够并发执行的逻辑更多,更有利于利用多核cpu资源。但是粒度细的锁被反复进行加锁解锁(涉及反复的锁竞争),可能实际效果还不如粒度粗的锁。
以下为粒度细的锁:
for(){
synchronized(lock){
n++;
}
}
以下为粒度粗的锁:
synchronized(lock){
for(){
n++;
}
}
比如以上锁的粗化避免了一些不必要的锁竞争,提高了程序的执行效率。