Java多线程 之 访问共享资源(六)

多线程产生问题的原因大多都是访问了共享资源。所谓共享资源就是只能被一个线程访问的内存对象(如变量、打印机、文件、输入输出端口等)。下面看一个《Thinking in java》第四版中给出的一个例子:

package org.fan.learn.thread.share;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * Created by fan on 2016/6/12.
 */
public class EvenChecker implements Runnable {
    private IntGenerator intGenerator;
    private final int id;
    public EvenChecker(int id, IntGenerator intGenerator) {
        this.id = id;
        this.intGenerator = intGenerator;
    }
    public void run() {
        while (!intGenerator.isCanceled()) {
            int val = intGenerator.next();
            if (val %2 != 0) {
                System.out.println(val + "not even!");
                intGenerator.cancel(); //线程安全的
            }
        }
    }
    public static void test(IntGenerator intGenerator, int threadCount) {
        System.out.println("Press control-c to exit");
        ExecutorService exe = Executors.newCachedThreadPool();
        for (int i = 0; i < threadCount; i++) {
            exe.execute(new EvenChecker(i, intGenerator));
        }
        exe.shutdown();
    }
    public static void test(IntGenerator intGenerator) {
        test(intGenerator, 10);
    }
}
package org.fan.learn.thread.share;
/**
 * Created by fan on 2016/6/12.
 */
public abstract class IntGenerator {
    private volatile boolean canceled = false;
    public abstract int next();
    public boolean isCanceled() {
        return canceled;
    }
    public void cancel() {
        this.canceled = true;
    }
}

其中EvenChecker作为测试任务:检测IntGenerator产生的整数是否是偶数。
EvenChecker的test实现中默认创建了10个线程,共享一个IntGenerator对象。这产生了资源竞争。注意:IntGenerator中的canceled属性是基本类型boolean类型的,而在java中对基本类型的基本操作(如赋值、返回值)是原子的。也就是说,java中对除long和double之外的基本类型的诸如赋值、返回值的简单操作是线程安全的(但是也不要过分依赖这一点,最好使用同步机制)。但是也要注意IntGenerator中的canceled是volatile的(volatile保证某个任务对canceled修改之后,其他任务可以实时看到,也就是说其他任务读取的canceled数据不是从该任务所运行的CPU缓存中获取的,而是从内存中读取的)。
在《Thinking in java》中说,一个任务不能依赖于另一个任务(这里说的任务就是线程),因为,任务的结束顺序是无法得到保证的。在这里,EvenChecker依赖的IntGenerator是非任务的(IntGenerator不是一个线程),可以消除潜在的资源竞争。
下面写一个IntGenerator的实现类,来展示不当的资源竞争的结果:

package org.fan.learn.thread.share;
/**
 * Created by fan on 2016/6/12.
 */
public class EvenGenerator extends IntGenerator {
    private int evenValue;
    @Override
    public int next() {
        ++evenValue;
        //快速产生不正确的结果
        //Thread.yield();
        ++evenValue;
        return evenValue;
    }
    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator());
    }
}

在我win7 JVM1.8机器上,如果不加Thread.yield()调用,很长时间都不会产生错误,为了展示错误添加了yield调用。输出结果如下:
结果一:
Press control-c to exit
9 not even!
结果二:
Press control-c to exit
171 not even!
287 not even!
289 not even!
结果三:
Press control-c to exit
513 not even!
515 not even!
所以,在看上去运行结果正常的情况下,并不能保证代码是正确的,这就是多线程的特性,这也是多线程编码的难点。
注意:在java中,递增不是原子性的操作

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值