微信扫一扫

028-83195727 , 15928970361
business@forhy.com

Java多线程共享资源

java,多线程2016-11-10

多线程中的一个核心问题就是对共享资源的读写问题。你永远都不知道一个线程何时在运行。如果同时有多个线程对一个对象进行读写,结果就会出现脏数据

接下来展示一个多线程同时对一个对象进行读写出现脏数据的案例。

为了方便解耦,创建一个抽象类。

public abstract class Ingenerator  {

    private volatile boolean caceled = false;
    public abstract int next();

    public void cacel(){
        caceled = true;
    }
    public boolean isCanceled(){
        return caceled;
    }

}

EvenChecker任务总是读取和测试从其相关的Ingenerator 返回的值。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EvenChecker implements Runnable {

    private Ingenerator generator;
    public EvenChecker(Ingenerator g){
        generator = g;
    }

    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(!generator.isCanceled()){
            int val = generator.next();
            if(val%2!=0){
                System.out.println(val + " not even!");
                generator.cacel();
            }
        }

    }

    public static void test(Ingenerator gp,int count){
        System.out.println("Press Control to exit");
        ExecutorService exec = Executors.newCachedThreadPool();
        for(int i = 0;i<count;i++){
            exec.execute(new EvenChecker(gp));
        }
        exec.shutdown();
    }

}

继承Ingenerator抽象类的next()产生偶数。

public class EvenGenerator extends Ingenerator {
    private int currentEvenValue = 0;

    @Override
    public int next() {
        ++currentEvenValue;
        ++currentEvenValue;
        return currentEvenValue;
    }

    public static void main(String[] args) {
        EvenChecker.test(new EvenGenerator(), 10);
    }

}

一个线程有可能在另一个线程执行第一个++currentEvenValuede 操作之后,还没有来得及执行第二个操作之前调用了next()方法。这个时候可能就会产生一个奇数。也就是脏数据。

基本上所有的并发模式在解决线程冲突问题的时候,都是采用序列化访问共享资源的方案。
一个屋子只有一个浴室,多个人(多个线程)都希望能单独使用这个浴室(共享资源)。为了使用浴室,一个人先敲门,看看有没有人,如果没人的话,他就进入浴室并锁上门。等待使用浴室的人们挤在浴室门口,当锁住浴室门的那个人打开锁离开的时候,离门最近的那个人可能进入浴室,可以通过yield()和setPriority()来给线程调度器提供建议,虽然未必有用。还是取决于CPU。

Java提供关键字synchronized的形式,来防止资源冲突。当线程执行被synchronized关键字保护的代码片段时,它将检查锁是否可用,然后获取锁,执行代码,释放锁。

注意,在使用并发的时候,将域(currentEvenValuede)设置为private非常重要,否则,synchronized关键字就不能阻止其它线程之间访问域。
一个线程可以多次获得对象的锁,如果一个方法在同一对象上调用第二个方法,后者又调用同一对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁(即锁被完全释放),其计数变为0。在线程第一次给对象加锁的时候,计数变为1.每当这个相同的线程在这个对象获得锁时,计数都会递增。显然,只有首先获得了锁的线程才能允许继续获取多个锁。每当任务离开一个synchronized方法,计数递减,当计数为0时,锁被完全释放。

针对每个类,也有一个锁,所以synchronized static方法可以在类的范围内防止对static数据的并发访问。
如果在你的类中有超过一个方法在处理共享资源,那么你必须同步所有相关的方法。