zoukankan      html  css  js  c++  java
  • JAVA多线程的问题以及处理(二)【转】

    使用互斥解决多线程问题是一种简单有效的解决办法,但是由于该方法比较简单,所以只能解决一些基本的问题,对于复杂的问题就无法解决了。

             解 决多线程问题的另外一种思路是同步。同步是另外一种解决问题的思路,结合前面卫生间的示例,互斥方式解决多线程的原理是,当一个人进入到卫生间内部时,别 的人只能在外部时刻等待,这样就相当于别的人虽然没有事情做,但是还是要占用别的人的时间,浪费系统的执行资源。而同步解决问题的原理是,如果一个人进入 到卫生间内部时,则别的人可以去睡觉,不占用系统资源,而当这个人从卫生间出来以后,把这个睡觉的人叫醒,则它就可以使用临界资源了。所以使用同步的思路 解决多线程问题更加有效,更加节约系统的资源。

             在常见的多线程问题解决中,同步问题的典型示例是“生产者-消费者”模型,也就是生产者线程只负责生产,消费者线程只负责消费,在消费者发现无内容可消费时则睡觉。下面举一个比较实际的例子——生活费问题。

             生 活费问题是这样的:学生每月都需要生活费,家长一次预存一段时间的生活费,家长和学生使用统一的一个帐号,在学生每次取帐号中一部分钱,直到帐号中没钱时 通知家长存钱,而家长看到帐户还有钱则不存钱,直到帐户没钱时才存钱。在这个例子中,这个帐号被学生和家长两个线程同时访问,则帐号就是临界资源,两个线 程是同时执行的,当每个线程发现不符合要求时则等待,并释放分配给自己的CPU执行时间,也就是不占用系统资源。实现该示例的代码为:

    package syn4;
    
    /**
     * 测试类
     */
    public class TestAccount {
             public static void main(String[] args) {
                       Accout a = new Accout();
                       StudentThread s = new StudentThread(a);
                       GenearchThread g = new GenearchThread(a);
             }
    }
    package syn4;
    /**
     * 模拟学生线程
     */
    public class StudentThread extends Thread {
             Accout a;
             public StudentThread(Accout a){
                       this.a = a;
                       start();
             }
             public void run(){
                       try{
                                while(true){
                                         Thread.sleep(2000);
                                         a.getMoney(); //取钱
                                }
                       }catch(Exception e){}
             }
    }
    package syn4;
    /**
     * 家长线程
     */
    public class GenearchThread extends Thread {
             Accout a;
             public GenearchThread(Accout a){
                       this.a = a;
                       start();
             }
             public void run(){
                       try{
                                while(true){
                                         Thread.sleep(12000);
                                         a.saveMoney(); //存钱
                                }
                       }catch(Exception e){}
             }
    }
    package syn4;
    /**
     * 银行账户
     */
    public class Accout {
             int money = 0;
             /**
              * 取钱
              * 如果账户没钱则等待,否则取出所有钱提醒存钱
              */
             public synchronized void getMoney(){
                       System.out.println("准备取钱!");
                       try{
                                if(money == 0){
                                         wait(); //等待
                                }
                                //取所有钱
                                System.out.println("剩余:" + money);
                                money -= 50;
                                //提醒存钱
                                notify();
                       }catch(Exception e){}                
             }
            
             /**
              * 存钱
              * 如果有钱则等待,否则存入200提醒取钱
              */
             public synchronized void saveMoney(){
                       System.out.println("准备存钱!");
                       try{
                                if(money != 0){
                                         wait(); //等待
                                }
                                //取所有钱
                                money = 200;
                                System.out.println("存入:" + money);
                                //提醒存钱
                                notify();
                       }catch(Exception e){}                
             }
    }

             该程序的一部分执行结果为:

    准备取钱!
    
    准备存钱!
    存入:200
    剩余:200
    准备取钱!
    剩余:150
    准备取钱!
    剩余:100
    准备取钱!
    剩余:50
    准备取钱!
    准备存钱!
    存入:200
    剩余:200
    准备取钱!
    剩余:150
    准备取钱!
    剩余:100
    准备取钱!
    剩余:50
    准备取钱!
    View Code

             在该示例代码中,TestAccount类是测试类,主要实现创建帐户Account类的对象,以及启动学生线程StudentThread和启动家长线程GenearchThread。在StudentThread线程中,执行的功能是每隔2秒中取一次钱,每次取50元。在GenearchThread线程中,执行的功能是每隔12秒存一次钱,每次存200。这样存款和取款之间不仅时间间隔存在差异,而且数量上也会出现交叉。而该示例中,最核心的代码是Account类的实现。

             在Account类中,实现了同步控制功能,在该类中包含一个关键的属性money,该属性的作用是存储帐户金额。在介绍该类的实现前,首先介绍一下两个同步方法——wait和notify方法的使用,这两个方法都是Object类中的方法,也就是说每个类都包含这两个方法,换句话说,就是Java天生就支持同步处理。这两个方法都只能在synchronized修饰的方法或语句块内部采用被调用。其中wait方法的作用是使调用该方法的线程休眠,也就是使该线程退出CPU的等待队列,处于冬眠状态,不执行动作,也不占用CPU排队的时间,notify方法的作用是唤醒一个任意该对象的线程,该线程当前处于休眠状态,至于唤醒的具体是那个则不保证。在Account类中,被StudentThread调用的getMoney方法的功能是判断当前金额是否是0,如果是则使StudentThread线程处于休眠状态,如果金额不是0,则取出50元,同时唤醒使用该帐户对象的其它一个线程,而被GenearchThread线程调用的saveMoney方法的功能是判断当前是否不为0,如果是则使GenearchThread线程处于休眠状态,如果金额是0,则存入200元,同时唤醒使用该帐户对象的其它一个线程。

             如果还是不清楚,那就结合前面的程序执行结果来解释一下程序执行的过程:在程序开始执行时,学生线程和家长线程都启动起来,所以输出“准备取钱”和“准备存钱”,然后学生线程按照该线程run方法的逻辑执行,先延迟2秒,然后调用帐户对象a中的getMoney方法,但是由于初始情况下帐户对象a中的money数值为0,所以学生线程就休眠了。在学生线程执行的同时,家长线程也按照该线程的run方法的逻辑执行,先延迟12秒,然后调用帐户对象a中的saveMoney方法,由于帐户a对象中的money为零,条件不成立,所以执行存入200元,同时唤醒线程,由于使用对象a的线程现在只有学生线程,所以学生线程被唤醒,开始执行逻辑,取出50元,然后唤醒线程,由于当前没有线程处于休眠状态,所以没有线程被唤醒。同时家长线程继续执行,先延迟12秒,这个时候学生线程执行了4次,耗时4X2秒=8秒,就取光了帐户中的钱,接着由于帐户为0则学生线程又休眠了,一直到家长线程延迟12秒结束以后,判断帐户为0,又存入了200元,程序继续执行下去。

             在解决多线程问题是,互斥和同步都是解决问题的思路,如果需要形象的比较这两种方式的区别的话,就看一下下面的示例。一个比较忙的老总,桌子上有2部电话,在一部处于通话状态时,另一部响了,老总拿其这部电话说我在接电话,你等一下,而没有挂电话,这种处理的方式就是互斥。而如果老总拿其另一部电话说,我在接电话,等会我打给你,然后挂了电话,这种处理的方式就是同步。两者相比,互斥明显占用系统资源(浪费电话费,浪费别人的时间),而同步则是一种更加好的解决问题的思路。


    在日常生活中,例如火车售票窗口等经常可以看到“XXX优先”,那么多线程编程中每个线程是否也可以设置优先级呢?

             在多线程编程中,支持为每个线程设置优先级。优先级高的线程在排队执行时会获得更多的CPU执行时间,得到更快的响应。在实际程序中,可以根据逻辑的需要,将需要得到及时处理的线程设置成较高的优先级,而把对时间要求不高的线程设置成比较低的优先级。

             在Thread类中,总计规定了三个优先级,分别为:

    MAX_PRIORITY——最高优先级

    NORM_PRIORITY——普通优先级,也是默认优先级

    MIN_PRIORITY——最低优先级

    在前面创建的线程对象中,由于没有设置线程的优先级,则线程默认的优先级是NORM_PRIORITY,在实际使用时,也可以根据需要使用Thread类中的setPriority方法设置线程的优先级,该方法的声明为:

             public final void setPriority(int newPriority)

    假设t是一个初始化过的线程对象,需要设置t的优先级为最高,则实现的代码为:

             t. setPriority(Thread. MAX_PRIORITY);

    这样,在该线程执行时将获得更多的执行机会,也就是优先执行。如果由于安全等原因,不允许设置线程的优先级,则会抛出SecurityException异常。

    下面使用一个简单的输出数字的线程演示线程优先级的使用,实现的示例代码如下:

     package priority;
    /**
     * 测试线程优先级
     */
    public class TestPriority {
             public static void main(String[] args) {
                       PrintNumberThread p1 = new PrintNumberThread("高优先级");
                       PrintNumberThread p2 = new PrintNumberThread("普通优先级");
                       PrintNumberThread p3 = new PrintNumberThread("低优先级");
                       p1.setPriority(Thread.MAX_PRIORITY);
                       p2.setPriority(Thread.NORM_PRIORITY);
                       p3.setPriority(Thread.MIN_PRIORITY);
                       p1.start();
                       p2.start();
                       p3.start();
             }
    }
    package priority;
    /**
     * 输出数字的线程
     */
    public class PrintNumberThread extends Thread {
             String name;
             public PrintNumberThread(String name){
                       this.name = name;
             }
             public void run(){
                       try{
                                for(int i = 0;i < 10;i++){
                                         System.out.println(name + ":" + i);
                                }
                       }catch(Exception e){}
             }
    }

    程序的一种执行结果为:

    高优先级:0
    高优先级:1
    高优先级:2
    普通优先级:0
    高优先级:3
    普通优先级:1
    高优先级:4
    普通优先级:2
    高优先级:5
    高优先级:6
    高优先级:7
    高优先级:8
    高优先级:9
    普通优先级:3
    普通优先级:4
    普通优先级:5
    普通优先级:6
    普通优先级:7
    普通优先级:8
    普通优先级:9
    低优先级:0
    低优先级:1
    低优先级:2
    低优先级:3
    低优先级:4
    低优先级:5
    低优先级:6
    低优先级:7
    低优先级:8
    低优先级:9
    View Code

     在该示例程序,PrintNumberThread线程实现的功能是输出数字,每次数字输出之间没有设置时间延迟,在测试类TestPriority中创建三个PrintNumberThread类型的线程对象,然后分别设置线程优先级是最高、普通和最低,接着启动线程执行程序。从执行结果可以看出高优先级的线程获得了更多的执行时间,首先执行完成,而低优先级的线程由于优先级较低,所以最后一个执行结束。

             其实,对于线程优先级的管理主要由系统的线程调度实现,较高优先级的线程优先执行,所以可以通过设置线程的优先级影响线程的执行。

  • 相关阅读:
    创建部署规划
    并发容器Map之一:(jdk1.8) ConcurrentHashMap的红黑树实现分析
    CopyOnWrite容器之二:CopyOnWriteArraySet
    jQuery1.5的新特征subclass——jQuery插件机制的救赎
    getElementsByTagName
    facebook是如何管理代码的
    由addClass衍生出来的字符串去重问题
    节点排序
    Sizzle是怎样工作的
    我的模块加载系统 v3
  • 原文地址:https://www.cnblogs.com/tk55/p/6763764.html
Copyright © 2011-2022 走看看