Synchronized 简介
Synchronized 是 Java 中的关键字,是一种同步锁。在多线程编程中,有可能会出现多个线程同时争抢同一个共享资源的情况,这个资源一般被称为临界资源。这种共享资源可以被多个线程同时访问,且又可以同时被多个线程修改,然而线程的执行是需要 CPU 的资源调度,其过程是不可控的,所以需要采用一种同步机制来控制对共享资源的访问,于是线程同步锁—— Synchronized 就应运而生了。
Synchronized 用法
Synchronized 关键字主要有以下 3 种使用方式:
修饰实例方法
public synchronized void test1(){
}
Synchronized 锁定的是具体的一个实例对象,即该类的不同实例对象之间的锁是隔离的,当多个线程操作的实例对象不一样的,可以同时访问相同的被 Synchronized 修饰的方法。
修饰静态方法
public synchronized static void test2(){
}
Synchronized 锁定的是整个 class 对象,即不同线程操作该类的不同实例对象时,只要被 Synchronized 修饰的代码都无法同步访问。
修饰代码块
public void test3(){
synchronized(new test()){
}
}
指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
其实就是锁方法、锁代码块和锁对象,那他们是怎么实现加锁的呢?
在了解 Synchronized 的实现原理之前,我们需要先对对象的内存布局有个基本了解。在 JVM 中,对象在内存中分为三块区域,对象头,实例数据和对齐填充
- 对象头
- Mark Word(标记字段):默认存储对象自身运行时数据,如哈希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳、对象分代年龄,这部分信息称为 “Mark Word”;Mark Word 被设计成一个非固定的数据结构以便在极小的空间内存储尽量多的信息,它会根据自己的状态复用自己的存储空间
- Klass Point(类型指针):对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例
- 实例数据
- 存放类的属性数据信息,包括父类的属性信息
- 对齐填充
- 由于虚拟机要求对象起始地址必须是 8 字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐
下面我们看一下对像头的结构:
对象头中与锁有关的就是这个 Mark Word,Mark Word 在不同的虚拟机中所占的空间不同,32 位 JVM 中占 32 bit,64 位 JVM 中占 64 bit。
在 32 位虚拟机下,Mark Word 是 32bit 大小的,其存储结构如下:

在 64 位虚拟机下,Mark Word 是 64bit 大小的,其存储结构如下:

现在虚拟机基本是 64 位的,而 64 位的对象头有点浪费空间,JVM 默认会开启指针压缩,所以基本上也是按 32 位的形式记录对象头的。也可以通过下面参数进行控制 JVM 开启和关闭指针压缩:
- 开启压缩指针:(-XX:+UseCompressedOops)
- 关闭压缩指针:(-XX:-UseCompressedOops)
那为什么 JVM 需要默认开启指针压缩呢?
这是因为在对象头上类元信息指针 Klass pointer 在 32 位 JVM 虚拟机中用 4 个字节存储,但是到了 64 位 JVM 虚拟机中 Klass pointer 用的确是 8 个字节来存储,一些对象在 32 位虚拟机用的也是 4 字节来存储,到了 64 位机器用的都是 8 字节来存储了。
一个项目中有成千上万的对象,如果每个对象都用 8 字节来存放的话,那这些对象无形中就会增加很多空间,导致堆的压力就会很大,堆很容易就会满了,然后就会更容易的触发 GC,那指针压缩的最主要的作用就是压缩每个对象内存地址的大小,那么同样堆内存大小就可以放更多的对象。
Synchronized 的实现原理
同步代码
当一个线程访问同步代码块时,首先是需要得到锁才能执行同步代码,当退出或者抛出异常时必须要释放锁,那么它是如何来实现这个机制的呢?我们来看一段代码:
public class SynchronizedTest {
//同步代码块
public void test(){
synchronized (this){
System

本文详细解析了Java Synchronized的实现原理,包括实例方法、静态方法和代码块的加锁机制,以及JDK 1.6后的锁优化策略,如自旋锁、锁升级和偏向锁。深入探讨了锁的类型及其在不同场景的应用和性能影响。
4345

被折叠的 条评论
为什么被折叠?



