记一次synchronized的学习

锁的概念

是利用锁的机制来实现同步的。
锁机制有如下两种特性:
**互斥性:**即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性。
**可见性:**必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。

synchronized的用法

synchronized根据获取锁的分类

synchronized修饰类锁

类锁修饰静态方法。针对每个类有对应的类锁。类锁实际上也是通过对象锁实现的。即class的对象锁,每个类只有一个class对象。所以每个类只有一个类锁。

synchronized修饰对象锁

对象锁修饰非静态方法。每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。

synchronized根据修饰对象分类

同步方法

(1) 同步非静态方法
Public synchronized void methodName(){
……
}
(2) 同步静态方法
Public synchronized static void methodName(){
……
}

同步代码块

synchronized(this|object) {}
synchronized(类.class) {}
Private final Object MUTEX =new Object();
Public void methodName(){
Synchronized(MUTEX ){
……
}
}

在 Java 中,每个对象都会有一个 monitor 对象,监视器。

  1. 某一线程占有这个对象的时候,先monitor 的计数器是不是0,如果是0还没有线程占有,这个时候线程占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程等待。当线程释放占有权的时候,monitor-1;
  2. 同一线程可以对同一对象进行多次加锁,+1,+1的原因就是因为锁的重入性。

博客上都是直到对象有3个部分:对象头、实例数据、填充数据。能不能具体看到?

怎么看到java的对象头

引入一个maven包。

	<dependencies>
		<!-- https://mvnrepository.com/artifact/org.openjdk.jol/jol-core -->
		<dependency>
			<groupId>org.openjdk.jol</groupId>
			<artifactId>jol-core</artifactId>
			<version>0.10</version>
		</dependency>

测试代码:

package com.test.sync;

public class L {
}

package com.test.sync;

import org.openjdk.jol.info.ClassLayout;

public class Test {
	static L l =new L();
	public static void main(String[] args) {
		System.out.println("start");
		System.out.println(ClassLayout.parseInstance(l).toPrintable());
		synchronized (l) {
			System.out.println("锁中");
		}
		System.out.println("end");
	}
}

jol中的有个ClassLayout可以看到具体对象怎么存的。
在这里插入图片描述这个是64位(即有8字节)的对象头。圈起来的就是具体对象。

官网对java对象头组成的介绍

这篇文章有对对象头的理解。
http://openjdk.java.net/groups/hotspot/docs/HotSpotGlossary.html
其中包含了对象头的介绍。
在这里插入图片描述
Common structure at the beginning of every GC-managed heap object. (Every oop points to an object header.) Includes fundamental information about the heap object’s layout, type, GC state, synchronization state, and identity hash code. Consists of two words. In arrays it is immediately followed by a length field. Note that both Java objects and VM-internal objects have a common object header format.
有道翻译如下:
每个gc管理的堆对象开始处的公共结构。(每个oop都指向一个对象头。)包括关于堆对象的布局、类型、GC状态、同步状态和标识哈希码的基本信息。由两个词组成。在数组中,紧随其后的是长度字段。注意,Java对象和vm内部对象都有通用的对象头格式。
在这里插入图片描述
注意看下图:
在这里插入图片描述
klass pointer
The second word of every object header. Points to another object (a metaobject) which describes the layout and behavior of the original object. For Java objects, the “klass” contains a C++ style “vtable”.
mark word
The first word of every object header. Usually a set of bitfields including synchronization state and identity hash code. May also be a pointer (with characteristic low bit encoding) to synchronization related information. During GC, may contain GC state bits.

文中提到的两部分组成是由mark word和klass pointer组成。

怎么知道上面对象头的具体组成呢?这就需要看openjdk的源码了。怎么下源码给个博客。
https://blog.csdn.net/qq_36522306/article/details/94398868

我们自己能看到的对象头

下面是找到的最新的对象头的文件:
http://hg.openjdk.java.net/jdk-updates/jdk15u/file/624948a92351/src/hotspot/share/oops/markWord.hpp

在这里插入图片描述
关键注释:
// 32 bits:
// --------
// hash:25 ------------>| age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:23 epoch:2 age:4 biased_lock:1 lock:2 (biased object)
//
// 64 bits:
// --------
// unused:25 hash:31 -->| unused_gap:1 age:4 biased_lock:1 lock:2 (normal object)
// JavaThread*:54 epoch:2 unused_gap:1 age:4 biased_lock:1 lock:2 (biased object)
//
32bit的就不说了。主要说64bit。
从markWord.hpp中可以知道markWord占64位,剩下的klass pointer就占32位。但是很多博客说klass pointer也占64位。这是因为由状态压缩,如果没有状态压缩,就是64bit。

64bit下是怎么存的呢?
1.前25位是没有使用的。即无意义的位数。
2.31位就是拿来存hashcode,所以hashcode都是2的32次方。
3.再往后1位没有意义。
4.age有4位。表示分代年龄。也就是说在虚拟机的gc分代年龄中有from区和to区,每次一个区满了就是发生一次轻GC,然后往另一个区倒,如此反复发生超过16次后,就会被放到old区。这里的16次刚好跟这里的agen4位对应上。
5.biased_lock,1位。是否是偏向锁。
6.lock,占2位。表示锁的状态。
为什么25位放在前面呢?
因为我们拿的时候不能1bit的拿,所以放25bit在前面+31的hashcode=56就是8byte,方便取出。

这里要特别注意一个地方。

在这里插入图片描述
com.test.sync.L 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) 43 c0 00 f8 (01000011 11000000 00000000 11111000) (-134168509)
12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

00000001 00000000 00000000 000000001?
00000000 00000000 00000000 00000000
0
01000011 11000000 00000000 11111000==-134168509?

其它不看。就看00000001 00000000 00000000 00000000==1。
首先这样看肯定不可能相等的。但是你要是反着看就对了。说明这个是反向存储的。这个可以百度大端存储和小端存储。这里就是使用小端存储的。
所以前56位是hashcode就是后面的56位!

下面使用具体的图来说明。
main方法多打印一行。

package com.test.sync;

import org.openjdk.jol.info.ClassLayout;

public class Test {
	static L l =new L();
	public static void main(String[] args) {
		System.out.println("start");
		System.out.println(Integer.toHexString(l.hashCode()));
		System.out.println(ClassLayout.parseInstance(l).toPrintable());
		synchronized (l) {
			System.out.println("锁中");
		}
		System.out.println("end");
	}
}

下图中红色圈起来的25位就是未使用的位数。而蓝色圈起来的部分是15db9742就是16进制的hashcode。对应16进制中的15,db,97,42。懂了吧。
在这里插入图片描述

对象头中的锁(核心)

无锁:不做任何控制。
偏向锁:当第一个线程访问的时候。就是是偏向锁。而后如果一直是这个线程访问。那么就会一直是偏向锁的状态。如果有其它线程访问,就会使用CAS算法竞争。竞争失败则会升级为轻量级锁。竞争不激烈使用。
轻量锁:如果是多个线程交替执行。因为这种是互斥性不是很强。所以会是轻量级锁。当CAS失败的时候就会使轻量级锁。当CAS失败一定的次数之后检测为不适合的时候就转换为重量级锁。
重量锁:当多个线程竞争执行的时候,这种时候轻量级锁已经不适用了,就会转换为重量级锁(因为用户线程转为核心线程非常耗时,所以时间长)。
GC标记:开始垃圾回收。
这个就看百度。
https://www.cnblogs.com/heqiyoujing/p/11144649.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值