一、synchronized关键字
1.1、锁的状态
(1)无锁:在没有开启偏向锁或者偏向锁延迟未到或者批量撤销后,对象创建完成后,没有任何线程使用该对象来加锁,此时该对象的状态是无锁。对应的JAVA对象头lock=01,biased_lock=0。(biased_lock=0的含义表示不可偏向)
(2)偏向锁:对象创建完成后处于可偏向状态,某一个线程使用该对象来加锁,那么这个对象就偏向这个线程。对应的JAVA对象头lock=01,biased_lock=1。
(3)轻量级锁:当有多个线程交替使用某个对象来加锁,其中并无竞争的情况下,该对象会成为轻量级锁。对应的JAVA对象头lock=00。
(4)重量级锁:当有多个线程竞争使用某个对象来加锁,改对象会成为重量级锁。对应的JAVA对象头lock=10。
1.2、JVM相关参数
UseBiasedLocking:是否使用偏向锁,默认true
BiasedLockingStartupDelay:偏向锁开启延迟时间,默认4000ms
BiasedLockingBulkRebiasThreshold:偏向锁重偏向阈值,默认20次
BiasedLockingBulkRevokeThreshold:偏向锁撤销阈值,默认40次
1.3、synchronized关键字执行流程
下图为synchronized关键字执行流程,不考虑重偏向和批量撤销的情况。
注:
1、无锁(lock为01,biased_lock为0,不可偏向状态)状态没有任何路径升级为偏向锁状态;
2、轻量锁和重量锁在synchronized代码块执行完毕后会将锁对象重置为无锁状态,而偏向锁在执行完毕后不会重置锁的状态。
3、升级路径只有无锁或偏向锁升级为轻量级锁和轻量级锁升级为重量级锁。
4、在偏向锁升级为轻量级锁时,会进行锁撤销操作,将锁的状态修改为无锁状态,然后再升级。
1.4、重偏向
当某一个类的产生的对象连续多次执行了锁的撤销操作,默认是20次。JVM会修改这个class类的epoch值+1,并且遍历当前堆栈,找出该class类产生的对象,将正在处于偏向锁状态且未退出同步代码块的对象的对象头的epoch字段也+1。这样在堆栈中,class类产生的对象处于偏向锁状态且已经退出同步代码块的对象的对象头的epoch字段和class中的epoch字段就会不一致。下次如果有其他线程再次使用这些对象去加锁,那么JVM会让这些对象进行重偏向到后面的线程。
示例代码:
import java.util.ArrayList;
import java.util.List;
import org.openjdk.jol.info.ClassLayout;
/**
* 重偏向
* @author ZTP
*
*/
public class BiasedLockingBulkRebiasDemo {
public static void main(String[] args) throws Exception {
Thread.sleep(5000); // 因为偏向锁有延迟4000ms,这里必须先睡眠4000ms以上
List<Object> lockList = new ArrayList<>();
Thread t1 = new Thread() {
@Override
public void run() {
for(int i = 0; i < 30; i++) {
Object lock = new Object();
lockList.add(lock);
synchronized (lock) {
System.out.println("t1 :" + i);
System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 偏向锁,101
}
}
}
};
t1.start();
t1.join();
Thread t2 = new Thread() {
@Override
public void run() {
for(int i = 0; i < 30; i++) {
Object lock = lockList.get(i);
synchronized (lock) {
System.out.println("t2 :" + i);
System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 前20个是轻量级锁,00。后面的都会重偏向,是偏向锁101
}
}
}
};
t2.start();
t2.join();
}
}
1.5、批量撤销
当某一个类的产生的对象连续多次执行了锁的撤销操作,默认是40次。JVM会认为这个类产生的对象没必要再使用偏向锁了。JVM遍历当前堆栈,找出该class类产生的对象,将正在处于偏向锁状态且未退出同步代码块的对象批量撤销并升级为轻量级锁。在后续在使用该类的对象加锁时,就算它是可偏向的,也不会将其偏向,而是直接变成轻量级锁。之后的加锁都是轻量级锁。
注意:之后这个类新创建的对象直接就是无锁不可偏向状态,即使过了延迟时间且UseBiasedLocking参数是true。
示例代码:
import java.util.ArrayList;
import java.util.List;
import org.openjdk.jol.info.ClassLayout;
/**
* 批量撤销
* @author ZTP
*
*/
public class BiasedLockingBulkRevokeDemo {
public static void main(String[] args) throws Exception {
Thread.sleep(5000); // 因为偏向锁有延迟4000ms,这里必须先睡眠4000ms以上
List<Object> lockList = new ArrayList<>();
Thread t1 = new Thread() {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
Object lock = new Object();
lockList.add(lock);
synchronized (lock) {
System.out.println("t1 :" + i);
System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 偏向锁,101
}
}
}
};
t1.start();
t1.join();
Thread t2 = new Thread() {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
Object lock = lockList.get(i);
synchronized (lock) {
System.out.println("t2 :" + i);
System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 前20个是轻量级锁,00。后面的都会重偏向,是偏向锁101
}
}
}
};
t2.start();
t2.join();
Thread t3 = new Thread() {
@Override
public void run() {
for(int i = 0; i < 100; i++) {
Object lock = lockList.get(i);
synchronized (lock) {
System.out.println("t3 :" + i);
System.out.println(ClassLayout.parseInstance(lock).toPrintable()); // 前20个是t2线程撤销的轻量级锁还原成了无锁,再次加为了轻量级锁,00。
// 21-40个是t3线程撤销的20个轻量级锁,00。(此时达到40个阈值)
// 41-100个是在达到阈值后不会再次重偏向,而是直接升级为轻量级锁,00。
// 这100个虽然都是轻量级锁,但是产生的路径不一样
}
}
}
};
t3.start();
t3.join();
}
}