共享可变性设计中存在风险以及解决方法(四)

2019-07-14 02:41发布

本篇是《Java虚拟机并发编程》第五章的阅读笔记 在(三)中,我们在代码里没有使用任何显示的同步操作,直接作用在可变变量上,当然是因为在程序中只有一个可变字段。如果程序中不止一个与可变状态相关或依赖的变量,那么我们就无可避免地要使用显示的同步操作。
到目前为止重构都达到了预想的效果,但我们还要想更高要求的目标迈进:
  1. 追踪并记录电源的使用情况。即每次电源电量消耗完毕的时候,我们度需要把电源的使用次数进行累加
当然这也意味着,我们必须保证对于变量level和useage的改动是原子的。(在同一个线程里修改这个两个变量,要么全部修改成功,要么全部都不变) 我们这里选择使用ReentrantReadWriteLock。该类同时提供两把锁(即读锁和写锁)。由于使用了显示的锁,所以就可以将level字段由AtomicLong改回long类型。 package com.ensureatomicity; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class EnergySource { private final long MAXLEVEL = 100; private long level = MAXLEVEL; private long usage = 0; private final ReadWriteLock monitor = new ReentrantReadWriteLock(); private static final ScheduledThreadPoolExecutor replenishTimer = new ScheduledThreadPoolExecutor(10); private ScheduledFuture replenishTask; private EnergySource(){} private void init(){ replenishTask = replenishTimer.scheduleAtFixedRate(new Runnable(){ public void run(){ System.out.println(System.nanoTime()/1.0e9); replenish(); } }, 0, 1, TimeUnit.SECONDS); } public static EnergySource create(){ final EnergySource energySource = new EnergySource(); energySource.init(); return energySource; } public long getUnitsAvailable(){ monitor.readLock().lock(); try{ return level; }finally{ monitor.readLock().unlock(); } } public long getUsageCount(){ monitor.readLock().lock(); try{ return usage; }finally{ monitor.readLock().unlock(); } } public boolean useEnergy(final long units){ monitor.writeLock().lock(); try{ if(units>0 && level>=units){ level -= units; usage++; return true; }else{ return false; } }finally{ monitor.writeLock().lock(); } } public void stopEnergySource(){ replenishTask.cancel(false); } private void replenish(){ monitor.writeLock().lock(); try{ if(level < MAXLEVEL){ level++; } }finally{ monitor.writeLock().unlock(); } } }
  1. 在上面的代码中,引入两个新字段:usage和monitor。其中usage的作用是记录电源被使用的次数。
  2. 在useEnergy函数中,我们先获得了写锁,如果可以的话还可以在获得锁的时候指定一个超时时间。一旦获得写锁的操作完成,就可以更改两个变量的值。最后在finally块中,安全地将锁释放。 遗憾的是,这一版本的代码比之前的版本复杂了不少,而这错误正是由复杂性所产生的。当然还有方法可以避免使用显示的同步操作。

ReentrantReadWriteLock简单介绍
  1. 读锁,可以使得读操作并发执行,但是如果巧合有线程在进行写操作,那么该读操作会被阻塞。
  2. 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁,要获取写入锁,只能先释放读取锁。 可以参考别人写的ReentrantReadWriteLock 可重入读写锁的理解ReentrantReadWriteLock可重入读写锁分析