浅谈线程安全与sychronized

本文探讨了多线程程序设计中常见的线程安全问题,通过实例演示如何使用synchronized关键字确保数据一致性,并介绍了其三种主要用法及锁重入特性。

使用多线程处理相关业务,在一定程度上能够获得更高的执行效率,提高程序性能,但是,如果我们在写多线程程序时,不加强注意,容易出现数据不一致性,也就是我们常说的 "线程安全" 问题。线程安全是我们在设计多线程程序时必须保证的一点,如果我们写出的程序数据正确性不能得到保证,即使程序性能得到提高,也是毫无意义的。

非线程安全在什么时候会出现呢?

当多个线程对同一对象实例进行并发访问时,可能得到错误的结果,这就是线程不安全的。相反,无论多少线程对同一对象实例进行并发访问,都能得到一致的结果,这就是线程安全的。

下面我们给出一个经典的线程不安全的例子:

我们在当前程序中定义来了一个instance对象,对它执行++操作,按照正常逻辑,它返回的结果应该是200,然而结果总是差强人意。这里,我们可以看到返回的结果是144,大家可以多执行几次,看看有什么不同,由于instance++不是原子性操作,因此,我们的程序存在安全性问题。那么怎样才能使结果变为200呢?我们做一点小的修改:

大家可以发现,结果是我们预期值200了。这里我们在instance++操作上加了一段synchronized语句块,好神奇,它为什么就能解决线程安全性问题呢。

sychronized关键字,实现线程间的同步,它的作用就是给同步代码加锁,每一次只能有一个线程进入同步块,从而保证线程安全性问题。在上一段代码中,当线程A在对instance变量进行读写的时候,线程B只能等待线程A执行完成,才能对instance变量进行++操作。

sychronized大致有三种用法,这里详细讲解一下:

1.指定对象加锁。当线程访问同步块时,首先获取加锁对象,如果对象锁被其它线程持有,则进行等待。

2.作用于实例方法上。给当前实例加锁,访问同步代码时,首先获得当前实例的锁。

3.作用于静态方法上。静态方法加锁,给当前class加锁,访问同步代码时,需要获得当前类的锁。

大家现在应该立马就能想到,上述代码,我们使用的就是第一种方式,给指定对象加锁。我们加锁的对象就是当前类的实例对象,当我们对同一实例对象进行访问时,一次只有一个线程获得对象锁。关于另外两种加锁方式,我们修改一下上述代码来理解它们:

实例对象加锁:

结果符合预期,我们把之前的同步块改成了实例方法,该方法,锁住的是SychronizedDemo的实例对象,和之前同步块中this的效果一样。当线程需要访问同步方法时,必须先获得当前类的实例对象,否则,只能进行等待。

静态方法加锁:

结果符合预期,大家可以看到,main()方法中,我们new 了两个实例对象,但是结果竟然是正确的。因为我们使用的是静态方法加锁,这样锁住的就是当前类对象,无论我们定义多少个实例对象,结果都是一样的。

sychronized的另外一个特性:锁重入。在使用sychronized关键字时,当一个线程获得对象锁后,再次请求此对象锁时是可以再次获得该对象锁的。简单来说就是,在一个sychronized方法或同步块中调用当前类的其它sychronized方法,是能够得到锁的。大家可以想一下,当前线程在还未释放锁时,再一次请求锁,如果不可锁重入的话,就会形成死锁,这是大家最不想看到的。关于锁重入,大家可以写三个sychronized修饰的方法,然后相互调用,看看是否可以正常执行。限于篇幅,这里我就不贴出代码了。

sychronized关键字,大家是不是觉得它在解决线程安全时,特别的简单,无需太多的操作。大家再思考一下,如果我们现在有两个方法,第一个方法执行写操作,第二个方法执行读操作,当我们使用sychronized关键字修饰时,那么我们多个线程同时执行读方法,也会相互等待,这是我们不愿意看到的。我们只希望读写互斥,写写互斥,读读不互斥,那么我们就需要了解另外一个重入锁Lock了,下一节,我们关于Lock的用法进行详细讲解。

 

 

<think>我们正在讨论ReentrantLocksynchronized的区别。根据引用[1]和引用[2]的内容,我们可以从多个维度进行比较。 同时,引用[3]提到了ReentrantLock的Condition可以更灵活地控制线程的等待和唤醒。 我们将从以下几个方面进行对比: 1. 锁的获取释放机制 2. 可中断性 3. 公平锁非公平锁 4. 条件变量(Condition) 5. 性能 6. 使用方式 注意:引用[1]说明了ReentrantLock支持重入,并且可以分析公平锁非公平锁的行为;引用[2]解释了synchronized的底层原理(基于监视器锁);引用[3]提到了ReentrantLock的Condition可以分组唤醒线程。 下面进行详细说明。 </think>### ReentrantLock synchronized 的核心区别 #### 一、锁实现机制对比 | **特性** | **synchronized** | **ReentrantLock** | |------------------------|-----------------------------------------------------|-------------------------------------------------------| | **实现原理** | JVM 内置监视器锁(Monitor)[^2] | 基于 AQS(AbstractQueuedSynchronizer)的 API 实现[^1] | | **锁类型** | 非公平锁(默认) | 支持公平锁和非公平锁(通过构造函数指定)[^1] | | **锁获取方式** | 自动获取和释放 | 需显式调用 `lock()` 和 `unlock()` | | **底层依赖** | JVM 原生支持 | JDK 层面的 API 实现 | #### 二、功能特性对比 ```java // synchronized 示例 synchronized (obj) { // 临界区代码 } // ReentrantLock 示例 ReentrantLock lock = new ReentrantLock(); lock.lock(); try { // 临界区代码 } finally { lock.unlock(); // 必须显式释放 } ``` 1. **可中断性** - synchronized:线程阻塞时**不可中断** - ReentrantLock:支持 `lockInterruptibly()` 方法,允许中断等待中的线程[^1] ```java lock.lockInterruptibly(); try { // 可被 Thread.interrupt() 中断的临界区 } finally { lock.unlock(); } ``` 2. **超时控制** - synchronized:**不支持**获取锁超时 - ReentrantLock:支持 `tryLock(long timeout, TimeUnit unit)` 方法 ```java if (lock.tryLock(3, TimeUnit.SECONDS)) { try { /* 临界区 */ } finally { lock.unlock(); } } else { // 超时处理 } ``` 3. **条件变量** - synchronized:通过 `wait()`/`notify()` 实现单一等待队列 - ReentrantLock:支持多个 **Condition 对象**,实现分组唤醒[^3] ```java Condition condition = lock.newCondition(); // 线程A:condition.await(); // 线程B:condition.signal(); // 精确唤醒特定条件线程 ``` #### 三、性能差异 $$ \text{低并发场景} \rightarrow \begin{cases} \text{synchronized 优化(锁升级)} \rightarrow \text{性能接近} \\ \text{偏向锁/轻量级锁:开销低} \end{cases} $$ $$ \text{高并发场景} \rightarrow \begin{cases} \text{ReentrantLock} \rightarrow \text{更优的吞吐量} \\ \text{CAS 操作减少线程切换} \end{cases} $$ (JDK 1.6 后 synchronized 性能大幅提升,多数场景差距<5%[^2]) #### 四、异常处理 | **场景** | **synchronized** | **ReentrantLock** | |------------------------|---------------------------|----------------------------| | **同步块内异常** | 自动释放锁 | 需在 finally 中手动释放锁 | | **死锁风险** | 较高(不可中断) | 较低(支持尝试获取锁) | #### 五、适用场景对比 | **场景** | **推荐方案** | **原因** | |------------------------------|----------------------|------------------------------------| | 简单同步逻辑 | synchronized | 简洁安全,自动管理锁 | | 需要公平锁 | ReentrantLock | 构造函数指定公平策略[^1] | | 跨方法锁传递(锁链) | ReentrantLock | 可灵活控制锁边界 | | 分组唤醒等待线程 | ReentrantLock+Condition | 多个条件队列支持[^3] | | 高并发竞争优化 | ReentrantLock | CAS 减少线程切换 | > ⚠️ **关键陷阱**: > 使用 ReentrantLock 时**必须**在 finally 块中释放锁,否则会导致死锁: > ```java > lock.lock(); > try { > // 业务代码 > } finally { > lock.unlock(); // 必须确保执行 > } > ``` #### 六、最佳实践 1. **优先选择 synchronized**:简单同步需求(90%+场景) 2. **明确使用 ReentrantLock 的场景**: - 需要公平锁 - 需要尝试获取锁(tryLock) - 需要可中断的锁获取 - 需要分组唤醒线程 3. **监控工具支持**:ReentrantLock 可通过 `getOwner()` 等 API 获取锁状态(调试优势)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值