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类型的线程对象,然后分别设置线程优先级是最高、普通和最低,接着启动线程执行程序。从执行结果可以看出高优先级的线程获得了更多的执行时间,首先执行完成,而低优先级的线程由于优先级较低,所以最后一个执行结束。

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

 

posted on 2017-04-25 18:53  Honey_Badger  阅读(370)  评论(0编辑  收藏  举报

导航

github