在本节中,学习如何使用ReadWriteLock接口,实现程序用此接口控制存储两种产品价格的对象。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
###实现过程
通过如下步骤完成范例:
-
创建名为PricesInfo的类,用来存储两种产品的价格信息:
public class PricesInfo {
-
定义两个名为price1和price2的double类型属性:
private double price1; private double price2;
-
定义名为lock的ReadWriteLock对象:
private ReadWriteLock lock;
-
实现类构造函数,初始化三个属性。其中为lock属性初始化新的ReentrantReadWriteLock对象:
public PricesInfo(){ price1 = 1.0; price2 = 2.0; lock = new ReentrantReadWriteLock(); }
-
实现getPrice1()方法,返回price1属性值,使用读锁来控制使用此属性值:
public double getPrice1() { lock.readLock().lock(); double value = price1; lock.readLock().unlock(); return value; }
-
实现getPrice2()方法,返回price2属性值,使用读锁来控制使用此属性值:
public double getPrice2() { lock.readLock().lock(); double value = price2; lock.readLock().unlock(); return value; }
-
实现setPrices()方法来确定两个使用写锁来控制使用的属性值。设置线程休眠10秒钟,这表明尽管它在使用写锁,但也没有其它线程控制读锁:
public void setPrices(double price1, double price2){ lock.writeLock().lock(); System.out.printf("%s: PricesInfo: Write Lock Adquired.\n", new Date()); try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } this.price1 = price1; this.price2 = price2; System.out.printf("%s: PricesInfo: Write Lock Released.\n", new Date()); lock.writeLock().unlock(); }
-
创建名为Reader的类,指定其实现Runnable接口。此类实现一个读取程序,获得PricesInfo类属性的值:
public class Reader implements Runnable {
-
定义PricesInfo对象,实现初始化此对象的类构造函数:
private PricesInfo pricesInfo; public Reader (PricesInfo pricesInfo) { this.pricesInfo = pricesInfo; }
-
实现此类的run()方法,读取10遍两种商品的价格:
@Override public void run() { for( int i = 0 ; i < 10 ; i++){ System.out.printf("%s : %s : Price 1 : %f\n", new Date(), Thread.currentThread().getName(), pricesInfo.getPrice1()); System.out.printf("%s : %s : Price 2 : %f\n", new Date(), Thread.currentThread().getName(), pricesInfo.getPrice2()); } }
-
创建名为Reader的类,指定其实现Runnable接口。此类实现一个修改程序,设置PricesInfo类属性的值:
public class Writer implements Runnable{
-
定义PricesInfo对象,实现初始化此对象的类构造函数:
private PricesInfo pricesInfo; public Writer (PricesInfo pricesInfo) { this.pricesInfo = pricesInfo; }
-
实现run()方法,循环修改两种商品价格三次,每次之间休眠2秒:
@Override public void run() { for(int i = 0 ; i < 3 ; i++){ System.out.printf("%s : Writer : Attempt to modify the prices.\n", new Date()); pricesInfo.setPrices(Math.random() * 10, Math.random() * 8); System.out.printf("%s : Writer : Prices have been modified.\n", new Date()); try { Thread.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
-
创建本范例中的主类,实现一个包含main()方法的Main类:
public class Main { public static void main(String[] args) {
-
创建PricesInfo对象:
PricesInfo pricesInfo = new PricesInfo();
-
创建五个Reader对象和五个执行它们的Thread对象:
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对象和执行它的Thread对象:
Writer writer = new Writer(pricesInfo); Thread threadWriter = new Thread(writer);
-
启动读写线程:
for (int i = 0 ; i < 5 ; i ++){ threadsReader[i].start(); } threadWriter.start();
工作原理
下图显示执行本范例输出的部分内容:
当写入程序控制写锁时,所有读取程序都无法得到数据。通过控制台可以看到Write Lock Acquired信息后面有一些读取程序信息,这些时之前已经执行完并且没有实时输出的指令信息。一旦写入程序释放了写锁,读取程序将再次获得访问价格信息的权限,显示新的价格。
如之前所述,ReentrantReadWriteLoc类包含两种锁:一种是读取操作锁,一种是写入操作锁。读取操作使用的锁通过ReadWriteLock接口定义的readLock()方法获得。读锁是实现Lock接口的对象,所以可以使用lock()、unlock()、和tryLock()方法。写入操作使用的锁通过ReadWriteLock接口定义的writeLock()方法获得。写锁也是实现Lock接口的对象,所以可以使用lock()、unlock()、和tryLock()方法。开发人员的职责是确保正确使用这些锁,与其初始设计的使用目的相同。当使用Lock接口的读锁时,不能修改变量值。否则,可能出现与不一致性相关的数据错误。
更多关注
- 本章中”锁同步代码块”小节。
- 第九章“测试并发应用”中的“监控锁接口”小节。