在java并发中synchronized一直是一个重要的角色,有人称它为重量级锁,但在jdk1.6之后synchronized得到了优化,引入了偏向锁和轻量级锁,避免线程上下文切换带来的耗时,所以看起来就没有那么重了。
因synchronized锁信息都是保存在对象头部中,故而先从对象头入手。
对象的组成:
在优化synchronized过程中大量使用到CAS操作。CAS全称(Compare And Set),CAS是一种乐观锁操作并且包含三个操作数:内存位置(V)、原值(A)、新值(B)。当条件有且只能满足V与A相等时,才会将B赋值给A。
AtomicInteger当中常用的自增方法 incrementAndGet:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next)) return next;
}
}
private volatile int value;
public final int get() {
return value;
}
CAS的自旋。循环体当中做了三件事:
一个对象虽然有多个线程加锁,但是加锁的时间是错开的(也就是没有锁竞争),那么会使用轻量级锁来优化,轻量级锁是透明的,语法仍然是synchronized
static final Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}
结果如图mark word信息000000000370f0f0转为二进制11011100001111000011110000为轻量级锁:
如果在尝试加轻量级锁的过程中,CAS 操作无法成功,这时一种情况就是有其它线程为此对象加上了轻量级锁(有竞争),这时需要进行锁膨胀,将轻量级锁变为重量级锁。
重量级锁竞争的时候,还可以使用自旋来进行优化,如果当前线程自旋成功(即这时候持锁线程已经退出了同步块,释放了锁),这时当前线程就可以避免阻塞。
自旋成功情况:
线程 1 (core 1 上) | 对象 Mark Word | 线程 2 (core 2 上) |
---|---|---|
- | 10(重量锁) | - |
访问同步块,获取 monitor | 10(重量锁) | - |
成功(加锁) | 10(重量锁) | - |
执行同步块 | 10(重量锁) | - |
执行同步块 | 10(重量锁) | 访问同步块,获取 monitor |
执行同步块 | 10(重量锁) | 自旋重试 |
执行完毕 | 10(重量锁) | 自旋重试 |
成功(解锁) | 01(无锁) | 自旋重试 |
- | 10(重量锁) | 成功(加锁) |
- | 10(重量锁) | 执行同步块 |
自旋失败情况:
线程 1 (core 1 上) | 对象 Mark Word | 线程 2 (core 2 上) |
---|---|---|
- | 10(重量锁) | - |
访问同步块,获取 monitor | 10(重量锁) | - |
成功(加锁) | 10(重量锁) | - |
执行同步块 | 10(重量锁) | - |
执行同步块 | 10(重量锁) | 访问同步块,获取 monitor |
执行同步块 | 10(重量锁) | 自旋重试 |
执行同步块 | 10(重量锁) | 自旋重试 |
执行同步块 | 10(重量锁) | 自旋重试 |
执行同步块 | 10(重量锁) | 阻塞 |
执行同步块 | 10(重量锁) | 阻塞 |
轻量级锁在没有竞争时(就当前自己这把锁),为了避免每次获取锁都需要多次执行cas原子指令操作,jdk1.6之后引入了偏向锁。
偏向锁执行步骤:
public static void main(String[] args) {
Object o=new Object();
//o.hashCode();
new Thread(()->{
System.out.println("synchronized前..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o){
System.out.println("synchronized中..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
System.out.println("synchronized后..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}).start();
}
偏向锁默认是延迟的,程序启动不会立即生效,启用参数-XX:BiasedLockingStartupDelay=0
禁用延迟,运行如上代码结果如图:
如上偏向锁程序打开注释调用Object的hashCode()方法,代码就不贴出了,直接查看执行结果:
调用Object的hashCode()方法会撤销偏向锁,因hashCode值wark word中为31位,线程id是54位,存储空间不够,所以会转为轻量级锁,将hashCode值存入当前线程的锁记录中。
private static Thread a,b;
public static void main(String[] args) {
Object o=new Object();
a = new Thread(() -> {
synchronized (o) {
System.out.println("a线程:" + ClassLayout.parseInstance(o).toPrintable());
}
LockSupport.unpark(b);//唤醒线程
}, "a");
a.start();
b = new Thread(() -> {
LockSupport.park();//阻塞线程
System.out.println("synchronized前..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
synchronized (o) {
System.out.println("synchronized中..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}
System.out.println("synchronized后..");
System.out.println(ClassLayout.parseInstance(o).toPrintable());
}, "b");
b.start();
}
启动程序设置jvm参数-XX:+PrintFlagsFinal
public static void main(String[] args) throws InterruptedException {
Thread.sleep(3000);
List<Object> list = new ArrayList<>();
Thread a = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Object o = new Object();
list.add(o);
synchronized (o) {
System.out.println("a"+i + "\t" + ClassLayout.parseInstance(o).toPrintable());
}
}
synchronized (list){
list.notify();
}
}, "a");
a.start();
Thread b = new Thread(() -> {
synchronized (list){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int i = 0; i < 30; i++) {
Object o = list.get(i);
synchronized (o) {
if( i==19 || i==20) {
System.out.println("b" + i + "\t" + ClassLayout.parseInstance(o).toPrintable());
}
}
}
}, "b");
b.start();
//b.join();
//System.out.println(ClassLayout.parseInstance(new Object()).toPrintable());
}
当撤销偏向锁阈值超过 40 次后,jvm 会这样觉得,自己确实偏向错了,根本就不该偏向。于是整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的。