zoukankan      html  css  js  c++  java
  • java 多线程(三)条件对象

    转载请注明出处:http://blog.csdn.net/xingjiarong/article/details/47417383
    在上一篇博客中,我们学会了用ReentrantLock来控制线程訪问同一个数据,防止出现Race Condition。这一次呢。我们继续深入的学习,学习一下java中的条件对象。条件对象在多线程同步中用到的比較多。

    首先,我们来介绍一下临界区。

    临界区:在同步的程序设计中。临界区指的是一个訪问共用资源的程序片段,而这些共用资源又具有无法同一时候被多个线程訪问的特性。 当有线程进入临界区时,其它线程或是进程必须等待,在一些情况下必须在临界区的进入点与离开点採用一些特殊的方法。以确保这些共用资源是被相互排斥使用的。

    如今我们来看一个新的样例,这是一个银行转账的样例。在Bank类中,我们声明了一个10个大小的数组,用来表示银行中的10个账户。而相应的数值就是这个账户中相应的金额。Bank类提供了转移账户资金的方法,能够从from账户,转移amount的资金到to账户。还提供了获得这些账户的总金额的方法,getTotalBalabce(),由于资金转移是发生在这10个账户中的,所以不管如何转移。总的金额应该是不变的。

    Bank.java

    import java.util.concurrent.locks.ReentrantLock;
    
    public class Bank {
        /**
         * 利用数组模拟银行账户
         */
        private final double accounts[];
        private ReentrantLock lock = new ReentrantLock();
    
        public Bank() {
            accounts = new double[10];
            /*
             * 初始化,使每一个账户都初始有initialBalance金钱
             */
            for (int i = 0; i < 10; i++)
                accounts[i] = 1000;
        }
    
        /**
         * 资金转移的方法
         * 
         * @param from
         *            源账户
         * @param to
         *            目标账户
         * @param amount
         *            转移金额
         */
        public void transfer(int from, int to, double amount) {
            lock.lock();
            try {
                if (accounts[from] < amount)
                    return
                System.out.print(Thread.currentThread());
                accounts[from] -= amount;
                System.out.printf("%5.2f from %d to %d", amount, from, to);
                accounts[to] += amount;
                System.out.printf(" Total Balance:%5.2f
    ", getTotalBalance());
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        /**
         * 获取全部账户的总金额
         * 
         * @return
         */
        public double getTotalBalance() {
            lock.lock();
            double sum = 0;
            try {
    
                for (double a : accounts)
                    sum += a;
            } finally {
                lock.unlock();
            }
            return sum;
        }
    
        /**
         * 获取如今账户的数量
         * 
         * @return
         */
        public int size() {
            return accounts.length;
        }
    }
    

    TransferRunnable这个类实现了Runnable接口,我们在Main函数中拿他做线程,它的run方法就是产生一个随机的目标账户和转移的金额。然后调用Bank类的方法将资金转移到目标账户中去。

    TransferRunnable.java

    
    public class TransferRunnable implements Runnable{
    
        private Bank bank;
        private int fromAccount;
    
        public TransferRunnable(Bank b,int from){
            bank=b;
            fromAccount=from;
        }
    
        public void run(){
            try{
                while(true)
                {
                    int toAccount = (int)(bank.size()*Math.random());
                    double amount = 1000*Math.random();
                    bank.transfer(fromAccount, toAccount, amount);
                    Thread.sleep(1000);
                }
            }catch(Exception e){
                e.printStackTrace();
            }
        }   
    }
    

    在主程序中,我们创建了10个线程,每一个线程都从一个相应的账户转移一定的资金到随机的账户中去。
    Main.java

    public class Main {
    
        public static void main(String[] args) {
            Bank b = new Bank();
            Thread[] threadArray = new Thread[10];
            for (int i = 0; i < 10; i++) {
                threadArray[i] = new Thread(new TransferRunnable(b, i));
            }
    
            for(int i=0;i<10;i++){
                threadArray[i].start();
            }
        }
    
    }
    

    在这个程序中有共同的变量——10个银行账户的资金,所以就有相应的临界区——Bank类的transfer方法和getTotalBalabce方法,假设不正确临界区加以保护的话,会导致多个线程同一时候进入临界区,而导致结果错误,读者能够试验一下,将ReentrantLock加锁的地方都去掉,看看总金额是不是会产生变化,在我的试验中,不但总金额的数量不正确。就连打印的语句顺序都是不正确的,说明一个线程在System.out.printf()的过程中都被别的线程打断了。

    看了这个样例,相信大家对ReentrantLock的使用更加熟练了。

    如今我们来考虑这样一个问题,大家看到transfer方法中,假设账户中的金额不足的时候就立马返回而不再发生资金转移,我如今不想这样处理了。我想要这样处理,假设账户资金不足的话,就一直等待,直到其它的账户向这个账户转移足够的资金了。然后再发生资金转移。

    问题是。操作这个账户的线程已经获得了lock,其它的线程无法再进入lock方法,也就不可能向这个账户转移相应的资金了,所以说,这个线程必须放弃lock的控制权,让其它的线程获得。

    Java中提供了条件对象,Condition类,来配合ReentrantLock实现对临界区的控制,我们对Bank类做以下的改变。

    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class Bank {
        /**
         * 利用数组模拟银行账户
         */
        private final double accounts[];
        private ReentrantLock lock = new ReentrantLock();
        private Condition condition;
    
        public Bank() {
            accounts = new double[10];
            /*
             * 初始化,使每一个账户都初始有initialBalance金钱
             */
            for (int i = 0; i < 10; i++)
                accounts[i] = 1000;
    
            condition = lock.newCondition();
        }
    
        /**
         * 资金转移的方法
         * 
         * @param from
         *            源账户
         * @param to
         *            目标账户
         * @param amount
         *            转移金额
         */
        public void transfer(int from, int to, double amount) {
            lock.lock();
            try {
                while (accounts[from] < amount)
                    condition.await();
                System.out.print(Thread.currentThread());
                accounts[from] -= amount;
                System.out.printf("%5.2f from %d to %d", amount, from, to);
                accounts[to] += amount;
                System.out.printf(" Total Balance:%5.2f
    ", getTotalBalance());
                condition.signalAll();
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    
        /**
         * 获取全部账户的总金额
         * 
         * @return
         */
        public double getTotalBalance() {
            lock.lock();
            double sum = 0;
            try {
    
                for (double a : accounts)
                    sum += a;
            } finally {
                lock.unlock();
            }
            return sum;
        }
    
        /**
         * 获取如今账户的数量
         * 
         * @return
         */
        public int size() {
            return accounts.length;
        }
    }
    

    首先,声明了一个Condition类的对象引用变量condition,在Bank的构造方法中进行的初始化。初始化是是调用的ReentrantLock类的方法newCondition(),说明Condition对象时依赖于ReentrantLock对象的。一个ReentrantLock对象能够创建多个条件对象。名字通常以控制的条件来命名。

    然后在transfer方法中,以下两行是关键:

    while (accounts[from] < amount)
        condition.await();

    用while循环检測账户的剩余金额,假设剩余金额不足就调用await()方法,await()会堵塞线程,并释放线程保持的锁。这时其它的线程就能够进入临界区了,当账户的剩余金额满足条件时,等待的线程也不会主动的唤醒,直到有一个线程调用了signalAll()方法。signalAll()方法唤醒全部由于不满足条件而等待的线程,可是线程不一定能够继续向下运行,由于signalAll()唤醒线程时,并非告诉线程你的条件满足了,能够继续向下运行了,而是告诉线程,如今条件改变,你能够又一次检測条件是否满足了。假设条件被满足。那么也唯独一个线程能够继续向下运行。由于一旦一个对象获得了临界区的锁,其它的线程就不能再进入临界区了。

    那么应当什么时候调用signalAll()方法呢,从经验上将应该在对象的状态有利于等待线程的方向改变时调用。

    这里我们再发生资金转移后调用,由于资金转移后。其它的线程有可能满足条件了。

    注意:通常。await()的调用应该在例如以下形式的循环体中:

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

    这里的while循环不能换成if条件语句,由于被别的线程用signalAll方法唤醒的线程,不过条件可能满足了,而不是条件一定满足了。假设不用while循环继续检測的话,就会造成条件不满足的线程继续向下运行,从而产生错误。

    当一个线程拥有某个条件的锁时。它只能够在该条件上调用await(),signalAll()。signal()这三个方法。如今我们讲讲第三个方法。

    signal和signalAll的差别是,signal不会将全部的等待线程唤醒。而是随机选择一个线程唤醒,而signalAll是将全部的等待线程都唤醒。

    最后附上源代码:http://download.csdn.net/detail/xingjiarong/9010675

    在下一篇博客里。我会为大家介绍java的synchronized关键字,希望与大家一起学习一起进步,请大家继续关注我的博客,假设大家支持我的话,就顶我一下吧。

  • 相关阅读:
    php configure –help说明
    HTML5(目前)无法帮你实现的五件事多媒体
    Centos搭建PHP5.3.8+Nginx1.0.9+Mysql5.5.17
    lighttpdmod_secdownload 防盗链
    中文环境下PostgreSQL的使用
    一步一步教你安装Nginx+PHP+Mysql
    20+ 个免费和高级的 Web 视频播放器
    二十个你必须知道的SEO概念
    IO流
    sofaBoot
  • 原文地址:https://www.cnblogs.com/llguanli/p/7379811.html
Copyright © 2011-2022 走看看