Java中的锁-

本文详细探讨了Java并发机制中的核心概念,包括Lock接口、ReentrantLock、ReentrantReadWriteLock和Condition接口。解释了Lock接口相对于Synchronized关键字的增强特性,如非阻塞性、中断响应和超时获取。介绍了ReentrantLock的重入性和公平性选择,以及ReentrantReadWriteLock的读写锁特性和锁降级机制。最后,对比了Object监视器方法和Condition接口在等待/通知模式上的差异。

一、Lock接口提供的Synchronized关键字不具备的主要特性

特性描述
尝试非阻塞地获取锁当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功并持有锁
能被中断的获取锁与Synchronized不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会释放
超时获取锁在指定的截止时间之前获取锁,如果截止时间到了仍旧无法获取锁,则返回

二、同步队列器

同步队列器(AbstractQueueSynchronizer),是用来构建锁或者其他同步组件的基础架构,他用了一个int型成员变量表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作,主要使用是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态。

重写同步器指定的方法时,需要使用同步器提供的如下三个方法。

getstate():获取当前同步状态

setState(int newState):设置当前同步状态

compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性。

1独占式同步状态获取和释放:在获取同步状态时,同步器维护了一个同步队列,获取状态失败的线程都会被加入到队列中并在队列中进行自旋;移出队列(或停止自旋)的条件是前驱节点为头节点并且成功获取了同步状态。在释放同步状态时,同步器调用tryRelease(int arg)方法释放同步状态,然后唤醒头结点的后继节点。

2写操作要求对资源的独占式访问,而读操作可以是共享式访问。调用同步器的acquireShared(int arg)方法可以共享式地获取同步状态。在该方法中,同步器调用tryAcquireShared(int arg)方法尝试获取同步状态,tryAcquireShared(int arg)方法返回值为int 类型,当返回值大于等于0时,表示能够获取同步状态。因此,在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是tryAcquire(int arg)返回值大于等于0.

三、重入锁ReentrantLock

Synchronized隐式的支持重进入。ReentrantLock支持重进入,即该锁能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择。

简单来说,ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。

ReentrantLock提供了一个构造函数,能够控制锁是否公平。

事实上公平锁的效率往往没有非公平的效率高,但是并不是任何场景都是以TPS作为唯一的指标,公平锁能减少饥饿的发生的概率,等待越久越是能够得到优先满足。

1可重入实现:两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。  

2读写锁:之前提到锁(如Mutex和ReentrantLock)基本都是排它锁,这些锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻可以在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他线程均被阻塞。读写锁维护了一对锁,一个读锁和写锁,通过分离读锁和写锁,使得并发性相比一般的排它锁有了很大提升。

ReentrantReadWriteLock的特性

特性说明
公平性选择支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平
重进入该锁支持重进入,以读写线程为例:读线程在获取了读锁之后,能够再次获取读锁。而写线程在获取了写锁之后能够再次获取写锁,同时也可以获取读锁。
锁降级遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。

 

锁降级:把持住写锁(当前拥有的),再获取到读锁,随后释放(先前拥有的)写锁的释放过程。

四、condition接口

任意一个对象,都拥有一组监视器方法(定义在Java.lang.Object上),主要包括wait(),wait(long),notify()以及notifyAll()方法,这些方法与synchronized同步关键字配合,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。但是这两者在使用方式以及功能特性上还是有差别的。

Object监视器方法和Condition接口对比

对比项Object Monitor MethodsCondition
前置条件获取对象锁

调用Lock.lock()获取锁

调用Lock.newCondition()获取Condition对象

调用方法直接调用:如object.wait()直接调用 如:condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持

当前线程释放锁并进入等待状态,在等待

状态中不响应中断。

不支持支持
当前线程释放锁并进入超时等待支持支持
当前线程释放锁并进入等待状态到将来的某个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

 

### Java 中的机制概述 Java 提供了多种机制以支持多线程环境下的同步操作。这些机制主要包括内置(synchronized 关键字)、显示(ReentrantLock 类)以及一些高级特性如读写(ReadWriteLock)。以下是关于它们的具体介绍: --- ### 内置(Intrinsic Lock) Java 的 synchronized 关键字提供了一种简单的方式来进行线程间的同步。每个对象都有一个与之关联的监视器(monitor),当一个线程进入由 synchronized 修饰的方法或代码块时,它会自动获得该对象的监视器。 ```java public class Counter { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } } ``` 上述代码展示了如何使用 synchronized 方法来确保多个线程不会同时修改 `count` 变量[^1]。 --- ### 显示(Explicit Lock) 相比于内置,显示提供了更大的灵活性。`ReentrantLock` 是最常用的显示实现之一,它可以手动控制的获取和释放,并且支持可中断等待、超时尝试等功能。 #### 使用示例 ```java import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Worker { private final Lock lock = new ReentrantLock(); private int value = 0; public void updateValue(int newValue) throws InterruptedException { boolean isLocked = false; try { isLocked = lock.tryLock(1000, java.util.concurrent.TimeUnit.MILLISECONDS); if (isLocked) { value = newValue; } else { System.out.println("Failed to acquire the lock."); } } finally { if (isLocked) { lock.unlock(); } } } public int getValue() { lock.lock(); try { return value; } finally { lock.unlock(); } } } ``` 此代码片段演示了如何利用 `tryLock()` 方法在一定时间内尝试获取,如果无法获取,则可以选择其他逻辑路径[^1]。 --- ### 偏向(Biased Locking) 偏向是一种优化技术,旨在减少无竞争情况下的操作开销。它的基本思想是在第一次访问某个时将其绑定到当前线程,后续再次访问时不需额外验证即可直接执行。 然而,一旦检测到有其他线程尝试获取同一个,JVM 将撤销偏向并升级为轻量级甚至重量级。这一过程发生在全局安全点处,因为需要暂停所有活动线程以重新评估的状态[^3]。 --- ### 轻量级(Lightweight Locking) 轻量级通过自旋的方式来避免线程切换带来的性能损失。具体来说,当第一个线程请求时,它会在栈帧中保存指向记录的信息;第二个线程到达时发现已被占用,则启动循环不断检查是否可用。只有当自旋次数达到上限仍未能成功时,才会退化成重量级[^4]。 --- ### 解决 ABA 问题 在高并发场景下可能出现一种称为 ABA 的现象:某一变量先后经历了两次相同值的变化,但其间可能发生了不可忽略的动作。为此引入了带版本号的原子引用类 `AtomicStampedReference` 来加以防范[^5]。 ```java import java.util.concurrent.atomic.AtomicStampedReference; public class ABADemo { static AtomicStampedReference<Integer> atomicRef = new AtomicStampedReference<>(100, 0); public static void main(String[] args) { new Thread(() -> { int stamp = atomicRef.getStamp(); try {Thread.sleep(1);} catch (Exception ignored){} atomicRef.compareAndSet(100, 101, stamp, stamp + 1); atomicRef.compareAndSet(101, 100, stamp + 1, stamp + 2); }).start(); new Thread(() -> { int stamp = atomicRef.getStamp(); try {Thread.sleep(2);} catch (Exception ignored){} boolean result = atomicRef.compareAndSet(100, 101, stamp, stamp + 1); System.out.println(result ? "Success" : "Failure"); }).start(); } } ``` 以上例子表明即使目标数值看似未变,但由于附加了时间戳标记,依然能有效区分实际发生的改动序列[^5]。 --- ### 高效实践建议 - 对于短期定需求优先考虑内置- 如果涉及复杂业务流程推荐选用显式以便更好地掌控资源分配状况; - 注意合理配置粒度大小以免造成不必要的争用瓶颈。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值