并发编程之:synchronized (2)

那么synchronized修饰静态方法的字节码文件是什么样呢?

public static synchronized void sale(); descriptor: ()V flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED Code: stack=3, locals=0, args_size=0 0: getstatic #2 // Field ticket:I // 省略其他无关字节码

可以看出synchronized修饰静态方法和实例方法没有区别,都是增加一个ACC_SYNCHRONIZED的flag,静态方法只是比实例方法多一个ACC_STATIC标识代表这个方法是静态的。

以上的同步代码块,同步方法中都提到对象监视器这个概念,那么三种同步方式使用的对象监视器具体是哪个对象呢?

同步代码块的对象监视器就是使用的我们synchronized(str)中的str,也就是我们括号中指定的对象。而我们在开发中增加同步代码块的目的是为了多个线程同一时间只能有一个线程持有监视器,所以这个对象的指定一定要是多个线程共享的对象,不能直接在括号中new一个对象,这样不能做到互斥,也就不能保证安全。

同步实例方法的对象监视器是当前这个实例,也就是this。

同步静态方法的对象监视器是当前这个静态方法所在类的Class对象,我们都知道Java中每个类在运行过程中也会用一个对象表示,就是这个类的对象,每个类有且仅有一个。

对象锁(monitor)

上面说了线程要进入同步代码块需要先获取到对象监视器,也就是对象锁,那在开始说之前我们先来了解下在Java中一个对象都由哪些东西组成。

这里先问大家一个问题,Object obj = new Object()这段代码在JVM中是怎样的一个内存分布?

想必了解过JVM知识的同学应该都知道,new Object()会在堆内存中创建一个对象,Object obj是栈内存中的一个引用,这个引用指向堆中的对象。那么怎么知道堆内存中的对象到底由哪些内容组成呢?这里给大家介绍一个工具叫JOL(Java Object Layout)Java对象布局。可以通过maven在项目中直接引入。

<dependency> <groupId>org.openjdk.jol</groupId> <artifactId>jol-core</artifactId> <version>0.9</version> </dependency>

引入之后在代码中可以打印出对象的内存分布。

public static void main(String[] args) { Object obj = new Object(); // parseInstance将对象解析,toPrintable让解析后的结果可输出 System.out.println(ClassLayout.parseInstance(obj).toPrintable()); }

输出后的结果如下:

java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

从结果上可以看出,这个obj对象主要分4部分,每部分的SIZE=4代表4个字节,前三行是对象头object header,最后一行的4个字节是为了保证一个对象的大小能是8的整数倍。

image

我们再来看看对于一个加了锁的对象,打印出来有什么不一样?

public static void main(String[] args) { Object obj = new Object(); synchronized (obj){ System.out.println(ClassLayout.parseInstance(obj).toPrintable()); } } java.lang.Object object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 4 (object header) 58 f7 19 01 (01011000 11110111 00011001 00000001) (18478936) 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0) 8 4 (object header) e5 01 00 f8 (11100101 00000001 00000000 11111000) (-134217243) 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

可以很明显的看到,最前面的8个字节发生了变化,也就是Mark Word变了。所以给对象加锁,实际就是改变对象的Mark Word。

Mark Word中的这8个字节具有不同的含义,为了让这64个bit能表示更多信息,JVM将最后2位设置为标记位,不同标记位下的Mark word含义如下:

|------------------------------------------------------------------------------|--------------------| | Mark Word (64 bits) | State | |------------------------------------------------------------------------------|--------------------| | unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 | 无锁态 | |------------------------------------------------------------------------------|--------------------| | thread:54 | epoch:2 | unused:1 | age:4 | biased_lock:1 | lock:2 | 偏向锁 | |------------------------------------------------------------------------------|--------------------| | ptr_to_lock_record:62 | lock:2 | 轻量级锁 | |------------------------------------------------------------------------------|--------------------| | ptr_to_heavyweight_monitor:62 | lock:2 | 重量级锁 | |------------------------------------------------------------------------------|--------------------| | | lock:2 | GC标记 | |------------------------------------------------------------------------------|--------------------|

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/zzwzfs.html