동기화 된 원리 분석 및 경량 잠금, 잠금 확장, 스핀 최적화, 편향 잠금, 잠금 제거

동기화 됨

Java HotSpot 가상 머신에서 모든 객체에는 객체 헤더 (클래스 포인터 및 Mark Word 포함)가 있습니다. Mark Word는 일반적으로이 객체의 해시 코드와 생성 연령을 저장하며, 잠금이 잠기면이 정보는 상황에 따라 마크 비트, 스레드 잠금 레코드 포인터, 헤비 웨이트 잠금 포인터, 스레드 ID 등으로 대체됩니다.

자바 객체 헤더는
예를 들어 32 비트 가상 머신을 가지고
일반 객체

배열 객체

Mark Word의 구조는 다음과 같습니다.

64 비트 가상 머신 Mark Word

모니터 원리

모니터는 모니터 또는 모니터로 변환됩니다.
각 Java 오브젝트는 모니터 오브젝트와 연관 ​​될 수 있습니다. 오브젝트가 동기화를 사용하여 잠긴 경우 (무거움) 오브젝트 헤더의 마크 워드는 모니터 오브젝트에 대한 포인터로 설정됩니다.
모니터 구조 다음과 같이

  • 처음에는 모니터의 소유자가 null입니다.
  • Thread-2가 동기화 (obj)를 실행하면 모니터의 소유자가 스레드 -2로 설정되며 모니터에는 소유자가 한 명만있을 수 있습니다.
  • Thread-2 잠금 과정에서 Thread-3, Thread-4, Thread-5도 동기화 (obj)를 실행하면 EntryList BLOCKED로 들어갑니다.
  • Thread-2는 동기화 코드 블록의 내용 실행을 마치고 EntryList에서 대기중인 스레드를 깨워 잠금을 위해 경쟁합니다.
  • 그림에서 WaitSet의 Thread-0 및 Thread-1은 이전에 잠긴 스레드이지만 조건이 충족되지 않고 WAITING 상태가되며 나중에 wait-notify에 대해 이야기 할 때 분석됩니다.

참고 :
위의 효과를 얻으려면 동기화 됨이 동일한 개체에 들어가는 모니터 여야합니다
. 동기화되지 않은 개체는 모니터와 연결되지 않으며 위의 규칙을 따르지 않습니다.

동기화 된 원리

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
synchronized (lock) {
counter++;
}
}

해당 바이트 코드는 다음과 같습니다.

public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #2 // <- lock引用 (synchronized开始)
3: dup
4: astore_1 // lock引用 -> slot 1
5: monitorenter // 将 lock对象 MarkWord 置为 Monitor 指针
6: getstatic #3 // <- i
9: iconst_1 // 准备常数 1
10: iadd // +1
11: putstatic #3 // -> i
14: aload_1 // <- lock引用
15: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
16: goto 24
19: astore_2 // e -> slot 2
20: aload_1 // <- lock引用
21: monitorexit // 将 lock对象 MarkWord 重置, 唤醒 EntryList
22: aload_2 // <- slot 2 (e)
23: athrow // throw e
24: return
Exception table:
from to target type
6 16 19 any
19 22 19 any
LineNumberTable:
line 8: 0
line 9: 6
line 10: 14
line 11: 24
LocalVariableTable:
Start Length Slot Name Signature
0 25 0 args [Ljava/lang/String;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 19
locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 

참고
바이트 코드 지침에 반영되지 않습니다 동기화 방법 수준을

고급 동기화 원리

Synchronized는 jdk1.6에서 최적화되었습니다. 최적화 포인트는 편향된 잠금, 경량 잠금, 헤비 웨이트 잠금, 스핀 등이지만 프로그래머가 동기화 작업을 수행 할 필요가 없으며 내부 최적화는 jvm의 맨 아래 계층에서 수행됩니다.

5.1 경량 잠금 

개체에 다중 스레드 액세스가 있지만 다중 스레드 액세스 시간이 시차 (즉, 경쟁이 없음)하면 경량
잠금 을 사용 하여 최적화 할 수 있습니다 . 그것은이처럼 다음
학생 (스레드 A는) 교과서와 함께 좌석을 점유 반 수업을했다, (CPU 시간이 다) 나가서 다시 와서 교과서가 변경되지 않았 음을 발견, 더 경쟁이 없음을 나타내는 및 그의 수업을 계속하십시오. 이 기간 동안 다른 학생 (스레드 B)이 오면 동시 액세스가 있음을 알리고 (스레드 A) 스레드 A는 헤비 웨이트 잠금으로 업그레이드되고 헤비 웨이트 잠금 프로세스에 들어갑니다.
무거운 자물쇠는 교과서를 사용하여 좌석을 차지하는 것만 큼 간단하지 않습니다. 실 A가 떠나기 전에 좌석이 철 울타리로 둘러싸여 있다고 상상할 수 있습니다.
블록을 동기화하고 동일한 객체를 사용하여 잠그는 두 가지 방법이 있다고 가정 합니다.

static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块 A
method2();
}
}
public static void method2() {
synchronized( obj ) {
// 同步块 B
}
}

각 스레드의 스택 프레임에는 잠금 레코드 구조가 포함되며 잠긴 개체의 Mark Word는 내부에 저장 될 수 있습니다.

잠금 레코드의 Object 참조가 잠금 개체를 가리 키도록하고 Mark Word of Object를 cas로 바꾸고 Mark Word 값을 잠금 레코드에 저장합니다.

CAS 교체가 성공하면 잠금 레코드 주소와 상태 00이 객체 헤더에 저장되어 스레드가 객체를 잠그는 것을 의미합니다.이 때 다이어그램은 다음과 같습니다.

CAS가 실패하면 두 가지 상황이 있습니다.

  • 다른 스레드가 이미 Object의 경량 잠금을 보유하고있는 경우 경쟁이 있음을 표시하고 잠금 확장 프로세스에 들어갑니다.
  • 동기화 된 잠금 재진입을 직접 수행하는 경우 재진입 횟수로 다른 잠금 레코드를 추가하십시오.
     

동기화 된 코드 블록을 빠져 나갈 때 (잠금 해제시) null 값의 잠금 레코드가 있으면 재진입이 있음을 의미합니다.이 때 잠금 레코드를 재설정하면 재진입 횟수가 1 감소합니다.

동기화 된 코드 블록을 종료 할 때 (잠금 해제시) 잠금 레코드의 값이 null이 아닌 경우 cas를 사용하여 Mark Word의 값을 개체 헤더로 복원합니다.

  • 성공하면 잠금 해제가 성공합니다.
  • 실패, 경량 잠금 장치가 잠금 확장을 거쳤거나 헤비 웨이트 잠금 장치로 업그레이드되었음을 나타냅니다. 헤비 웨이트 잠금 잠금 해제 프로세스를 시작합니다.
     

  

5.2 잠금 확장

경량 잠금을 추가하려는 과정에서 CAS 작업이 성공할 수 없습니다. 이때 다른 스레드가이 객체에 경량 잠금을 추가 한 상태입니다 (경쟁 포함). 이때 잠금 확장이 필요합니다. , 무게 잠금 장치는 중량 잠금 장치가됩니다.

static Object obj = new Object();
public static void method1() {
synchronized( obj ) {
// 同步块
}
}

Thread-1이 경량 잠금을 수행 할 때 Thread-0은 이미 객체에 경량 잠금을 추가했습니다.
 

이때 Thread-1 plus 경량 잠금이 실패하고 잠금 확장 프로세스에 들어갑니다.

  • 즉, Object 개체에 대한 Monitor 잠금을 적용하고 Object가 Heavyweight Lock 주소를 가리 키도록합니다.
  • 그런 다음 모니터의 EntryList BLOCKED를 직접 입력하십시오.
     

Thread-0이 잠금 해제를 위해 동기화 블록을 종료하면 cas를 사용하여 Mark Word의 값을 개체 헤더로 복원하면 실패합니다. 이때 무거운 잠금 해제 과정으로 진입한다. 즉, Monitor 주소에 따라 Monitor 객체를 찾고 Owner를 null로 설정하고 EntryList에서 BLOCKED 쓰레드를 깨운다.

5.3 스핀 최적화

무거운 잠금이 경쟁 할 때 스핀을 사용하여 최적화 할 수도 있습니다. 현재 스레드가 성공적으로 회전하면 (즉, 잠금 보유 스레드가 현재 동기화 블록을 종료하고 잠금을 해제 한 경우) 현재 스레드는 차단을 피할 수 있습니다. Java 6 이후 스핀 잠금은 적응 형입니다. 예를 들어 객체가 방금 스핀 작업에 성공한 경우이 스핀 성공 가능성이 높은 것으로 간주되므로 몇 번 더 스핀합니다. 스핀 없음, 짧게 말하면 오래 기다릴 수있는 스마트하고 비용 효율적입니다.
Java 7 이후 에는 스핀 기능이 켜져 있고 스핀 재 시도가 성공할지 여부를 제어 할 수 없습니다.

  • 스핀은 CPU 시간을 차지합니다. 단일 코어 CPU 스핀은 낭비이며 멀티 코어 CPU 스핀은이를 활용할 수 있습니다.
  • Java 6 이후 스핀 잠금은 적응 형입니다. 예를 들어 객체가 방금 스핀 작업에 성공한 경우이 스핀 성공 가능성이 높은 것으로 간주되므로 몇 번 더 스핀합니다. 짧게 말하면 더 지능적입니다.
  • Java 7 이후 스핀 기능을 켤지 여부를 제어 할 수 없습니다.
     

5.4 바이어스 잠금

경량 잠금은 경쟁이 없을 때 (자체 스레드에서) 재진입 할 ​​때마다 CAS 작업을 수행해야합니다.

Java 6은 추가 최적화를 위해 편향된 잠금을 도입했습니다. CAS를 처음 사용하여 스레드 ID를 객체의 Mark Word 헤더로 설정 한 후 스레드 ID가 고유 한 것으로 확인되어 경쟁이 없음을 의미합니다. , 다시 CAS를 할 필요가 없습니다. 미래에 경쟁이없는 한이 객체는 스레드가 소유합니다.

자신의 것이라면 경쟁이 없으며 다시 CAS를 사용할 필요가 없음을 의미합니다.

  • 바이어스를 취소하려면 잠금 유지 스레드를 경량 잠금으로 업그레이드해야합니다.이 프로세스 동안 모든 스레드를 일시 중지 (STW)해야합니다.
  • 객체의 hashCode에 액세스하면 바이어스 잠금도 취소됩니다.
  • 객체가 여러 스레드에 의해 액세스되지만 경쟁이없는 경우 스레드 T1에 바이어스 된 객체는 여전히 T2에 다시 바이어스 할 수 있습니다.
  • 재 편향은 객체의 스레드 ID를 재설정합니다.
  • 해지 편향 및 재 편향은 모두 클래스를 단위로 배치로 수행됩니다.
  • 해지 편향이 특정 임계 값에 도달하면 전체 클래스의 모든 객체가 편향되지 않습니다.
  • -XX : -UseBiasedLocking을 적극적으로 사용하여 편향 잠금을 비활성화 할 수 있습니다.

이 문서를 참조 할 수 있습니다. https://www.oracle.com/technetwork/java/biasedlocking-oopsla2006-wp-149958.pdf
동일한 객체를 사용하여 블록을 동기화하는 두 가지 방법이 있다고 가정합니다.

tatic final Object obj = new Object();
public static void m1() {
synchronized( obj ) {
// 同步块 A
m2();
}
}
public static void m2() {
synchronized( obj ) {
// 同步块 B
m3();
}
}
public static void m3() {
synchronized( obj ) {
// 同步块 C
}
}

편향된 상태

개체 헤더 형식 불러 오기

 

객체가 생성되면 :

  • 바이어스 잠금이 활성화 된 경우 (기본적으로 활성화 됨) 객체가 생성 된 후 마크 워드 값은 0x05입니다. 즉, 마지막 3 자리 숫자는 101이고 스레드, 에포크 및 나이는 모두 0입니다.
  • 바이어스 잠금은 기본적으로 지연되며 프로그램 시작시 즉시 적용되지 않습니다. 지연을 피하려면 VM 매개 변수 -XX : BiasedLockingStartupDelay = 0을 추가하여 지연을 비활성화 할 수 있습니다.
  • 바이어스 잠금이 설정되어 있지 않은 경우 객체 생성 후 마크 워드 값은 0x01, 즉 마지막 3 자리는 001입니다. 이때 해시 코드와 나이는 모두 0이며 다음과 같은 경우 값이 할당됩니다. 해시 코드가 처음으로 사용됨

1) 테스트 지연 특성


2) 바이어스 잠금 테스트

class Dog {}
jol 타사 도구를 사용하여 개체 헤더 정보를 봅니다 (출력을 더 간결하게 만들기 위해 jol을 확장했습니다).

// 添加虚拟机参数 -XX:BiasedLockingStartupDelay=0
public static void main(String[] args) throws IOException {
Dog d = new Dog();
ClassLayout classLayout = ClassLayout.parseInstance(d);
new Thread(() -> {
log.debug("synchronized 前");
System.out.println(classLayout.toPrintableSimple(true));
synchronized (d) {
log.debug("synchronized 中");
System.out.println(classLayout.toPrintableSimple(true));
}
log.debug("synchronized 后");
System.out.println(classLayout.toPrintableSimple(true));
}, "t1").start()
}

산출

11 : 08 : 58.117 c. TestBiased [t1]-동기화 됨 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
11 : 08 : 58.121 c. TestBiased [t1]-동기화 됨 中
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101
11 : 08 : 58.121 c .TestBiased [t1]-동기화 됨 后
00000000 00000000 00000000 00000000 00011111 11101011 11010000 00000101

참고
바이어스 잠금의 개체가 잠금 해제 된 후에도 스레드 ID는 개체 헤더에 계속 저장됩니다.

3) 테스트 비활성화

위의 테스트 코드가 실행 중이면 VM 매개 변수 -XX : -UseBiasedLocking을 추가하여 편향된 잠금
출력 을 비활성화합니다.

11 : 13 : 10.018 c. TestBiased [t1]-동기화 됨 前
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
11 : 13 : 10.021 c. TestBiased [t1]-동기화 됨 中
00000000 00000000 00000000 00000000 00100000 00010100 11110011 10001000
11 : 13 : 10.021 c .TestBiased [t1]-동기화 됨 后
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

4) hashCode 테스트

정상 상태 객체는 처음에 hashCode가 없으며 첫 번째 호출 이후에만 생성됩니다.

Undo-call 객체 hashCode

객체의 hashCode가 호출되지만 스레드 id는 바이어스 잠금의 객체 MarkWord에 저장되어 있습니다. 해시 코드가 호출되면 바이어스 잠금이
해제됩니다.

  • 경량 잠금은 잠금 레코드에 hashCode를 기록합니다.
  • Heavyweight 잠금은 모니터에 hashCode를 기록합니다.

hashCode 호출 후 편향된 잠금을 사용하고 -XX : -UseBiasedLocking
출력 을 제거해야 합니다.

11 : 22 : 10.386 c.TestBiased [main]-调用 hashCode : 1778535015
11 : 22 : 10.391 c.TestBiased [t1]-동기화 된 前
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001
11 : 22 : 10.393 c.TestBiased [t1]- 동기화 됨 中
00000000 00000000 00000000 00000000 00100000 11000011 11110011 01101000
11 : 22 : 10.393 c. TestBiased [t1]-동기화 됨 后
00000000 00000000 00000000 01101010 00000010 01001010 01100111 00000001

실행 취소-다른 스레드는 객체를 사용합니다.
다른 스레드가 편향된 잠금 객체를 사용하면 편향된 잠금이 경량 잠금으로 업그레이드됩니다.

private static void test2() throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
synchronized (TestBiased.class) {
TestBiased.class.notify();
}
// 如果不用 wait/notify 使用 join 必须打开下面的注释
// 因为:t1 线程不能结束,否则底层线程可能被 jvm 重用作为 t2 线程,底层线程 id 是一样的
/*try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}*/
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (TestBiased.class) {
try {
TestBiased.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}, "t2");
t2.start();
}

산출

[t1]-00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2]-00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2]-00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] -00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001

취소-통화 대기 / 알림

ublic static void main(String[] args) throws InterruptedException {
Dog d = new Dog();
Thread t1 = new Thread(() -> {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
try {
d.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t1");
t1.start();
new Thread(() -> {
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (d) {
log.debug("notify");
d.notify();
}
}, "t2").start();
}

산출

[t1]
-00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101 [t1]-00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2]-알림
[t1]-00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010

일괄 재 편향

  • 객체가 여러 스레드에 의해 액세스되지만 경쟁이없는 경우 스레드 T1에 바이어스 된 객체는 여전히 T2로 바이어스 될 기회가 있으며, 다시 바이어스하면 객체의 스레드 ID가 재설정됩니다.
  • 편향된 잠금을 취소하는 임계 값이 20 회를 초과하면 jvm은 이런 느낌이들 것입니다. 내가 틀렸나 요? 따라서 이러한 객체를 잠글 때 잠금 스레드에 다시 바이어스됩니다.
private static void test3() throws InterruptedException {
Vector<Dog> list = new Vector<>();
Thread t1 = new Thread(() -> {
for (int i = 0; i < 30; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
synchronized (list) {
list.notify();
}
}, "t1");
t1.start();
Thread t2 = new Thread(() -> {
synchronized (list) {
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug("===============> ");
for (int i = 0; i < 30; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t2");
t2.start();
}

산출

[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101

일괄 취소
취소 바이어스 잠금 임계 값이 40 배를 초과하면 JVM은 실제로 바이어스되어 전혀 바이어스되지 않아야한다고 느낍니다. 따라서 전체 클래스의 모든 객체는 편향되지 않으며 새 객체도 편향되지 않습니다.

static Thread t1,t2,t3;
private static void test4() throws InterruptedException {
Vector<Dog> list = new Vector<>();
int loopNumber = 39;
t1 = new Thread(() -> {
for (int i = 0; i < loopNumber; i++) {
Dog d = new Dog();
list.add(d);
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}
LockSupport.unpark(t2);
}, "t1");
t1.start();
t2 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
LockSupport.unpark(t3);
}, "t2")
t2.start();
t3 = new Thread(() -> {
LockSupport.park();
log.debug("===============> ");
for (int i = 0; i < loopNumber; i++) {
Dog d = list.get(i);
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
synchronized (d) {
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
}
}, "t3");
t3.start();
t3.join();
log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

 

참조 자료
https://github.com/farmerjohngit/myblog/issues/12
https://www.cnblogs.com/LemonFive/p/11246086.html
https://www.cnblogs.com/LemonFive/p/11248248. HTML
바이어스 잠금 용지

5.5 잠금 제거

@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
static int x = 0;
@Benchmark
public void a() throws Exception {
x++;
}
@Benchmark
public void b() throws Exception {
Object o = new Object();
synchronized (o) {
x++;
}
}
}

java -jar benchmarks.jar

벤치 마크 모드 샘플 점수 점수 오류 단위
ciMyBenchmark.a avgt 5 1.542 0.056 ns / op
ciMyBenchmark.b avgt 5 1.518 0.091 ns / op

java -XX : -EliminateLocks -jar benchmarks.jar

벤치 마크 모드 샘플 점수 점수 오류 단위
ciMyBenchmark.a avgt 5 1.507 0.108 ns / op
ciMyBenchmark.b avgt 5 16.976 1.572 ns / op

잠금 조 대화
동일한 객체를 여러 번 잠그면 여러 재진입 스레드가 발생합니다. 잠금 조 대화 방법을 사용하여 최적화 할 수 있습니다. 이는 앞에서 언급 한 세분화 된 잠금의 세분성과 다릅니다.

기타 최적화

1. 잠금 시간 단축

동기화 코드를 최대한 짧게 유지하십시오.
2.
잠금 세분성을 줄이십시오. 동시성을 향상시키기 위해 잠금을 여러 잠금으로 분할하십시오. 예를 들면 다음과 같습니다. 

CAS를 사용하여 기본 값을 누적하고, 동시 경합이 있고, 셀 배열이 초기화되고, 배열에있는 셀 수, 병렬로 수정할 수있는 스레드 수, 마지막으로 배열의 각 셀이 누적, 더하기 base는 최종 값입니다. LinkedBlockingQueue는 대기열에 추가 및 대기열에서 빼기 위해 서로 다른 잠금을 사용합니다. LinkedBlockingArray와 비교할 때 하나의 잠금 만 더 효율적입니다.

3. 锁粗 화

동기화 된 블록에 대한 다중 루프는 동기화 된 블록의 다중 루프만큼 좋지 않습니다. 또한 JVM은 여러 추가의 잠금 작업을 하나로 조잡하게하기 위해 다음과 같은 최적화를 수행 할 수 있습니다 (모두 동일한 객체를 잠그기 때문에 여러 번 재 입력 할 필요 없음)

new StringBuffer (). append ( "a"). append ( "b"). append ( "c");

4. 잠금 제거
JVM은 코드 이스케이프 분석을 수행합니다. 예를 들어, 잠긴 객체는 메서드의 로컬 변수이며 다른 스레드에서 액세스하지 않습니다. 이때 모든 동기화 작업은 Just-In-Time에서 무시됩니다. 컴파일러.
5. 읽기 및 쓰기 분리
CopyOnWriteArrayList ConyOnWriteSet
참조 :
https://wiki.openjdk.java.net/display/HotSpot/Synchronization
http://luojinping.com/2015/07/09/java 잠금 최적화 /
https : // www . infoq.cn/article/java-se-16-synchronized
https://www.jianshu.com/p/9932047a89be
https://www.cnblogs.com/sheeva/p/6366782.html
https : // stackoverflow. com / questions / 46312817 / does-java-ever-rebias-an-individual-lock

 

동기화 된 다이어그램으로 이동하십시오.

https://www.processon.com/view/5fac7e7e5653bb4cab72970d

추천

출처blog.csdn.net/nmjhehe/article/details/109501999