Lock和Synchronize区别详解

1、两者所处层面不同
synchronized是Java中的一个关键字,当我们调用它时会从在虚拟机指令层面加锁,关键字为monitorenter和monitorexit
Lock是Java中的一个接口,它有许多的实现类来为它提供各种功能,加锁的关键代码为大体为Lock和unLock;
2、获锁方式
synchronized可对实例方法、静态方法和代码块加锁,相对应的,加锁前需要获得实例对象的锁或类对象的锁或指定对象的锁。说到底就是要先获得对象的监视器(即对象的锁)然后才能够进行相关操作。
Lock的使用离不开它的实现类AQS,而它的加锁并不是针对对象的,而是针对当前线程的,并且AQS中有一个原子类state来进行加锁次数的计数
3、获锁失败
使用关键字synchronized加锁的程序中,获锁失败的对象会被加入到一个虚拟的等待队列中被阻塞,直到锁被释放;1.6以后加入了自旋操作
使用Lock加锁的程序中,获锁失败的线程会被自动加入到AQS的等待队列中进行自旋,自旋的同时再尝试去获取锁,等到自旋到一定次数并且获锁操作未成功,线程就会被阻塞
4、偏向或重入
synchronized中叫做偏向锁
当线程访问同步块时,会使用 CAS 将线程 ID 更新到锁对象的 Mark Word 中,如果更新成功则获得偏向锁,并且之后每次进入这个对象锁相关的同步块时都不需要再次获取锁了。
Lock中叫做重入锁
AQS的实现类ReentrantLock实现了重入的机制,即若线程a已经获得了锁,a再次请求锁时则会判断a是否持正有锁,然后会将原子值state+1来实现重入的计数操作
5、Lock独有的队列
condition队列是AQS中的一个Lock的子接口的内部现类,它一般会和ReentrantLock一起使用来满足除了加锁和解锁以外的一些附加条件,比如对线程的分组和临界数量的判断(阻塞队列)
6、解锁操作
synchronized:不能指定解锁操作,执行完代码块的对象会自动释放锁
Lock:可调用ulock方法去释放锁比synchronized更灵活

来看下面具体例子:

public class DeadLockTest implements Runnable{
   
   

	//final ReentrantLock lock = new ReentrantLock();
	
	static String source1 = "A";
	static String source2 = "B";
	
	public void run() {
   
   
		if(Thread.currentThread().getName().equals("1")) {
   
   
			synchronized(source1) {
   
   
			//lock.lock();
				System.out.println("线程1获取资源1锁");
				try {
   
   
					Thread.currentThread().sleep(100);
				} catch (InterruptedException e) {
   
   
					e.printStackTrace();
				}
				//lock.lock();
				synchronized(source2) {
   
   
					System.out.println("线程1获取资源2锁");
					try {
   
   
						Thread.currentThread().sleep(100);
					} catch (InterruptedException e) {
   
   
						e
<think>我们正在讨论RCU(Read-Copy-Update)机制,它是一种用于同步的机制,特别适用于读多写少的场景。根据引用[^1],RCU被描述为一种改进的读写锁(rwlock),它几乎消除了读者的同步开销。读者不需要获取锁,不使用原子指令,因此不会导致锁竞争、内存延迟流水线停滞。同时,由于读者不需要锁,也避免了死锁问题。 引用[^2]展示了一个RCU同步函数`synchronize_rcu`的实现,它调用了`synchronize_sched`,然后根据情况选择等待一个宽限期(graceperiod)或者使用加速版本的同步。这个宽限期是RCU机制中的关键概念,它确保所有在更新开始之前已经开始的读操作完成之后,才释放旧数据。 引用[^3]提到了在Linux内核初始化过程中,在调度器初始化之后会进行RCU的初始化(`rcu_init`)。现在,我将详细解释RCU的读写锁使用机制。###RCU机制概述RCU的核心思想是:在更新共享数据时,先创建数据的副本,修改副本,然后在适当的时机用修改后的副本替换原数据。这个“适当的时机”就是当所有正在使用旧数据的读者都完成之后(即宽限期结束)。这样,读者可以无锁地访问数据,而写者则需要等待宽限期结束才能释放旧数据。 ###读者操作读者在访问共享数据时,只需要在读取数据前后分别加上`rcu_read_lock()``rcu_read_unlock()`(或者使用其他等效的RCU原语,如`rcu_read_lock_bh`等)。注意,这两个函数并不实际加锁,它们主要是禁止重新启用内核抢占,并标记进入退出RCU读端临界区。读者在临界区内可以安全地访问共享数据,因为写者会等待所有在更新开始前进入临界区的读者离开临界区后才释放旧数据。 ###写者操作写者需要执行以下步骤: 1.分配新内存,复制要更新的数据。 2.修改新数据。3.用新数据替换旧数据(通常是通过原子指针替换)。 4.等待宽限期结束(通过`synchronize_rcu()`或类似的函数),以确保所有可能持有旧数据引用的读者都已经退出临界区。5.释放旧数据。 ###宽限期(Grace Period) 宽限期是RCU机制中一个关键概念。它是指从更新操作(替换指针)开始,到所有在更新操作之前已经开始的读操作都结束为止的时间段。写者必须等待宽限期结束后才能释放旧数据,以确保没有读者还在使用旧数据。###示例代码以下是一个简化的RCU使用示例(注意:实际内核代码中会使用更复杂的结构,但原理相同):```c//读者rcu_read_lock();struct data*d= rcu_dereference(global_ptr);//使用d进行读取操作rcu_read_unlock(); //写者struct data*new_ptr =kmalloc(sizeof(*new_ptr), GFP_KERNEL);//初始化new_ptr,复制数据并修改rcu_assign_pointer(global_ptr, new_ptr);//替换全局指针synchronize_rcu(); //等待宽限期结束kfree(old_ptr); //释放旧数据```###机制分析-**读者开销小**:因为读者不需要锁,也不使用原子操作,所以读操作非常高效。 -**写者开销较大**:写者需要复制数据、等待宽限期,并且可能阻塞。因此,RCU适用于读多写少的场景。- **内存回收延迟**:旧数据不会立即释放,而是等待宽限期结束后才释放,这可能导致内存占用增加。 -**无死锁**:由于读者不会阻塞写者,写者也不会阻塞读者(写者只阻塞在等待宽限期),所以不会出现死锁。###相关问题1.RCU中的宽限期是如何确定的?它如何知道所有读者已经离开临界区? 2.在哪些场景下RCU比传统的读写锁更有优势?3.RCU机制在内核中除了同步之外还有哪些应用?</think>### RCU读写锁机制详解 RCU(Read-Copy-Update)是Linux内核中一种高效的同步机制,特别适用于**读多写少**的场景。其核心思想是通过**延迟回收旧数据**来实现无锁读取,大幅降低读者开销。 #### 一、核心机制 1. **读者操作** - 无需获取锁,直接访问共享数据 - 使用`rcu_read_lock()`/`rcu_read_unlock()`标记临界区 - 示例代码: ```c rcu_read_lock(); struct data *d = rcu_dereference(ptr); // 安全获取指针 /* 读取数据操作 */ rcu_read_unlock(); ``` **开销**:仅需禁用内核抢占(非原子操作),无锁竞争[^1] 2. **写者操作** - 分三步完成更新: ```c // 1. 创建新副本 struct data *new = kmalloc(...); memcpy(new, old, ...); new->value = updated_value; // 2. 原子替换指针 rcu_assign_pointer(ptr, new); // 发布新数据 // 3. 延迟回收旧数据 synchronize_rcu(); // 等待宽限期结束 kfree(old); // 安全释放旧数据 ``` - 关键函数`synchronize_rcu()`会阻塞直到**所有现存的读临界区退出**[^2] #### 二、关键设计 1. **宽限期(Grace Period)** - 定义:从指针更新到所有旧读者退出的时间段 - 实现原理: - 内核维护所有CPU的**静止状态(Quiescent State)** - 当所有CPU至少经历一次上下文切换时,宽限期结束[^3] - 等待机制: ```c void synchronize_sched(void) { if (rcu_blocking_is_gp()) return; wait_rcu_gp(call_rcu_sched); // 核心等待函数[^2] } ``` 2. **无锁读取优势** | 特性 | 传统读写锁 | RCU | |------------|------------|-----------| | 读者开销 | 原子操作 | 零原子操作| | 内存屏障 | 需要 | 仅写者需要| | 死锁风险 | 存在 | 无 | | 适用场景 | 均衡读写 | 读密集型 | #### 三、典型应用场景 1. **内核数据结构** - 路由表、进程描述符等频繁读取的结构 2. **网络协议栈** - 连接状态表的快速查询 3. **文件系统** - VFS dentry缓存等高频读取对象 #### 四、使用注意事项 1. **读者限制** - 临界区内不能休眠 - 必须使用`rcu_dereference()`访问指针 2. **写者开销** - 内存复制成本(需权衡更新频率) - 宽限期等待可能阻塞(毫秒级) > **设计哲学**:通过空间换时间(数据副本)延迟回收(宽限期),实现读操作零锁竞争[^1]。 ### 相关问题 1. RCU中的宽限期检测如何在内核中具体实现? 2. 在哪些场景下RCU比传统的读写锁性能更差? 3. RCU机制如何处理嵌套读取的情况? 4. 如何验证RCU保护的数据访问的正确性?
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Archie_java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值