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

本篇是《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可重入读写锁分析

猜你喜欢

转载自blog.csdn.net/qq_24986539/article/details/52441810
今日推荐