前言
在前面的一篇文章里,我曾经讨论了volatile修饰变量的使用。当时,经过各种分析和比较,发现在如果只有一个单线程写但是有多个线程读数据的情况下,volatile变量是一个适用的选项。在这部分,我们可以探讨另外一个选项。那就是ReentrantReadWriteLock。
应用场景
记得以前在一些社区讨论的时候,就看到有人提出过这么一些让人觉得比较纠结的场景。比如说有两个线程,他们之间共享一块数据,在一个线程写数据和一个线程读数据的时候,怎么样保证数据和逻辑的正确性。是否需要同步和加锁呢?还是完全没有必要?实际上,在前面关于volatile的介绍里已经基本上解决了。对于有多个读取数据的线程和单个写数据线程的场景。在我们看来这是一个比较理想的情况,因为对于读操作来说它本身不会带来任何的副作用,我们希望所有的这种操作能够并行。而对于写操作来说,一旦它发生作用,那么其他读数据的线程必须和它互斥,这样才能保证程序的正确性。
在这种情况下,ReentrantReadWriteLock就算是一个比较理想的选择。它本身就定义了两个锁,一个读锁,一个写锁。在所有读数据的线程来说,他们都通过获取读锁来获得数据。这个锁对于读线程来说都是并行的,他们不会互斥。而对于写锁来说,一次只有一个线程能够获得。当它获得的时候,会和其他线程互斥。这样,我们在使用他们的时候,在需要读取数据的地方加上读锁,在需要写数据的地方加上写锁就能够满足基本的要求了。
下面是一个使用ReentrantReadWriteLock的示例:
import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class PricesInfo { private double price1; private double price2; private ReadWriteLock lock; public PricesInfo() { price1 = 1.0; price2 = 2.0; lock = new ReentrantReadWriteLock(); } public double getPrice1() { lock.readLock().lock(); double value = price1; lock.readLock().unlock(); return value; } public double getPrice2() { lock.readLock().lock(); double value = price2; lock.readLock().unlock(); return value; } public void setPrices(double price1, double price2) { lock.writeLock().lock(); this.price1 = price1; this.price2 = price2; lock.writeLock().unlock(); } }
这部分是定义我们需要操作的数据对象,在getPrice的两个方法中,都添加了读锁。在setPrices方法里增加了写锁。
接着,我们再定义读数据的线程Reader和写数据的线程Writer:
public class Reader implements Runnable { private PricesInfo pricesInfo; public Reader(PricesInfo pricesInfo) { this.pricesInfo = pricesInfo; } @Override public void run() { for(int i = 0; i < 10; i++) { System.out.printf("%s: Price 1: %f\n", Thread.currentThread().getName(), pricesInfo.getPrice1()); System.out.printf("%s: Price 2: %f\n", Thread.currentThread().getName(), pricesInfo.getPrice2()); } } }
public class Writer implements Runnable { private PricesInfo pricesInfo; public Writer(PricesInfo pricesInfo) { this.pricesInfo = pricesInfo; } @Override public void run() { for(int i = 0; i < 3; i++) { System.out.printf("Writer: Attempt to modify the prices.\n"); pricesInfo.setPrices(Math.random() * 10, Math.random() * 8); System.out.printf("Writer: Prices have been modified.\n"); try { Thread.sleep(2); } catch(InterruptedException e) { e.printStackTrace(); } } } }
在测试程序里,我们定义了5个读线程和一个写线程,通过运行他们我们可以来查看运行的结果:
/** * Main class of the Example. Create and start two initialization tasks * and wait for their finish * */ public class Main { /** * Main method of the class. Create and star two initialization tasks * and wait for their finish * @param args */ public static void main(String[] args) { PricesInfo pricesInfo = new PricesInfo(); Reader[] readers = new Reader[5]; Thread[] threadsReader = new Thread[5]; for(int i = 0; i < 5; i++) { readers[i] = new Reader(pricesInfo); threadsReader[i] = new Thread(readers[i]); } Writer writer = new Writer(pricesInfo); Thread threadWriter = new Thread(writer); for(int i = 0; i < 5; i++) { threadsReader[i].start(); } threadWriter.start(); } }
如果我们去分析程序运行的结果,会发现只要是在执行写操作结束后,所有读线程获得的数据都会是一致的。这样就保证了正确性。
和volatile的比较
使用读写锁的方式在单个写线程加多个读线程的情况下,其实差别不大。在使用volatile变量的时候,因为每次操作修改的结果对于全局都是可见的。那么在只有一个写线程的情况下,只有这个线程可以唯一修改数据。不会存在有几个写线程而产生的竞争条件。对于有多个写线程的情况下,volatile变量就不能保证数据的一致性了。而ReentrantReadWriteLock却可以有锁的机制保证互斥。它同时也尽可能保证了足够大的并行性。
和synchronized的比较
synchronized的修饰一般限制这个区域是不可重入的。每次只有一个线程可以访问。这种强烈的互斥性使得每次不管是读数据还是写数据都只能有一个线程可以操作。在希望有多个读线程可以并行执行的情况下,它并不是一个理想的选择。
总结
ReentrantReadWriteLock是一个解决单线程写和多线程读的理想方法。它采用类似于读写分离的思路设定了读锁和写锁。对于这两个锁的访问保证尽可能大的读并行和写互斥。另外,在一定的条件下写锁可以转换成读锁,而读锁却不能转换成写锁。
参考资料
http://stackoverflow.com/questions/6637170/reentrantreadwritelock-vs-synchronized
http://docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
相关推荐
inux 读写锁应用实例 /*使用读写锁实现四个线程读写一段程序的实例,共创建了四个新的线程,其中两个线程用来读取数据,另外两个线程用来写入数据。在任意时刻,如果有一个线程在写数据,将阻塞所有其他线程的任何...
#### 读写锁应用实例 下面是一个简单的示例代码,演示了如何使用读写锁来同步线程间的读写操作。 ```c #include #include #include // 全局读写锁变量 pthread_rwlock_t global_rwlock; void *reader_thread...
在Windows操作系统中,读写锁(Read-Write Lock)是一种多线程同步原语,它允许多个线程同时读取共享资源,但在写入时仅允许...在实际应用中,应当根据具体需求和性能测试结果来调整读写锁的实现策略,以达到最佳效果。
在多线程编程中,读写锁是一种非常重要的同步机制,它允许多个读线程同时访问共享资源,但只允许一个写线程独占资源,从而提高了并发性能。本话题将详细探讨在Windows和Linux环境下,如何使用C++来实现读写锁。 ...
下面我们将详细探讨Java读写锁的概念、实现原理以及如何在实际代码中应用。 1. **读写锁概念**: - 读写锁分为读锁(共享锁)和写锁(独占锁)。读锁允许多个线程同时读取数据,而写锁只允许一个线程进行写操作。 ...
在这个例子中,我们讨论了如何使用读写锁(Read-Write Lock)来实现多线程环境中的并发访问控制。读写锁是一种高级的同步机制,它允许多个线程同时读取共享资源,但只允许一个线程进行写操作。这在数据读取远比写入...
本文将深入探讨易语言中的原子锁与读写锁。 原子操作是一种不可分割的操作,它在执行过程中不会被其他线程中断。在易语言中,原子操作常用于更新计数器、标志位等简单数据类型的场景,避免了线程间的竞态条件。例如...
7. **实际应用**:读写锁在数据库、文件系统、缓存管理等需要并发访问的场景中广泛应用。例如,在数据库中,当多个查询(读)操作可以并行执行,而更新(写)操作需要独占资源时,读写锁可以显著提高性能。 通过...
本文将深入探讨基于Zookeeper实现的分布式读写锁,并利用Zkclient客户端进行操作。Zookeeper是一个分布式服务协调框架,它提供了一种简单且高效的方式来实现分布式锁。 **一、Zookeeper简介** Zookeeper是由Apache...
读写锁是多线程编程中的一个重要概念,它在C#中被广泛应用于提高并发访问数据的效率。本文将深入探讨C#中的读写锁及其使用,帮助你理解和掌握这一关键技能。 首先,读写锁提供了对共享资源的细粒度控制。在C#中,`...
接着,我们可以创建一个静态的ReadWriterLockSlim实例,确保在整个应用程序中只有一个读写锁对象,以避免不同锁之间的竞争条件。 以下是使用读写锁的示例代码: ```csharp using System; using System.IO; using ...
在C++编程中,读写锁(Read-Write Lock)是一种多线程同步机制,它允许多个线程同时读取共享资源,但只允许一个线程写入。这种锁机制提高了对共享数据的并发访问效率,是并发编程中常用的一种工具。本实例将通过代码...
读写锁是多线程编程中的一个重要概念,用于提高并发访问数据时的效率。在并发环境中,如果多个线程同时读取数据,...通过深入理解读写锁的工作原理,并在实际项目中谨慎应用,我们可以构建出更加高效、可靠的并发程序。
在实际应用中,使用这样的读写锁时,开发者需要根据业务需求谨慎设计并发策略,确保在保证数据一致性的同时,优化系统的并发性能。合理地使用读写锁能够减少不必要的线程同步开销,提高程序的运行效率。然而,过度...
本节将通过具体的案例演示MySQL中读写锁的应用及效果。 ##### 1. 环境准备 为了更好地展示读写锁的效果,首先需要准备一个测试环境。这里创建一个名为`user`的表,并插入几条数据: ```sql -- 创建用户表 CREATE ...
关于读写锁算法的Java实现及思考,是一个深入探讨了多线程...通过本文的讲解,我们不仅学习到了读写锁的基本概念,也深入探讨了其在Java中的具体应用和自定义实现,这对于提高软件开发中的并发编程能力具有重要意义。
综上所述,这个项目提供了一套完整的多线程解决方案,包括线程池的高效调度、读写锁的并发控制以及同步原语的使用,这些都是在开发高性能、高并发的Linux应用时不可或缺的技术。开发者可以通过深入研究这些源代码,...
在Linux高级程序设计中,主要介绍了三种线程同步机制:互斥锁、条件变量和读写锁,以及线程与信号的交互。 1. **互斥锁通信机制**: 互斥锁是用于保护临界区的一种机制,确保同一时间只有一个线程能访问共享资源。...
为了在实际代码中应用读写锁,下面是一个简单的例子,展示了如何使用读写锁来安全地读写文件: ```csharp using System.IO; using System.Threading; using System.Threading.Tasks; public class FileHandler { ...
本文将深入探讨标题和描述中提及的各种锁,包括乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁以及行级锁。 1. **乐观锁**:乐观锁假设多线程环境中的冲突较少,所以在读取数据时不加锁,只有...