Sync全部知识
基础:对象JOL相关内容:
无锁: 0 01
偏向锁: 1 01
轻量锁: 0 00
重量: 0 10
GC: 1 0 01
用JOL测试打印对象头的时候需要注意一个问题,JVM启动时会默认开启5s的偏向锁启用延迟,前5s是没有偏向锁的,这时调用sync得到的轻量锁,可以通过JVM的参数来禁用延迟-XX:BiasedLockingStartupDelay=0。
三种锁的性能对比:
package org.example.markWord;
import org.openjdk.jol.info.ClassLayout;
import static java.lang.System.out;
//偏向锁运行时长:1763ms ++++关闭JVM启动时偏向锁的延迟启动 -XX:BiasedLockingStartupDelay=0
//轻量级锁运行时长:16055ms
public class JOLExample5 {
public static void main(String[] args) throws Exception {
// Thread.sleep(5000);
A a = new A();
long start = System.currentTimeMillis();
//调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
//如果不出意外,结果灰常明显
for (int i = 0; i < 1000000000l; i++) {
a.add();
}
long end = System.currentTimeMillis();
String format = String.format("%sms", end - start);
System.out.println(String.format("%sms", end-start));
}
}
package org.example.markWord;
import java.util.concurrent.CountDownLatch;
//重量级锁运行时长:31324ms
public class JOLExample6 {
static CountDownLatch countDownLatch = new CountDownLatch(1000000000);
public static void main(String[] args) throws Exception {
final A a = new A();
long start = System.currentTimeMillis();
//调用同步方法1000000000L 来计算1000000000L的++,对比偏向锁和轻量级锁的性能
//如果不出意外,结果灰常明显
for(int i=0;i<2;i++){
new Thread(){
@Override
public void run() {
while (countDownLatch.getCount() > 0) {
a.parse();
}
}
}.start();
}
countDownLatch.await();
long end = System.currentTimeMillis();
System.out.println(String.format("%sms", end-start));
}
}
已经计算hashCode的对象不能实现偏向锁
偏向锁中需要存储线程相关信息,要占用54位,但hashCode已在对象头中占用,此时调用sync将直接变成轻量锁。
几种同步方式
单线程执行
多线程交替执行(锁不会膨胀升级,只会经历偏向锁,轻量级锁)
多线程竞争执行(当竞争时,一个线程自旋等待锁的时间,超过设定的值(据说一个线程上下文切换的时间),这个时候锁就会膨胀升级)
在sync内调用wait方法,锁会立刻变成重量级锁
sync的批量重偏向和批量撤销
package com.luban.layout;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class JOLExample12 {
static List list = new ArrayList();
public static void main(String[] args) throws Exception {
Thread t1 = new Thread() {
public void run() {
for (int i=0;i<40;i++){
A a = new A();
synchronized (a){
System.out.println(“111111”);
list.add(a);
}
}
}
};
t1.start();
t1.join();
out.println(“befre t2”);
//偏向
out.println(ClassLayout.parseInstance(list.get(1)).toPrintable());
Thread t2 = new Thread() {
int k=0;
public void run() {
for(A a:list){
synchronized (a){
System.out.println(“22222”);
if (k==19){
out.println(“t2 ing”);
//轻量锁
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
k++;
}
}
};
t2.start();
}
}
这里批量重偏向阈值是20次,当超过20次时,已使用过的锁对象会置为无锁,后续的锁对象会改成偏向线程2;
偏向锁批量撤销是当阈值40次,当有第三个线程来竞争时,不会重偏向,而是撤销偏向锁,直接膨胀为轻量级锁(待验证)
正常创建一个对象,是可偏向,的状态
package com.luban.layout;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.vm.VM;
import static java.lang.System.out;
public class JOLExample1 {
// static A a = new A();
public static void main(String[] args) throws InterruptedException {
Thread.sleep(10000);
A a = new A();
// A a = new A();
//jvm的信息
out.println(VM.current().details());
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
如果不关闭默认偏向锁延迟开启(此处为注释掉sleep)
此时状态为无锁不可偏向
锁从轻量级锁或重量级锁变成无锁时,无法再次变成偏向锁。
sync的对象不能重偏向,但这种情况下,会触发偏向锁,应该属于JVM线程复用(猜测,案例证明猜测)
package com.luban.layout;
import org.openjdk.jol.info.ClassLayout;
import java.util.ArrayList;
import java.util.List;
import static java.lang.System.out;
public class JOLExample13 {
static A a;
public static void main(String[] args) throws Exception {
a= new A();
Thread t1 = new Thread() {
public void run() {
synchronized (a){
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t1.start();
t1.join();
Thread t2= new Thread() {
public void run() {
synchronized (a){
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t2.start();
t2.join();
Thread temp1 = new Thread(){
@Override
public void run() {
super.run();
}
};
temp1.start();
Thread t3 = new Thread() {
public void run() {
synchronized (a){
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
};
t3.start();
t3.join();
out.println(“after…”);
out.println(ClassLayout.parseInstance(a).toPrintable());
}
}
偏向锁是通过CAS比较产生,第二次这个线程过来,通过简单判断(比如if())来偏向,以提高效率,如果线程不对,继续CAS操作,每次CAS操作产生轻量级锁,所以轻量级锁性能较低。
偏向锁的批量重偏向和批量撤销原理
批量重偏向阈值为20,批量撤销阈值为40
当同一个类型的锁对象成为偏向锁后,被第二个线程同步,会调用计数器,当达到阈值20时,epoch值会+1,变成无效状态。每次同步之前,会先判断epoch值是否有效,和第一次的一致,如果不一致(无效),会撤销偏向锁,重新偏向线程2。如果一致,则通过CAS变成轻量级锁。
当第三个线程加入竞争时,如果达到阈值40,sync认为该锁对象存在严重问题,会批量撤销偏向锁。
从我的语雀笔记复制过来,图片懒得弄了,有兴趣可以移步:https://www.yuque.com/aichimaodeyu-yhsuu/mepo5n/pivm9u