本篇是《Java虚拟机并发编程》第五章的阅读笔记
在(三)中,我们在代码里没有使用任何显示的同步操作,直接作用在可变变量上,当然是因为在程序中只有一个可变字段。如果程序中不止一个与可变状态相关或依赖的变量,那么我们就无可避免地要使用显示的同步操作。
到目前为止重构都达到了预想的效果,但我们还要想更高要求的目标迈进:
- 追踪并记录电源的使用情况。即每次电源电量消耗完毕的时候,我们度需要把电源的使用次数进行累加
当然这也意味着,我们必须保证对于变量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();
}
}
}
- 在上面的代码中,引入两个新字段:usage和monitor。其中usage的作用是记录电源被使用的次数。
- 在useEnergy函数中,我们先获得了写锁,如果可以的话还可以在获得锁的时候指定一个超时时间。一旦获得写锁的操作完成,就可以更改两个变量的值。最后在finally块中,安全地将锁释放。
遗憾的是,这一版本的代码比之前的版本复杂了不少,而这错误正是由复杂性所产生的。当然还有方法可以避免使用显示的同步操作。
ReentrantReadWriteLock简单介绍
- 读锁,可以使得读操作并发执行,但是如果巧合有线程在进行写操作,那么该读操作会被阻塞。
- 写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁,要获取写入锁,只能先释放读取锁。
可以参考别人写的ReentrantReadWriteLock 可重入读写锁的理解和ReentrantReadWriteLock可重入读写锁分析