`
frank-liu
  • 浏览: 1669719 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

读写锁的应用

 
阅读更多

前言

    在前面的一篇文章里,我曾经讨论了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

分享到:
评论

相关推荐

    linux 读写锁应用实例

    inux 读写锁应用实例 /*使用读写锁实现四个线程读写一段程序的实例,共创建了四个新的线程,其中两个线程用来读取数据,另外两个线程用来写入数据。在任意时刻,如果有一个线程在写数据,将阻塞所有其他线程的任何...

    读写锁实现例子

    读写锁实现例子

    标准C写的线程池和读写锁

    linux下用标准C写的线程池全部实现代码,其中包含有读写锁的实现和应用

    各种锁汇总,乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁、行级锁等

    各种锁汇总,乐观锁、悲观锁、分布式锁、可重入锁、互斥锁、读写锁、分段锁、类锁、行级锁等

    rwlock:Haxe 的读写锁

    读写锁 应用程序接口 创建 new( maxReaders, ?waitLogTimeout, ?waitLogger) 功能级别 read(func, ?wait) write(func, ?wait) 小鬼水平 // read prepareRead(?wait) releaseRead() // write prepareWrite(?wait) ...

    面试官:有没有比读写锁更快的锁?

    目录面试三连什么是读写锁StampedLock横空出世StampedLock三种模式基本语法StampedLock完整的demo让StampedLock性能更上一楼的乐观读你了解乐观读的应用场景吗使用StampedLock的注意事项总结 面试三连 面试官:了解...

    e语言-易语言读写锁

    易语言读写锁源码,源码是多线程的应用例程,代码写得不错,想学习多线程的朋友可以下载看看了。强烈推荐。

    易语言-易语言读写锁

    易语言读写锁源码,源码是多线程的应用例程,代码写得不错,想学习多线程的朋友可以下载看看了。强烈推荐。

    8、读写锁ReentrantReadWriteLock&StampLock详解.pdf

    6.JUC并发工具类在大厂的应用场景详解 (1).pdf ...8、读写锁ReentrantReadWriteLock&StampLock详解.pdf 9、并发容器 (Map、List、Set) 实战及其原理.pdf 10、阻塞队列BlockingQueue 实战及其原理分析.pdf

    Android例子源码解决多线程读写sqlite数据库锁定问题

    SQLite实质上是将数据写入一个文件,通常情况下,在应用的包名下面都能找到xxx.db的文件,拥有root权限的手机,可以通过adb shell,看到data/data/packagename/databases/xxx.db这样的文件。我们可以得知SQLite是...

    ClientServerHashTable:简单的客户端-服务器应用程序,利用带有读写锁的并发哈希表,通过共享内存进行通信

    整数是一个受锁保护的简单计数器,该计数器标记缓冲区/队列中的最后一个可用插槽(客户端需要)。 如果缓冲区足够大(例如无限大),则简单的原子增量就足够了(例如fetch_and_add)。 每个“ OPS”数组的元素都是...

    面试官:Zookeeper怎么解决读写、双写并发不一致问题,以及共享锁的实现原理?.doc

    Zookeeper 解决读写、双写并发不一致问题,以及共享锁的实现原理 Zookeeper 是一个广泛使用的分布式协调服务,可以帮助应用程序在分布式环境中实现高可用性和高性能。今天,我们将讨论 Zookeeper 是如何解决读写、...

    基于单片机的IC卡读写专业系统设计.doc

    这些应用都需要使用IC卡读写系统来实现数据的读写操作。 知识点10:单片机应用系统设计 单片机应用系统设计是指使用单片机实现的各种应用,如自动控制系统、智能仪器设计等。这些应用都需要使用单片机来实现数据的...

    一种避免对flash分区频繁读写的方法.pdf

    一种避免对flash分区频繁读写的方法 本资源摘要信息介绍了一种避免对flash分区频繁读写的方法,该方法可以极大...本发明的技术方案可以广泛应用于嵌入式Linux系统中,对nandflash存储介质的使用寿命产生了积极的影响。

    深入探索Zookeeper:实战应用与高效策略

    通过具体案例,我们理解了Zookeeper的加锁原理,包括并发问题处理、羊群效应、读写锁机制等。此外,文章还详细介绍了使用Zookeeper构建注册中心的步骤,包括服务注册、节点监听、自动发现和负载均衡等关键概念。这些...

    (完整word)基于51单片机的IC卡读写系统设计.doc

    本文总结了基于51单片机的IC卡读写系统设计的相关知识点,包括IC卡的发展和应用情况、IC卡的工作原理、51单片机的概述、IC卡读写系统设计、单片机最小系统设计、单片机与IC卡读写器接口电路设计、编写IC卡读写控制...

    一种Linux内核自旋锁死锁检测机制的设计与实现.pdf

    在 Linux 内核中,自旋锁的种类有很多,包括基本型、读写自旋、排队锁和 MCS 自旋锁等。每种自旋锁都有其特点和使用场景,需要根据不同的情况选择合适的自旋锁。 在设计自旋锁死锁检测机制时,需要考虑到自旋锁的...

    Linux系统编程之线程同步

    3. 读写锁是“读模式加锁”时, 既有试图以写模式加锁的线程,也有试图以读模式加锁的线程。那么读写锁会阻塞随后的读模式锁请求。优先满足写模式锁。读锁、写锁并行阻塞,写锁优先级高 读写锁也叫共享-独占锁。当...

    (完整版)基于51单片机的IC卡读写器的设计于实现.doc

    在 IC 卡应用系统的设计中,读写设备对 IC 卡的读写控制的每一个环节都应当遵照相应的国际标准,才能保证数据的正确读取。 (5)89C51 单片机的应用:89C51 单片机是一种常用的 MCU,具有高性价比、低功耗、高可靠...

    第吉尔电子门锁系统(接触式IC卡门锁)

    (将抓记录卡插入到读写器中→单击“抓记录卡” →输入姓名→单击“确定” →取出卡片→插入要读取开门记录的那把锁→门锁的绿灯亮,待绿灯灭了之后→将卡取出→将抓记录卡插入读写器中→启动主画面下方“B级”栏→...

Global site tag (gtag.js) - Google Analytics