Java多线程共享资源

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

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

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

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数据的并发访问。
如果在你的类中有超过一个方法在处理共享资源,那么你必须同步所有相关的方法。

使用显式的Lock对象

Lock对象必须被显式的创建,锁定和释放。
这里写图片描述
当你使用synchronized关键字的时候,需要写的代码量更少,并且用户错误出现的可能性也会降低,因此通常只有在解决特殊问题时,才使用显式的Lock对象。例如,用synchronized关键字不能尝试获取锁且最终获取锁会失败,或者尝试着获取锁一段时间,然后放弃它。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;


public class AttemptLocking {

    private ReentrantLock lock = new ReentrantLock();

    public void untimed(){

        boolean captured = lock.tryLock();
        try {
            System.out.println("tryLock(): "+captured);
        } finally{
            if(captured)lock.unlock();
        }

    }

    public void timed(){

        boolean captured = false;
        try {
            captured = lock.tryLock(2, TimeUnit.SECONDS);
        } catch (Exception e) {
            throw new RuntimeException();
        }

        try {
            System.out.println("tryLock(2, TimeUnit.SECONDS):" + captured);
        } finally{
            if(captured)lock.unlock();
        }

    }

    public static void main(String[] args) {
        final AttemptLocking al = new AttemptLocking();
        al.untimed();
        al.timed();

        new Thread(){
            {setDaemon(true);}
            public void run() {
                al.lock.lock();
                System.out.println("acquired");
            };
        }.start();

        //通过yield()来使的后台线程和al.untimed()对al这个资源进行竞争,如果后台线程先获取到资源则把资源锁定了。
        Thread.yield();
        al.untimed();
        al.timed();

    }

}

ReentrantLock允许你尝试着获取锁,如果获取失败,那你可以决定离开去执行其它事情,而不用一直等待这个锁被释放,就像在untimed()方法中所看到的。在timed()中,做出了尝试去获取锁,该尝试可以在2秒之后失败。
这里写图片描述

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值