第二章 深入synchronized关键字

1.引言

线程安全是并发编程的重中之重,造成并发问题的诱因主要有两点。
1.存在临界资源
2.存在多条线程同时操作共享数据
基于这种情况,我们会考虑一种机制,使得一个线程访问临界资源的时候,其他线程不能对其访问,这种机制呢就称为互斥锁。java中的synchronized就是这样一种机制,其修饰的代码块同时只能有一个线程可以访问,因此保证了安全性,同时synchronized还可保证一个线程的变化(主要是共享数据的变化)被其他线程所看到,也就是说同步代码块中的变量无需使用volatile关键字。

2.synchronized的三种使用方式

修饰实例方法,作用于当前实例加锁,进入同步代码前要获得当前实例的锁
修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁
修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

package com.hqa.design.test;

import java.util.concurrent.CountDownLatch;

public class Test {

	/**
	 * 一把实例锁
	 */
	private static final Object lock = new Object();

	/**
	 * 静态方法锁,作用于Test.class对象
	 */
	public static synchronized void classLock() {
		try {
			System.out.println(Thread.currentThread().getName() + "进入classLock方法,休眠5s");
			Thread.sleep(5000);
			System.out.println(Thread.currentThread().getName() + "退出classLock方法");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 对象实例锁,作用于当前Test类实例
	 */
	public synchronized  void thisLock() {
			try {
				System.out.println(Thread.currentThread().getName() + "进入thisLock方法,休眠5s");
				Thread.sleep(5000);
				System.out.println(Thread.currentThread().getName() + "退出thisLock方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}		
	}

	/**
	 * 加锁对象为lock
	 */
	public void objLock() {
		synchronized (lock) {
			try {
				System.out.println(Thread.currentThread().getName() + "进入objLock方法,休眠5s");
				Thread.sleep(5000);
				System.out.println(Thread.currentThread().getName() + "退出objLock方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	public static void main(String[] args) throws InterruptedException {

		//声明一个闭锁,这个类的作用现在不讲,大家只需知道此类此处
		//是为了让子线程全部执行完毕后,主线程再继续执行的作用
		final CountDownLatch cnt = new CountDownLatch(2);

		// classLock测试
		Thread t1 = new Thread(() -> {
			Test.classLock();
			cnt.countDown();
		});

		Thread t2 = new Thread(() -> {
			Test.classLock();
			cnt.countDown();
		});

		t1.start();
		t2.start();

		cnt.await();
		System.out.println("===============classLock Test Over==============");

		CountDownLatch cnt2 = new CountDownLatch(2);

		// thisLock测试
		Test t = new Test();

		Thread t3 = new Thread(() -> {
			t.thisLock();
			cnt2.countDown();
		});

		Thread t4 = new Thread(() -> {
			t.thisLock();
			cnt2.countDown();
		});

		t3.start();
		t4.start();
		cnt2.await();
		System.out.println("===============thisLock Test Over==============");

		CountDownLatch cnt3 = new CountDownLatch(2);
		
		// objLock测试
		Thread t5 = new Thread(() -> {
			t.objLock();
			cnt3.countDown();
		});

		Thread t6 = new Thread(() -> {
			t.objLock();
			cnt3.countDown();
		});

		t5.start();
		t6.start();

		cnt3.await();
		System.out.println("===============objLock Test Over==============");
	}

}

输出:

Thread-0进入classLock方法,休眠5s
Thread-0退出classLock方法
Thread-1进入classLock方法,休眠5s
Thread-1退出classLock方法
===============classLock Test Over==============
Thread-3进入thisLock方法,休眠5s
Thread-3退出thisLock方法
Thread-2进入thisLock方法,休眠5s
Thread-2退出thisLock方法
===============thisLock Test Over==============
Thread-4进入objLock方法,休眠5s
Thread-4退出objLock方法
Thread-5进入objLock方法,休眠5s
Thread-5退出objLock方法
===============objLock Test Over==============

3.synchronized语义

JVM虚拟机中的同步是基于Monitor对象实现,此处无论显示同步还是隐示同步皆如此。我们需要注意同步方法并不是由monitorenter和monitorexit 实现,而是通过指令读取运行时常量池中方法的 ACC_SYNCHRONIZED 标志来实现。

4.Java对象头

在JVM中,对象在内存中的布局分为三块区域:对象头、实例数据和对齐填充。如下:
ps:图片引用自:https://blog.csdn.net/javazejian/article/details/72828483
在这里插入图片描述
对象头分为两种。

4.1 普通对象头

1,Mark Word
2,指向类的指针

在这里插入图片描述

4.2 数组对象头

1,Mark Word
2,指向类的指针
3,数组长度(只有数组对象才有)

在这里插入图片描述

4.3和同步锁有关的Mark Word

Mark Word记录了对象和锁有关的信息,当这个对象被synchronized关键字当成同步锁时,围绕这个锁的一系列操作都和Mark Word有关。
Mark Word在32位JVM中的长度是32bit,在64位JVM中长度是64bit。
Mark Word在不同的锁状态下存储的内容不同,在32位JVM中是这么存的:

锁状态

25bit

4bit

1bit

2bit

23bit

2bit

是否偏向锁

锁标志位

无锁

对象的HashCode

分代年龄

0

01

偏向锁

线程ID

Epoch

分代年龄

1

01

轻量级锁

指向栈中锁记录的指针

00

重量级锁

指向重量级锁的指针

10

GC标记

11

其中无锁和偏向锁的锁标志位都是01,只是在前面的1bit区分了这是无锁状态还是偏向锁状态。

JDK1.6以后的版本在处理同步锁时存在锁升级的概念,JVM对于同步锁的处理是从偏向锁开始的,随着竞争越来越激烈,处理方式从偏向锁升级到轻量级锁,最终升级到重量级锁。

JVM一般是这样使用锁和Mark Word的:

1,当没有被当成锁时,这就是一个普通的对象,Mark Word记录对象的HashCode,锁标志位是01,是否偏向锁那一位是0。

2,当对象被当做同步锁并有一个线程A抢到了锁时,锁标志位还是01,但是否偏向锁那一位改成1,前23bit记录抢到锁的线程id,表示进入偏向锁状态。

3,当线程A再次试图来获得锁时,JVM发现同步锁对象的标志位是01,是否偏向锁是1,也就是偏向状态,Mark Word中记录的线程id就是线程A自己的id,表示线程A已经获得了这个偏向锁,可以执行同步锁的代码。

4,当线程B试图获得这个锁时,JVM发现同步锁处于偏向状态,但是Mark Word中的线程id记录的不是B,那么线程B会先用CAS操作试图获得锁,这里的获得锁操作是有可能成功的,因为线程A一般不会自动释放偏向锁。如果抢锁成功,就把Mark Word里的线程id改为线程B的id,代表线程B获得了这个偏向锁,可以执行同步锁代码。如果抢锁失败,则继续执行步骤5。

5,偏向锁状态抢锁失败,代表当前锁有一定的竞争,偏向锁将升级为轻量级锁。JVM会在当前线程的线程栈中开辟一块单独的空间,里面保存指向对象锁Mark Word的指针,同时在对象锁Mark Word中保存指向这片空间的指针。上述两个保存操作都是CAS操作,如果保存成功,代表线程抢到了同步锁,就把Mark Word中的锁标志位改成00,可以执行同步锁代码。如果保存失败,表示抢锁失败,竞争太激烈,继续执行步骤6。

6,轻量级锁抢锁失败,JVM会使用自旋锁,自旋锁不是一个锁状态,只是代表不断的重试,尝试抢锁。从JDK1.7开始,自旋锁默认启用,自旋次数由JVM决定。如果抢锁成功则执行同步锁代码,如果失败则继续执行步骤7。

7,自旋锁重试之后如果抢锁依然失败,同步锁会升级至重量级锁,锁标志位改为10。在这个状态下,未抢到锁的线程都会被阻塞。

5.重量级锁

我们主要分析一下重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的是monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态。在Java虚拟机(HotSpot)中,monitor是由ObjectMonitor实现的,其主要数据结构如下(位于HotSpot虚拟机源码ObjectMonitor.hpp文件,C++实现的)

ObjectMonitor() {
    _header       = NULL;
    _count        = 0; //记录个数
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL; //处于wait状态的线程,会被加入到_WaitSet
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ; //处于等待锁block状态的线程,会被加入到该列表
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
  }

ObjectMonitor中有两个队列,_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象),_owner指向持有ObjectMonitor对象的线程,当多个线程同时访问一段同步代码时,首先会进入 _EntryList 集合,当线程获取到对象的monitor 后进入 _Owner 区域并把monitor中的owner变量设置为当前线程同时monitor中的计数器count加1,若线程调用 wait() 方法,将释放当前持有的monitor,owner变量恢复为null,count自减1,同时该线程进入 WaitSet集合中等待被唤醒。若当前线程执行完毕也将释放monitor(锁)并复位变量的值,以便其他线程进入获取monitor(锁)。如下图所示
在这里插入图片描述
monitor对象存在于每个Java对象的对象头中(存储的指针的指向),synchronized锁便是通过这种方式获取锁的,也是为什么Java中任意对象可以作为锁的原因,同时也是notify/notifyAll/wait等方法存在于顶级对象Object中的原因

6.synchronized反编译

6.1首先准备一个类

package com.hqa.design.test;

import java.util.concurrent.CountDownLatch;

public class Test {

	/**
	 * 一把实例锁
	 */
	private static final Object lock = new Object();

	/**
	 * 静态方法锁,作用于Test.class对象
	 */
	public static synchronized void classLock() {
		try {
			System.out.println(Thread.currentThread().getName() + "进入classLock方法,休眠5s");
			Thread.sleep(5000);
			System.out.println(Thread.currentThread().getName() + "退出classLock方法");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

	/**
	 * 对象实例锁,作用于当前Test类实例
	 */
	public void thisLock() {
		synchronized (this) {
			try {
				System.out.println(Thread.currentThread().getName() + "进入thisLock方法,休眠5s");
				Thread.sleep(5000);
				System.out.println(Thread.currentThread().getName() + "退出thisLock方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

	/**
	 * 加锁对象为lock
	 */
	public void objLock() {
		synchronized (lock) {
			try {
				System.out.println(Thread.currentThread().getName() + "进入objLock方法,休眠5s");
				Thread.sleep(5000);
				System.out.println(Thread.currentThread().getName() + "退出objLock方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

6.2对Test.class进行javap查看

javap -c -l -v Test > out.txt

输出:

Classfile /D:/mcWork/workspace/design-share/bin/com/hqa/design/test/Test.class
  Last modified 2020-1-20; size 1837 bytes
  MD5 checksum eaeadfde841431d8d72d966bc14159ba
  Compiled from "Test.java"
public class com.hqa.design.test.Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Class              #2             // com/hqa/design/test/Test
   #2 = Utf8               com/hqa/design/test/Test
   #3 = Class              #4             // java/lang/Object
   #4 = Utf8               java/lang/Object
   #5 = Utf8               lock
   #6 = Utf8               Ljava/lang/Object;
   #7 = Utf8               <clinit>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Methodref          #3.#11         // java/lang/Object."<init>":()V
  #11 = NameAndType        #12:#8         // "<init>":()V
  #12 = Utf8               <init>
  #13 = Fieldref           #1.#14         // com/hqa/design/test/Test.lock:Ljava/lang/Object;
  #14 = NameAndType        #5:#6          // lock:Ljava/lang/Object;
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lcom/hqa/design/test/Test;
  #19 = Utf8               classLock
  #20 = Fieldref           #21.#23        // java/lang/System.out:Ljava/io/PrintStream;
  #21 = Class              #22            // java/lang/System
  #22 = Utf8               java/lang/System
  #23 = NameAndType        #24:#25        // out:Ljava/io/PrintStream;
  #24 = Utf8               out
  #25 = Utf8               Ljava/io/PrintStream;
  #26 = Class              #27            // java/lang/StringBuilder
  #27 = Utf8               java/lang/StringBuilder
  #28 = Methodref          #29.#31        // java/lang/Thread.currentThread:()Ljava/lang/Thread;
  #29 = Class              #30            // java/lang/Thread
  #30 = Utf8               java/lang/Thread
  #31 = NameAndType        #32:#33        // currentThread:()Ljava/lang/Thread;
  #32 = Utf8               currentThread
  #33 = Utf8               ()Ljava/lang/Thread;
  #34 = Methodref          #29.#35        // java/lang/Thread.getName:()Ljava/lang/String;
  #35 = NameAndType        #36:#37        // getName:()Ljava/lang/String;
  #36 = Utf8               getName
  #37 = Utf8               ()Ljava/lang/String;
  #38 = Methodref          #39.#41        // java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #39 = Class              #40            // java/lang/String
  #40 = Utf8               java/lang/String
  #41 = NameAndType        #42:#43        // valueOf:(Ljava/lang/Object;)Ljava/lang/String;
  #42 = Utf8               valueOf
  #43 = Utf8               (Ljava/lang/Object;)Ljava/lang/String;
  #44 = Methodref          #26.#45        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #45 = NameAndType        #12:#46        // "<init>":(Ljava/lang/String;)V
  #46 = Utf8               (Ljava/lang/String;)V
  #47 = String             #48            // 进入classLock方法,休眠5s
  #48 = Utf8               进入classLock方法,休眠5s
  #49 = Methodref          #26.#50        // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #50 = NameAndType        #51:#52        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #51 = Utf8               append
  #52 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #53 = Methodref          #26.#54        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #54 = NameAndType        #55:#37        // toString:()Ljava/lang/String;
  #55 = Utf8               toString
  #56 = Methodref          #57.#59        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #57 = Class              #58            // java/io/PrintStream
  #58 = Utf8               java/io/PrintStream
  #59 = NameAndType        #60:#46        // println:(Ljava/lang/String;)V
  #60 = Utf8               println
  #61 = Long               5000l
  #63 = Methodref          #29.#64        // java/lang/Thread.sleep:(J)V
  #64 = NameAndType        #65:#66        // sleep:(J)V
  #65 = Utf8               sleep
  #66 = Utf8               (J)V
  #67 = String             #68            // 退出classLock方法
  #68 = Utf8               退出classLock方法
  #69 = Methodref          #70.#72        // java/lang/InterruptedException.printStackTrace:()V
  #70 = Class              #71            // java/lang/InterruptedException
  #71 = Utf8               java/lang/InterruptedException
  #72 = NameAndType        #73:#8         // printStackTrace:()V
  #73 = Utf8               printStackTrace
  #74 = Utf8               e
  #75 = Utf8               Ljava/lang/InterruptedException;
  #76 = Utf8               StackMapTable
  #77 = Utf8               thisLock
  #78 = String             #79            // 进入thisLock方法,休眠5s
  #79 = Utf8               进入thisLock方法,休眠5s
  #80 = String             #81            // 退出thisLock方法
  #81 = Utf8               退出thisLock方法
  #82 = Utf8               objLock
  #83 = String             #84            // 进入objLock方法,休眠5s
  #84 = Utf8               进入objLock方法,休眠5s
  #85 = String             #86            // 退出objLock方法
  #86 = Utf8               退出objLock方法
  #87 = Class              #88            // java/lang/Throwable
  #88 = Utf8               java/lang/Throwable
  #89 = Utf8               SourceFile
  #90 = Utf8               Test.java
{
  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #3                  // class java/lang/Object
         3: dup
         4: invokespecial #10                 // Method java/lang/Object."<init>":()V
         7: putstatic     #13                 // Field lock:Ljava/lang/Object;
        10: return
      LineNumberTable:
        line 10: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

  public com.hqa.design.test.Test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #10                 // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lcom/hqa/design/test/Test;

  public static synchronized void classLock();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
    Code:
      stack=4, locals=1, args_size=0
         0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #26                 // class java/lang/StringBuilder
         6: dup
         7: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        10: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        13: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        16: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        19: ldc           #47                 // String 进入classLock方法,休眠5s
        21: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc2_w        #61                 // long 5000l
        33: invokestatic  #63                 // Method java/lang/Thread.sleep:(J)V
        36: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        39: new           #26                 // class java/lang/StringBuilder
        42: dup
        43: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        46: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        49: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        52: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        55: ldc           #67                 // String 退出classLock方法
        57: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        60: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        63: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: goto          74
        69: astore_0
        70: aload_0
        71: invokevirtual #69                 // Method java/lang/InterruptedException.printStackTrace:()V
        74: return
      Exception table:
         from    to  target type
             0    66    69   Class java/lang/InterruptedException
      LineNumberTable:
        line 17: 0
        line 18: 30
        line 19: 36
        line 20: 66
        line 21: 70
        line 23: 74
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
           70       4     0     e   Ljava/lang/InterruptedException;
      StackMapTable: number_of_entries = 2
        frame_type = 247 /* same_locals_1_stack_item_frame_extended */
          offset_delta = 69
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */

  public synchronized void thisLock();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_SYNCHRONIZED
    Code:
      stack=4, locals=2, args_size=1
         0: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #26                 // class java/lang/StringBuilder
         6: dup
         7: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        10: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        13: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        16: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        19: ldc           #78                 // String 进入thisLock方法,休眠5s
        21: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        24: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        27: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        30: ldc2_w        #61                 // long 5000l
        33: invokestatic  #63                 // Method java/lang/Thread.sleep:(J)V
        36: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        39: new           #26                 // class java/lang/StringBuilder
        42: dup
        43: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        46: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        49: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        52: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        55: ldc           #80                 // String 退出thisLock方法
        57: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        60: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        63: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        66: goto          74
        69: astore_1
        70: aload_1
        71: invokevirtual #69                 // Method java/lang/InterruptedException.printStackTrace:()V
        74: return
      Exception table:
         from    to  target type
             0    66    69   Class java/lang/InterruptedException
      LineNumberTable:
        line 30: 0
        line 31: 30
        line 32: 36
        line 33: 66
        line 34: 70
        line 36: 74
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      75     0  this   Lcom/hqa/design/test/Test;
           70       4     1     e   Ljava/lang/InterruptedException;
      StackMapTable: number_of_entries = 2
        frame_type = 247 /* same_locals_1_stack_item_frame_extended */
          offset_delta = 69
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */

  public void objLock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=1
         0: getstatic     #13                 // Field lock:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: monitorenter
         6: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
         9: new           #26                 // class java/lang/StringBuilder
        12: dup
        13: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        16: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        19: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        22: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        25: ldc           #83                 // String 进入objLock方法,休眠5s
        27: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        30: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        33: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        36: ldc2_w        #61                 // long 5000l
        39: invokestatic  #63                 // Method java/lang/Thread.sleep:(J)V
        42: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        45: new           #26                 // class java/lang/StringBuilder
        48: dup
        49: invokestatic  #28                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
        52: invokevirtual #34                 // Method java/lang/Thread.getName:()Ljava/lang/String;
        55: invokestatic  #38                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)Ljava/lang/String;
        58: invokespecial #44                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        61: ldc           #85                 // String 退出objLock方法
        63: invokevirtual #49                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        66: invokevirtual #53                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        69: invokevirtual #56                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        72: goto          80
        75: astore_2
        76: aload_2
        77: invokevirtual #69                 // Method java/lang/InterruptedException.printStackTrace:()V
        80: aload_1
        81: monitorexit
        82: goto          88
        85: aload_1
        86: monitorexit
        87: athrow
        88: return
      Exception table:
         from    to  target type
             6    72    75   Class java/lang/InterruptedException
             6    82    85   any
            85    87    85   any
      LineNumberTable:
        line 42: 0
        line 44: 6
        line 45: 36
        line 46: 42
        line 47: 72
        line 48: 76
        line 42: 80
        line 51: 88
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      89     0  this   Lcom/hqa/design/test/Test;
           76       4     2     e   Ljava/lang/InterruptedException;
      StackMapTable: number_of_entries = 4
        frame_type = 255 /* full_frame */
          offset_delta = 75
          locals = [ class com/hqa/design/test/Test, class java/lang/Object ]
          stack = [ class java/lang/InterruptedException ]
        frame_type = 4 /* same */
        frame_type = 68 /* same_locals_1_stack_item */
          stack = [ class java/lang/Throwable ]
        frame_type = 250 /* chop */
          offset_delta = 2
}
SourceFile: "Test.java"

参考上述文件,我们可以看到关键部分:

修饰方法:

 public static synchronized void classLock();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_STATIC, `ACC_SYNCHRONIZED`

修饰对象代码块:

 public void objLock();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=3, args_size=1
         0: getstatic     #13                 // Field lock:Ljava/lang/Object;
         3: dup
         4: astore_1
         5: `monitorenter`
         6: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
         9: new           #26                 // class java/lang/StringBuilder
        .......
        80: aload_1
        81: `monitorexit`
        82: goto          88
        85: aload_1
        86: `monitorexit`

从字节码中可知同步语句块的实现使用的是monitorenter 和 monitorexit 指令,其中monitorenter指令指向同步代码块的开始位置,monitorexit指令则指明同步代码块的结束位置,当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,当 objectref 的 monitor 的进入计数器为 0,那线程可以成功取得 monitor,并将计数器值设置为 1,取锁成功。如果当前线程已经拥有 objectref 的 monitor 的持有权,那它可以重入这个 monitor (关于重入性稍后会分析),重入时计数器的值也会加 1。倘若其他线程已经拥有 objectref 的 monitor 的所有权,那当前线程将被阻塞,直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor 。值得注意的是编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时 monitorenter 和 monitorexit 指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行 monitorexit 指令。从字节码中也可以看出多了一个monitorexit指令,它就是异常结束时被执行的释放monitor 的指令。

6.3小结

对于synchronized修饰方法并没有monitorenter指令和monitorexit指令,取得代之的确实是ACC_SYNCHRONIZED标识,该标识指明了该方法是一个同步方法,JVM通过该ACC_SYNCHRONIZED访问标志来辨别一个方法是否声明为同步方法,从而执行相应的同步调用,monitorexit出现两次是因为即便方法发生异常,也要保证锁的正常释放。

7.关于锁的优化

7.1 锁消除

锁消除指的是在JVM即使编译时,通过运行少下文的扫描,去除不可能存在共享资源竞争的锁。
通过锁消除,可以节省毫无意义的锁请求.

比如在单线程下使用StringBuffer,其中的同步完全没有必要,这时候JVM可以在运行时基于逃逸分析计数,消除不必要的锁。

看以下代码片段:

 public static String createStringBuffer(String str1, String str2) {
        StringBuffer sBuf = new StringBuffer();
        sBuf.append(str1);// append方法是同步操作
        sBuf.append(str2);
        return sBuf.toString();

这个静态方法内部的sBuf是每次都会new一个的,因此多线程调用此方法也不会有线程安全问题,因此,此时的append操作若是使用同步操作,就是白白浪费的系统资源。

我们可以通过编译器将其优化,将锁消除,前提是java必须运行在server模式(server模式会比client模式作更多的优化),同时必须开启逃逸分析:

-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks

其中+DoEscapeAnalysis表示开启逃逸分析,+EliminateLocks表示锁消除。

7.2 锁粗化

通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽可能短,但是大某些情况下,一个程序对同一个锁不间断、高频地请求、同步与释放,会消耗掉一定的系统资源,因为锁的讲求、同步与释放本身会带来性能损耗,这样高频的锁请求就反而不利于系统性能的优化了,虽然单次同步操作的时间可能很短。锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗。

看一个极端情况:

public void doSomethingMethod(){
    synchronized(lock){
        //do some thing
    }
    //这是还有一些代码,做其它不需要同步的工作,但能很快执行完毕
    synchronized(lock){
        //do other thing
    }

这里面的代码会多次获取锁,其实这个是完全没有必要的,可以合并为:


public void doSomethingMethod(){
    //进行锁粗化:整合成一次锁请求、同步、释放
    synchronized(lock){
        //do some thing
        //做其它不需要同步但能很快执行完的工作
        //do other thing
    }

8.synchronized的可重入性

从互斥锁的设计上来说,当一个线程试图操作一个由其他线程持有的对象锁的临界资源时,将会处于阻塞状态,但当一个线程再次请求自己持有对象锁的临界资源时,这种情况属于重入锁,请求将会成功,在java中synchronized是基于原子性的内部锁机制,是可重入的,因此在一个线程调用synchronized方法的同时在其方法体内部调用该对象另一个synchronized方法,也就是说一个线程得到一个对象锁后再次请求该对象锁,是允许的,这就是synchronized的可重入性。

9.关于wait与notify

看一段代码:

package com.hqa.design.test;

public class Test {

	/**
	 * 一把实例锁
	 */
	private final Object lock = new Object();
	
	public Object getLock(){
		return lock;
	} 

	/**
	 * 加锁对象为lock
	 */
	public void lock1() {
		synchronized (lock) {
			try {
				System.out.println(Thread.currentThread().getName() + "进入lock1方法,休眠3s");
				Thread.sleep(3000);
				System.out.println(Thread.currentThread().getName() + ",3s之后释放锁");
				lock.wait();
				lock.notifyAll();
				Thread.sleep(7000);
				System.out.println(Thread.currentThread().getName() + "退出lock方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	/**
	 * 加锁对象为lock
	 */
	public void lock2() {
		synchronized (lock) {
			try {
				System.out.println(Thread.currentThread().getName() + "进入lock2方法,休眠10s");
				Thread.sleep(10000);
				lock.notifyAll();
				System.out.println(Thread.currentThread().getName() + ",释放锁,退出lock2方法");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Test test = new Test();
		
		Thread t1 = new Thread(() -> {
			test.lock1();
		});
		t1.start();
		
		Thread t2 = new Thread(() -> {
			test.lock2();
		});
		t2.start();
	}

}

输出:

Thread-0进入lock1方法,休眠3s
Thread-0,3s之后释放锁
Thread-1进入lock2方法,休眠10s
Thread-1,释放锁,退出lock2方法
Thread-0退出lock方法

lock对象为t1和t2所竞争,此处t1线程在执行过程中进行了await,此操作将会释放lock锁,而后通知其他所有线程再次抢锁,此时t2抢到锁,继续执行,完毕后再次通知其他线程执行。关于线程通信我们会独立出一个模块单独讲,篇幅有限,这章节就到此为止了。

参考链接:https://blog.csdn.net/javazejian/article/details/72828483

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值