`

Think in java 846-848 共享资源

阅读更多
共享资源
你可以认为单线程程序是一个单独的实体在你的程序中移动,每个时间点只做一件事情。因为只有一个实体,你永远不会去想两个实体试着在同一时间使用同一资源的问题:像两个人站在同一位置的问题,同时从门穿过或同时讲话。
当并发时,就不是一个实体的问题了。但是你现在可能要处理两个或多个任务彼此交互的问题。如果你不阻止这个问题,你将会发现在同一时间两个任务试着访问同一个银行账户,让同一台打印机打印,修改相同的值。
不正确地访问资源
   考虑下面的例子,当一个任务负责生成偶数,另一个任务消费这些数字。这里,消费数数字的任务仅仅是检查偶数的真实性。
首先我们定义EvenChecker,这个消费的任务,因为它要在接下来的例子里重用。为了将EvenChecker从我们将要检查的各种类型的生成器中解耦出来,我们将要创建一个抽象类IntGenerator,它包含EvenChecker必须知道的最少的方法:它有一个next()方法并且可以取消。这个类不去实现泛型,因为它必须产生一个int,而泛型不支持原始类型。
public abstract class IntGenerator {
private volatile boolean canceled = false;

public abstract int next();

// Allow this to be canceled:
public void cancel() {
canceled = true;
}

public boolean isCanceled() {
return canceled;
}
}
IntCenerator有一个改变boolean canceled标志的cancel()方法和查看这个对象是否已经被取消的isCanceled()方法。因为canceled标志是播哦lean类型,它是原子的,意味着简单的操作像赋值和返回不会被中断,所以你不会看到这个值处于这些简单操作的中间状态。这个canceled标志也设置为volatile为了确保多个线程之间的可见性。在这章的后面你将会了解原子性和可见性。
任何IntGenerator可以在被下面的EvenChecker所测试:
public class EvenChecker implements Runnable {
private IntGenerator generator;
private final int id;

public EvenChecker(IntGenerator g, int ident) {
generator = g;
id = ident;
}

public void run() {
while (!generator.isCanceled()) {
int val = generator.next();
if (val % 2 != 0) {
System.out.println(val + " not even!");
generator.cancel(); // Cancels all EvenCheckers
}
}
}

// Test any type of IntGenerator:
public static void test(IntGenerator gp, int count) {
System.out.println("Press Control-C to exit");
ExecutorService exec = Executors.newCachedThreadPool();
for (int i = 0; i < count; i++)
exec.execute(new EvenChecker(gp, i));
exec.shutdown();
}

// Default value for count:
public static void test(IntGenerator gp) {
test(gp, 10);
}
}
   要注意这个Class中可以被canceled的实例IntGenerator不是Runnable的。反而,EvenChecker却是Runnable的,它用来测试canceled状态的IntGenerator类却在它的run()方法里。
这种方式,这些共享共用资源(IntGenerator)的任务观察这个资源的信号去决定是否终止执行。这样会终止所谓的竞争条件,在那里,两个或多个任务竞争着去响应这个条件,因此产生冲突或者产生不一致的结果。你必须小心地考虑和防止所有可能并发系统可能崩溃的方式。例如,一个任务可能依赖于另一个任务,因为任务的停止顺序是没有保证的。这里 ,通过使任务依赖于非任务,我们消除了潜在的竞争条件。
   test()方法建立并通过启动一些使用相同IntGenerator的EvenChecker任务来执行任何IntGenerator类型的测试。如果IntGenerator产生失败,test()将会报告并返回;否则,你必须使用Control-C来终止它。
我们看到的第一个IntGenerator有一个next()方法产生一系列偶数:
public class EvenGenerator extends IntGenerator {
private int currentEvenValue = 0;

public int next() {
++currentEvenValue; // Danger point here!
++currentEvenValue;
return currentEvenValue;
}

public static void main(String[] args) {
EvenChecker.test(new EvenGenerator());
}
}
   可能发生这种情况,一个任务在另一个任务刚执行完第一个currentEvenValue的增加而没有执行第二个增值操作后调用call()。这使得值进入不正确的状态。为了证明这个可以发生,EvenChecker.test()创建一组EventChecker对象来不断地读取EvenGenerator产生的数据并测试它们是否是偶数。如果不是,报告错误并且终止程序。
   这个程序将最终失败,因为EventChecker任务会访问EvenGenerator处于不正确状态的信息。然而,在EvenGenerator 完成多次循环前, 程序可能不会检查到这个问题,循环次数依赖于你操作系统和其他的实现细节。如果你想快些地看到失败,可以将yield()放到第一和第二个增值语句之间。这是多线程程序问题的一部分—--如果失败的概率非常低,即使有缺陷存在,程序可以看起来是正确的。
  还有重要的一点需要知道,增值操作本身需要多步,并且任务可以通过线程机制在增值的中间状态被挂起。--就是说,在Java中,增值操作不是原子操作。所以一个简单的增值操作在没有保护的情况也是不安全的。
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics