synchronized锁
在了解锁之前我们要先了解对象布局
什么是java对象布局
在JVM中,对象在内存中存储的布局可以分为三块区域,即实例化之后的对象
- 对象头:分配的空间是固定的12Byte,由Mark Word(标记字段)和Class Pointer(类型指针)组成
- 实例数据:即定义的数据字段,这些示例数据分配的空间是不固定的
- 对齐填充:要求每个对象的总大小是8 字节的整数倍,比如JVM是64Bit,即8Byte,然而一个类声明了一个布尔类型的字段,一个布尔类型1Byte,当该字段实例化后进入JVM堆中,就需要7Byte的填充数据补齐到8Byte
对象头
对象头:分配的空间是固定的12Byte,由Mark Word(标记字段)和Class Pointer(类型指针)两部分组成
Mark Word(标记字段)
- 64 位(开启指针压缩时也是 64 位存储,但部分位用于压缩编码)。
- 存储内容随运行时状态而变:对象哈希码(hashCode)、GC 分代年龄、锁信息(偏向锁/轻量级锁/重量级锁指针)、线程持锁记录、类型、gc信息等。
Class Pointer(类型指针)
- 也叫 Klass Word,指向对象对应的类元数据(Method …)
- 在 64 位虚拟机中,如果开启了 Compressed Klass Pointers(类指针压缩),则占用 32 位,否则 64 位。
数组对象还有额外长度字段
- 如果这是一个数组实例,在上面两部分之后还有一个 32 位的
length
字段,用于记录数组长度。
public class L {boolean b =false;
}
class main{public static void main(String[] args) {L l =new L();}
}
上述L类声明了一个布尔类型的b字段,布尔类型占1byte,然而JVM为了补齐,填充了3Byte的数据,凑成了16Byte的l对象,其中对象头占12Byte,实例数据占1Byte,填充占3Byte
若改成
int i =1;
synchronized隐式锁
因为开发者无需手动申请或释放锁,而是通过关键字直接在代码中声明需要同步的代码块或方法。
ReentrantLock显示锁
ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个显式锁实现,它实现了Lock 接口,提供了比 synchronized 更灵活和细粒度的控制方式。
特点:1.可重入性:即一个线程在持有锁的情况下,可以再次获得该锁并不会被阻塞,内部会维护一个计数器,每次加锁计数器加 1,解锁时减 1,直到计数器归零,锁才真正释放。
2.公平锁: 可以构造一个公平的 ReentrantLock,保证等待时间最长的线程最先获得锁。这样虽然能降低线程饥饿风险,但在高并发情况下性能可能较低。
非公平锁(默认): 允许线程抢占式获得锁,不保证请求顺序,从而通常能获得更高的吞吐量,但可能存在线程饥饿问题。
3.多 Condition 支持:ReentrantLock 允许通过 newCondition()
方法创建一个 Condition 对象,可以用来实现比 Object.wait/notify 更细粒度的线程等待/唤醒机制。一个 ReentrantLock 可以创建多个 Condition,以满足不同等待条件的需求。