
本文共 1534 字,大约阅读时间需要 5 分钟。
一、并发同步器-设计同步器的意义
在多线程编程中,共享和可变的资源可能会导致线程安全问题。这些资源可以是对象、变量、文件等。共享意味着多个线程可以同时访问,而可变意味着资源在生命周期内可能会被修改。为了确保线程安全,需要采取同步机制来协调对临界资源的访问。
并发模式的解决方案
所有并发模式都采用的方案是序列化访问临界资源,即在同一时刻,只能有一个线程访问临界资源,这称为同步互斥访问。Java提供了两种方式来实现同步互斥访问:synchronized和Lock。
Java锁体系
synchronized是基于JVM内置锁,通过Monitor(监视器锁)实现,依赖底层操作系统的Mutex lock。synchronized关键字会被编译成monitorenter和monitorexit两条指令,分别对应JMM中的八大原子操作。
每个对象都有一个Monitor,加锁过程如下图所示。Monitor的状态记录在对象的内存布局中的Mark Word部分。Mark Word在32位和64位虚拟机中分别为32个和64个Bits,根据对象的状态复用存储空间。
synchronized的使用与原理
synchronized修饰的实例方法锁的是对象(this),同步代码块锁的是代码块内的对象。类锁锁的是类对象,类锁和对象锁是互不干扰的。
synchronized的加锁方式有三种:同步实例方法、同步代码块、同步类方法。加锁的目的是序列化访问临界资源,确保同步互斥访问。
锁状态与内存布局
每个对象的内存布局包括对象头(Header)、实例数据(Instance Data)和对齐填充(Padding)。对象头存储运行时数据,如哈希码、GC分代年龄、锁状态标志、线程持有的锁等。Mark Word是对象头的一部分,用于存储锁状态。
锁膨胀升级
JVM对synchronized进行了多项优化,如锁粗化、锁消除、轻量级锁、偏向锁和自旋锁。锁的状态从无锁到偏向锁再到轻量级锁和重量级锁。偏向锁优化了无锁状态下加锁的性能,轻量级锁减少了锁的重量,自旋锁减少了线程切换的开销。
逃逸分析
逃逸分析是JVM优化的一部分,用于减少不必要的锁开销。通过分析代码,确定哪些对象不会逃逸到堆区,可以对这些对象进行栈分配和标量替换优化。逃逸分析默认开启,可以通过JVM参数关闭。
Lock的实现
Lock是另一种同步机制,相比synchronized更接近操作系统的Mutex锁。Lock提供了更高的灵活性,可以实现更复杂的同步需求。Java的ReentrantLock是基于AQS实现的,支持可重入和公平/非公平锁模式。
AbstractQueuedSynchronizer(AQS)
AQS是Java并发编程的核心框架,定义了独占锁和共享锁两种资源共享方式。AQS通过同步等待队列(CLH队列)和条件等待队列实现线程等待和唤醒。
AQS的状态管理
AQS维护一个volatile int状态字段,通过getState、setState和compareAndSetState方法进行操作。独占锁和共享锁的实现依赖于这些方法。
公平锁与非公平锁
公平锁和非公平锁的主要区别在于加锁失败时的处理方式。公平锁优先让等待最久的线程获得锁,而非公平锁则直接抢占锁。非公平锁在加锁失败时不会入队,而是立即抢占锁。
读写锁
读写锁是一种更细粒度的锁机制。写锁是独享锁,读锁是共享锁。AQS通过将状态字段按位切割,分别管理读锁和写锁的数量。这种设计允许更灵活地控制资源的访问。
以上内容涵盖了Java中的synchronized、Lock以及AQS框架的核心知识,包括它们的实现原理、优化机制和实际应用场景。
发表评论
最新留言
关于作者
