当前位置: 首页> 技术教程> 正文

java 锁,java 锁原理

宁旺春土特产品

在Java并发编程的体系框架中,锁机制是保障多线程环境下共享数据一致性、避免竞态条件的核心手段,从最初的synchronized关键字到JUC包下丰富的显式锁实现,每一种锁的设计都对应着特定的并发场景与性能需求。开发者只有深入理解Java锁的底层原理、分类特性以及适用场景,才能在复杂的并发系统中合理选用锁策略,既保证线程安全,又尽可能降低锁带来的性能开销。

java 锁,java 锁原理

Java中的锁可以从不同维度进行分类,从实现层面可分为内置锁和显式锁,内置锁即synchronized关键字,它是JVM层面实现的锁,开发者无需手动进行锁的获取与释放操作,代码编写简洁但灵活性相对不足;显式锁则是JDK 1.5之后引入的java.util.concurrent.locks包下的Lock接口实现,最具代表性的是ReentrantLock,它提供了更丰富的锁操作能力,比如可中断的锁获取、超时获取锁、尝试非阻塞获取锁等,能够应对更复杂的并发场景。从特性维度又可分为可重入锁与非可重入锁、公平锁与非公平锁、乐观锁与悲观锁、读写锁与排他锁等,不同特性的锁在性能表现与适用场景上存在显著差异。

要理解Java锁的原理,首先需要深入synchronized的底层实现机制。synchronized可以修饰方法、代码块,其锁的本质是关联一个对象的监视器(Monitor),当线程进入同步代码块时,会尝试获取该对象对应的Monitor所有权,获取成功则持有锁,失败则会进入阻塞队列等待。在JDK 1.6之前,synchronized是重量级锁,依赖操作系统的Mutex Lock实现,线程的阻塞与唤醒需要从用户态切换到内核态,性能开销极大。为了优化synchronized的性能,JDK 1.6引入了锁升级机制,锁会从无锁状态逐步升级为偏向锁、轻量级锁,最终在竞争激烈的情况下膨胀为重量级锁。偏向锁适用于只有一个线程访问同步块的场景,它会在对象头的Mark Word中记录持有锁的线程ID,后续该线程进入同步块时无需额外的同步操作,直接通过比较线程ID判断是否持有锁,性能损耗极小。当有第二个线程尝试获取锁时,偏向锁会撤销并升级为轻量级锁,轻量级锁采用CAS操作尝试修改对象头的Mark Word,将其指向栈帧中的锁记录,竞争的线程不会立即阻塞,而是通过自旋的方式不断尝试获取锁,避免了内核态切换的开销。如果自旋超过一定次数或者有多个线程同时竞争,轻量级锁就会膨胀为重量级锁,此时未获取到锁的线程会进入阻塞状态,等待持有锁的线程释放锁后被唤醒。

显式锁的核心实现ReentrantLock则是基于AQS(AbstractQueuedSynchronizer,抽象队列同步器)实现的,AQS是JUC并发包的核心基础框架,它内部维护了一个volatile修饰的state变量用于表示锁的状态,同时通过一个双向链表结构的同步队列来管理等待获取锁的线程。当线程尝试获取锁时,会先通过CAS操作修改state变量,如果修改成功则表示获取锁成功,失败则会将当前线程封装成Node节点加入同步队列的尾部,并通过LockSupport.park()方法挂起当前线程。当持有锁的线程释放锁时,会修改state变量并唤醒队列中的后继节点,让被挂起的线程重新尝试获取锁。ReentrantLock作为可重入锁,会在state变量中记录锁的重入次数,同一个线程每次获取锁时state加1,每次释放锁时state减1,当state减为0时表示锁完全释放。此外ReentrantLock还支持公平锁与非公平锁的切换,公平锁会严格按照线程进入队列的顺序分配锁,保证先来先服务,而非公平锁则允许线程在尝试获取锁时先进行一次CAS争抢,失败后再进入队列,非公平锁的性能通常更高,但可能会出现线程饥饿的问题。

除了基础的排他锁,Java还提供了读写锁ReentrantReadWriteLock,它将锁分为读锁和写锁,读锁是共享锁,允许多个线程同时获取,写锁是排他锁,同一时间只能有一个线程持有,适用于读多写少的场景,能够显著提升并发性能。读写锁同样基于AQS实现,它将state变量的高16位用于表示读锁的持有数量,低16位用于表示写锁的重入次数,通过位运算来分别管理两种锁的状态。此外还有StampedLock,它是JDK 1.8引入的更高效的读写锁实现,提供了乐观读模式,读操作不需要加锁,通过验证戳记来判断读操作过程中是否有写操作修改了数据,进一步提升了读多写少场景下的性能。

在实际开发中,合理选择锁的类型与使用方式至关重要。对于竞争不激烈且代码逻辑简单的场景,synchronized是首选,因为它使用简单且JVM会自动进行优化,性能已经与ReentrantLock相差无几。如果需要可中断、超时获取锁、公平锁等高级特性,则可以选用ReentrantLock。对于读多写少的业务场景,读写锁或者StampedLock能够带来更好的并发性能。同时开发者还需要注意避免死锁、锁粒度不当、锁升级过度等问题,比如尽量缩小同步代码块的范围,减少锁的持有时间,合理设置锁的粒度,避免将不需要同步的操作也包含在锁范围内,只有充分理解锁的原理与特性,才能写出高效且安全的并发程序。

PC右下角
wap底部