zoukankan      html  css  js  c++  java
  • java多线程与线程池(二):几种实现加锁的方法(1)——ReentrantLock类+Condition条件对象

    java多线程中,需要防止代码块受并发访问产生的干扰。比如下图的并发访问,如果不使用锁机制,就会产生问题

    可以看到这里之前线程2之前的5900被后来线程1写入的5500直接覆盖了,导致add  900 这个操作消失了。

    public class Bank {
        private final double[] accouts;
    
        public Bank(int n,double initialBalance) {
            accouts = new double[n];
            Arrays.fill(accouts, initialBalance);
        }
    
        public void transfer(int from, int to, double amount) throws InterruptedException{
                if (accouts[from] < amount)
                    return;
                System.out.println(Thread.currentThread());
                accouts[from] -= amount;
                System.out.printf("%10.2f from %d to %d", amount, from, to);
                accouts[to] += amount;
                System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
        }
    
        public double getTotalBalance() {
                double sum = 0;
                for (double a : accouts)
                    sum += a;
    
                return sum;
        }
    
        public int size(){
            return accouts.length;
        }
    }
    
    
    public class UnsynchBankTest {
        public static final int NACCOUNTS = 100;
        public static final double INITIAL_BALANCE = 1000;
        public static final double MAX_AMOUNT = 1000;
        public static final int DELAY = 10;
    
        public static void main(String[] args) {
            Bank bank = new Bank(NACCOUNTS,INITIAL_BALANCE);
            for (int i = 0; i < NACCOUNTS; i++) {
                int fromAccount = i;
                Runnable r = ()->{
                    try{
                        while(true){
                            int toAccount = (int) (bank.size() * Math.random());
                            double amount = MAX_AMOUNT * Math.random();
                            bank.transfer(fromAccount, toAccount, amount);
                            Thread.sleep((int) (DELAY * Math.random()));
                        }
                    }catch (InterruptedException e){}
                };
                Thread t = new Thread(r);
                t.start();
            }
        }
    }

    该程序由于没有加锁

    所以会出现金额总数出错的情况,参考上图覆盖写入的情况。

    所以我们要使用锁机制在使用临界资源时对其加锁(禁止其他线程并发访问或防止并发访问时产生干扰)

    实现加锁就主要有下面几种方法:

    一、使用ReentrantLock类+Condition条件对象

    首先使用ReentraLock类就能实现简单的加锁了,在这种锁机制下,临界区需要使用try-finally括起来,因为加锁之后,若临界区里运行出现问题而抛出异常,也要确保锁被释放,否则其他线程会一直拿不到锁而无法运行。

    单使用ReentrantLock的代码如下:

    public class Bank {
        private final double[] accouts;
        private Lock banklock;
    
        public Bank(int n,double initialBalance) {
            accouts = new double[n];
            Arrays.fill(accouts, initialBalance);
            banklock = new ReentrantLock();
        }
    
        public void transfer(int from, int to, double amount) throws InterruptedException{
            banklock.lock();
            try{
                System.out.println(Thread.currentThread());
                accouts[from] -= amount;
                System.out.printf("%10.2f from %d to %d", amount, from, to);
                accouts[to] += amount;
                System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
            }finally {
                banklock.unlock();
            }
        }

    这样加了锁之后,确实金钱总额不会再发生改变,一直是100000,但当我把账户当前剩余资金打印出来时,发现:

    其实账户剩余资金为0了,竟然也还在转账!

    这样显然是不可以的。那么就需要在线程进入临界区后,判断某一条件,满足之后才继续执行,否则进入阻塞状态,并释放自己持有的锁。

    但这个判断又不能使用简单的if:

    这样的线程完全有可能在成功通过if之后,但在transfer之前被中断,然后在线程再次运行前可能账户余额已经低于提款金额。所以就要用到条件对象了,首先创建新的Condition对象:

    private Condition sufficientFunds;

    如果transfer发现余额不足,就调用sufficientFunds.await();

    此时当前进程就会被阻塞,并放弃锁。此时我们希望另一个线程拿到锁可以进行增加自己余额的操作。

    进入阻塞状态的线程,即使锁可以,他也不能马上解除阻塞,直到另一个线程调用同一个条件上的signalAll方法为止。

    所以另一个线程转账完毕后,应该调用sufficientFunds.signalAll();这一调用会重新激活因为这一条件而被阻塞的所有线程,这些线程会从等待集中被移出,再次成为可运行的,这时他应该再去检测该条件——因为signalAll方法仅仅是通知正在等待的线程:此时有可能已经满足条件,值得再次去检测该条件。

    所以检测条件的语句应该这么写:

    while(!(ok to proceed))
        condition.await();

    对bank改造如下:

    public class Bank {
        private final double[] accouts;
        private Lock banklock;
        private Condition sufficientFunds;
    
        public Bank(int n,double initialBalance) {
            accouts = new double[n];
            Arrays.fill(accouts, initialBalance);
            banklock = new ReentrantLock();
            sufficientFunds = banklock.newCondition();
        }
    
        public void transfer(int from, int to, double amount) throws InterruptedException{
            banklock.lock();
            try{
                while (accouts[from] < amount)
                    sufficientFunds.await();
                System.out.println(Thread.currentThread());
                accouts[from] -= amount;
                System.out.printf("%10.2f from %d to %d, remain %10.2f", amount, from, to,accouts[from]);
                accouts[to] += amount;
                System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
                sufficientFunds.signalAll();
            }finally {
                banklock.unlock();
            }
        }
    
        public double getTotalBalance() {
            banklock.lock();
            try {
                double sum = 0;
                for (double a : accouts)
                    sum += a;
    
                return sum;
            }finally {
                banklock.unlock();
            }
        }

    加上了锁和条件对象,运行后得到如图结果:

    可以看到余额不会再出现负数的情况。

  • 相关阅读:
    从输入网址到页面呈现的过程
    Git 常用命令合集
    Jquery浅克隆与深克隆
    CSS变量教程
    设计模式
    Servlet和JSP简述
    SQL Server,MySQL,Oracle三者的区别
    mysql事务处理
    计时器
    java中length,length(),size()区别
  • 原文地址:https://www.cnblogs.com/MYoda/p/11271730.html
Copyright © 2011-2022 走看看