• Java多线程技术学习笔记(二)


    目录:

    1. 线程间的通信示例
    2. 等待唤醒机制
    3. 等待唤醒机制的优化
    4. 线程间通信经典问题:多生产者多消费者问题
    5. 多生产多消费问题的解决
    6. JDK1.5之后的新加锁方式
    7. 多生产多消费问题的新解决办法
    8. sleep和wait的区别
    9. 停止线程的方式
    10. 守护线程
    11. 线程的其他知识点 

    一、线程间的通信示例 目录

    多个线程在处理同一资源,任务却不同

    假设有一堆货物,有一辆车把这批货物往仓库里面运,另外一辆车把前一辆车运进仓库的货物往外面运。这里货物就是同一资源,但是两辆车的任务却不同,一个是往里运,一个是往外运。

    下面举例子来逐步展示线程间通信:首先建立一个Person类。包含 name 和 sex 属性, 我们建立一个线程输入一个对象(即输入一个name和sex), 另一个线程输出该对象(即输出该对象的name 和 sex).

     1 package thread.demo;
     2 
     3 class Person
     4 {
     5     private String name;
     6     private String sex;
     7     
     8     public String getName() 
     9     {
    10         return name;
    11     }
    12     public void setName(String name) 
    13     {
    14         this.name = name;
    15     }
    16     public String getSex() 
    17     {
    18         return sex;
    19     }
    20     public void setSex(String sex) 
    21     {
    22         this.sex = sex;
    23     }
    24 
    25 }
    26 //输入
    27 class Input implements Runnable
    28 {
    29     private Person r;
    30     
    31     Input(Person r)
    32     {
    33         this.r = r;
    34     }
    35     
    36     public void run()
    37     {
    38         int x = 0;
    39         while(true)//这里加无限循环是为了方便后面观察现象
    40         {
    41             if (x == 0)
    42             {
    43                 r.setName("Mike");
    44                 r.setSex("Male");
    45             }
    46             else
    47             {
    48                 r.setName("Lucy");
    49                 r.setSex("Female");
    50             }
    51             x = (x + 1) % 2; //变换x的值,使得切换输入不同的对象
    52         }
    53     }
    54 }
    55 
    56 class  Output implements Runnable
    57 {
    58     private Person r;
    59     
    60     Output(Person r)
    61     {
    62         this.r = r;
    63     }
    64     
    65     public void run()
    66     {
    67         while(true)//这里加无限循环是为了方便后面观察现象
    68         {
    69             System.out.println(r.getName() + "..." + r.getSex());
    70         }
    71     }
    72 }
    73 public class MultithreadDemo_1 
    74 {
    75 
    76     /**
    77      * @param args
    78      */
    79     public static void main(String[] args) 
    80     {
    81         //建立共享数据Person r,输入和输出都操作对象 r
    82         Person r = new Person();
    83         Input in = new Input(r);
    84         Output out = new Output(r);
    85         
    86         //建立两个线程,分别执行输入任务和输出任务
    87         Thread t1 = new Thread(in);
    88         Thread t2 = new Thread(out);
    89         
    90         //开启线程
    91         t1.start();
    92         t2.start();
    93     }
    94 }
    View Code

    执行结果就是一直不断输出,会发现有如下类似现象:

    问题来了,程序中明明Mike的sex是Male,Lucy是Female,却出现了上面图片中“诡异”的的现象,这当然是线程安全问题!来分析一下:

    在上一篇博文Java多线程技术学习笔记(一)中分析了线程安全问题产生的原因

    • 多个线程操作共享的数据
    • 操作共享数据的线程代码有多条

    回头看我们的代码,共享数据Person r被两个线程操作,满足第一条;操作 r 的代码就是run方法里面的代码,看到第36行开始的run方法确实有很多条,满足第二个条件!所以出现上述诡异输出其实是很正常的现象!具体到上述代码,造成原因:

    • 假设线程0即Input线程,首先抢到cpu执行权,由于x==0, 那么Person对象r的name就是Mike, sex就是Male, 然后x = (x + 1)%2 = 1.
    • 接着有可能Input线程继续占有着cpu执行权, 由于39行的while(true)和x == 1,执行到48行,这时 r 的name = Lucy,问题来了,Input线程还没有来得及更新r的sex,即还没有来得及执行第49行代码,这时线程1,Output线程把cpu执行抢走了。
    • 于是此时r的name就是Lucy,而sex由于没来得及改变还是Male,然后Output线程输出:Lucy...Male! 产生Mike...Female的过程与此类似!

    分析了原因,就来解决问题,那就是前一篇博文笔记里面说的同步:

     1 //输入
     2 class Input implements Runnable
     3 {
     4     private Person r;
     5     
     6     Input(Person r)
     7     {
     8         this.r = r;
     9     }
    10      
    11     public void run()
    12     {
    13         int x = 0;
    14         while(true)//这里加无限循环是为了方便后面观察现象
    15         {
    16             synchronized(r)//同步代码块的锁可以使用任意对象,只要保证多个线程使用的是同一个锁即可
    17             {
    18                 if (x == 0)
    19                 {
    20                     r.setName("Mike");
    21                     r.setSex("Male");
    22                 }
    23                 else
    24                 {
    25                     r.setName("Lucy");
    26                     r.setSex("Female");
    27                 }
    28             }
    29             x = (x + 1) % 2; //变换x的值,使得切换输入不同的对象
    30         }
    31     }
    32 }
    33 
    34 class  Output implements Runnable
    35 {
    36     private Person r;
    37     
    38     Output(Person r)
    39     {
    40         this.r = r;
    41     }
    42     
    43     public void run()
    44     {
    45         while(true)//这里加无限循环是为了方便后面观察现象
    46         {
    47             synchronized(r)
    48             {
    49                 System.out.println(r.getName() + "..." + r.getSex());
    50             }
    51         }
    52     }
    53 }
    View Code

    多次运行之后可以验证,输出正常:

    但是注意到,输出是连续一堆Lucy...Female,然后连续一堆Mike...Male,原因很简单:一旦切换到输出线程,该线程不可能只执行一次,一下输出多次,因为name 和 sex 由于同步的缘故,要么是Lucy...Female,要么是Mike...Male,一输出就是一片相同的Lucy或者Mike. 为了展示多线程间的通信,现在要实现的是,输入线程输入一个name和sex,就立马在输出线程输出,然后再输入一个,再输出一个,如此交替!注意输入和输出是在不同线程里面执行的!所以就需要线程间通信,即输入线程输了一个name和sex,就不在继续输入,而是去通知输出线程输出一下刚才输入的name和sex,输出一次之后,也不再继续输出,而是去通知输入线程继续输入新的内容,输入线程和输出线程如此交替... 这就是所谓的“等待唤醒机制”。

    二、等待唤醒机制 目录

    要达到上面所说的输入和输出线程交替执行,需要设置一个标志位,根据标志位来判断到底是该执行输出还是输出!

    涉及的方法:

    • wait():让线程处于冻结状态,被wait的线程会被存储到线程池,所有等待的线程都在这个池子里面,等待机会去执行。该方法是从java.lang.Object继承过来的:

      翻译过来意思就是:该方法会导致当前线程等待,直到其他线程调用了此线程的notify或者notifyAll方法。 注意到wait方法会抛出异常,所以在面我们的代码中加入了try/catch

    • nofity():唤醒线程池中任意一个线程。
    • notifyAll():唤醒线程池中的所有线程。

    这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法,所以必须要明确到底操作的是哪个锁上的线程。 

    注意到上述操作线程的方法都是放在Object类中,这是因为方法都是同步锁的方法。而锁可以是任意对象,任意的对象都可以调用的方法一定定义在Object类中。

    代码思路:初始化标志位-->输入线程输入-->更改标志位-->唤醒输出线程-->输出线程输出-->更该标志位-->唤醒输入线程-->输入线程输入--> ...

    代码改动如下:

      1 package thread.demo;
      2 /*
      3  * 等待/唤醒机制
      4  */
      5 class Person
      6 {
      7     private String name;
      8     private String sex;
      9     boolean full = false;//标志位,代表着是否已经更新了name和sex
     10     
     11     public String getName() 
     12     {
     13         return name;
     14     }
     15     public void setName(String name) 
     16     {
     17         this.name = name;
     18     }
     19     public String getSex() 
     20     {
     21         return sex;
     22     }
     23     public void setSex(String sex) 
     24     {
     25         this.sex = sex;
     26     }
     27 
     28 }
     29 //输入
     30 class Input implements Runnable
     31 {
     32     private Person r;
     33     
     34     Input(Person r)
     35     {
     36         this.r = r;
     37     }
     38      
     39     public void run()
     40     {
     41         int x = 0;
     42         while(true)//这里加无限循环是为了方便后面观察现象
     43         {
     44             synchronized(r)//同步代码块的锁可以使用任意对象,只要保证多个线程使用的是同一个锁即可
     45             {
     46                 if (r.full)
     47                 {
     48                     // 如果full标志位为真
     49                     // r锁的wait方法让线程冻结,在线程池中等待,就不执行后面的输入name和sex的语句
     50                     try {
     51                         r.wait();//注意调用wait方法要明确锁
     52                     } catch (InterruptedException e) {
     53                         e.printStackTrace();
     54                     }
     55                 }
     56                 //如果标志位为假,就执行下面语句输入name和sex
     57                 if (x == 0)
     58                 {
     59                     r.setName("Mike");
     60                     r.setSex("Male");
     61                 }
     62                 else
     63                 {
     64                     r.setName("Lucy");
     65                     r.setSex("Female");
     66                 }
     67                 //输入了一个对象,即一对name和sex之后,将标志位置为真
     68                 r.full = true;
     69                 //然后通知输出线程(即唤醒输出线程)来输出刚输入的内容
     70                 r.notify();
     71             }
     72             x = (x + 1) % 2; //变换x的值,使得切换输入不同的对象
     73         }
     74     }
     75 }
     76 
     77 class  Output implements Runnable
     78 {
     79     private Person r;
     80     
     81     Output(Person r)
     82     {
     83         this.r = r;
     84     }
     85     
     86     public void run()
     87     {
     88         while(true)//这里加无限循环是为了方便后面观察现象
     89         {
     90             synchronized(r)
     91             {
     92                 //如果输入线程还没有输入内容,输出线程就等待
     93                 if (!r.full)
     94                 {
     95                     try {
     96                         r.wait();
     97                     } catch (InterruptedException e) {
     98                         e.printStackTrace();
     99                     }
    100                 }
    101                 //如果已经输入了内容,就直接输出
    102                 System.out.println(r.getName() + "..." + r.getSex());
    103                 //输出完了之后,将标志位置为false,表明刚才的内容应经输出了
    104                 r.full = false;
    105                 //然后通知输入线程再输入新内容
    106                 r.notify();
    107             }
    108         }
    109     }
    110 }
    111 public class MultithreadDemo_1 
    112 {
    113 
    114     /**
    115      * @param args
    116      */
    117     public static void main(String[] args) 
    118     {
    119         //建立共享数据Person r,输入和输出都操作对象 r
    120         Person r = new Person();
    121         Input in = new Input(r);
    122         Output out = new Output(r);
    123         
    124         //建立两个线程,分别执行输入任务和输出任务
    125         Thread t1 = new Thread(in);
    126         Thread t2 = new Thread(out);
    127         
    128         //开启线程
    129         t1.start();
    130         t2.start();
    131     }
    132 }
    View Code

     

    运行结果:

    达到了预期。

    三、等待唤醒机制的优化 目录

    再考虑上面写的代码,其实并不好,同步的目的是为了防止某个线程对name赋值以后,还没来得及对sex赋值时,其他线程就切了进来!所以需要同步的代码就是赋值的两行:

    59,60行代码与64,65行代码代码功能重复,所以优化代码如下:

      1 package thread.demo;
      2 /*
      3  * 等待/唤醒机制
      4  */
      5 class Person
      6 {
      7     private String name;
      8     private String sex;
      9     private boolean full = false;//标志位,代表着是否已经更新了name和sex
     10     
     11     public String getName() 
     12     {
     13         return name;
     14     }
     15     public void setName(String name) 
     16     {
     17         this.name = name;
     18     }
     19     public String getSex() 
     20     {
     21         return sex;
     22     }
     23     public void setSex(String sex) 
     24     {
     25         this.sex = sex;
     26     }
     27 
     28     public synchronized void set(String name, String sex)
     29     {
     30         if (full)
     31         {
     32             try 
     33             {
     34                 this.wait(); //注意同步函数的锁是this,所以这里调用this的wait方法
     35             } 
     36             catch (InterruptedException e) 
     37             {
     38                 e.printStackTrace();
     39             }
     40         }
     41         
     42         this.name = name;
     43         this.sex = sex;
     44         full = true;
     45         notify();
     46     }
     47     
     48     public synchronized void show()
     49     {
     50         if (!full)
     51         {
     52             try 
     53             {
     54                 this.wait(); //注意同步函数的锁是this,所以这里调用this的wait方法
     55             } 
     56             catch (InterruptedException e) 
     57             {
     58                 e.printStackTrace();
     59             }
     60         }
     61         //如果已经输入了内容,就直接输出
     62         System.out.println(name + "..." + sex);
     63         full = false;
     64         notify(); 
     65     }
     66 }
     67 //输入
     68 class Input implements Runnable
     69 {
     70     private Person r;
     71     
     72     Input(Person r)
     73     {
     74         this.r = r;
     75     }
     76      
     77     public void run()
     78     {
     79         int x = 0;
     80         while(true)//这里加无限循环是为了方便后面观察现象
     81         {
     82             if (x == 0)
     83             {
     84                 r.set("Mike", "Male");
     85             }
     86             else
     87             {
     88                 r.set("Lucy", "Female");    
     89             }
     90             x = (x + 1) % 2; //变换x的值,使得切换输入不同的对象
     91         }
     92     }
     93 }
     94 
     95 class  Output implements Runnable
     96 {
     97     private Person r;
     98     
     99     Output(Person r)
    100     {
    101         this.r = r;
    102     }
    103     
    104     public void run()
    105     {
    106         while(true)//这里加无限循环是为了方便后面观察现象 
    107         {
    108             r.show();
    109         }
    110     }
    111 }
    112 public class MultithreadDemo_1 
    113 {
    114 
    115     /**
    116      * @param args
    117      */
    118     public static void main(String[] args) 
    119     {
    120         //建立共享数据Person r,输入和输出都操作对象 r
    121         Person r = new Person();
    122         Input in = new Input(r);
    123         Output out = new Output(r);
    124         
    125         //建立两个线程,分别执行输入任务和输出任务
    126         Thread t1 = new Thread(in);
    127         Thread t2 = new Thread(out);
    128         
    129         //开启线程
    130         t1.start();
    131         t2.start();
    132     }
    133 }
    View Code

     

    功能与前面代码其实是一样的。

    四、线程间通信经典问题:多生产者多消费者问题 目录

    这个问题很直接:就是一堆生产者生产产品,同时一堆消费者在消费产品!这一堆生产者和消费者对应程序中的多个线程,而产品就对应着这一堆线程共同操作的资源或者叫做共享数据。

    我们想达到的目的是生产一件商品,消费一件,生产消费彼此交替!

    首先来看一个生产者,一个消费者的例子,即生产一个就消费一个:

      1 package thread.demo;
      2 
      3 /*
      4  * 生产者消费者问题
      5  */
      6 class Product
      7 {
      8     private String name;// 产品名称
      9     private int number = 1; // 产品编号
     10     private boolean notEmpty = false; 
     11     public synchronized void produce(String name)
     12     {
     13         //如果有产品,可以停止生产一会
     14         if (notEmpty)
     15         {
     16             try 
     17             {
     18                 this.wait();
     19             } 
     20             catch (InterruptedException e) 
     21             {
     22                 e.printStackTrace();
     23             }
     24         }
     25         // 如果没有产品,就无需等待,直接生产
     26         // 生产的产品名称
     27         this.name = name + number;
     28         // 编号递增
     29         number++;
     30         // 输出生产的产品信息:线程名(对应在某一个生产者)+产品名
     31         System.out.println(Thread.currentThread().getName() + " 生产出: " + this.name);
     32         // 生产完了以后,就有了产品
     33         notEmpty = true;
     34         //通知消费者来消费
     35         notify();
     36     }
     37     
     38     public synchronized void consume()
     39     {
     40         // 如果没有产品,无法消费,等待
     41         if (!notEmpty)
     42         {
     43             try 
     44             {
     45                 this.wait();
     46             } 
     47             catch (InterruptedException e) 
     48             {
     49                 e.printStackTrace();
     50             }
     51         }
     52         //打印产品被消费的信息:线程名(对应着某一个消费者) + 产品名
     53         System.out.println(Thread.currentThread().getName() + "消费了:->  " + this.name);
     54         //消费完了,通知生产者
     55         notEmpty = false;
     56         notify();
     57     }
     58 }
     59 
     60 // 创建生产者线程
     61 class Producer implements Runnable
     62 {
     63     private Product p;
     64     Producer(Product p)
     65     {
     66         this.p = p;
     67     }
     68     
     69     public void run()
     70     {
     71         while (true)
     72         {
     73             p.produce("bread"); // 假如生产面包
     74         }
     75     }
     76     
     77 }
     78 
     79 //创建消费者线程
     80 class Consumer implements Runnable
     81 {
     82     private Product p;
     83     Consumer(Product p)
     84     {
     85         this.p = p;
     86     }
     87     
     88     public void run()
     89     {
     90         while (true)//消费者消费
     91         {
     92             p.consume();
     93         }
     94     }
     95     
     96 }
     97 public class ProducerConsumerDemo {
     98 
     99     public static void main(String[] args) 
    100     {
    101         // 创建共享资源
    102         Product p = new Product();
    103         // 创建两个线程:生产和消费
    104         Producer producer = new Producer(p);
    105         Consumer consumer = new Consumer(p);
    106         Thread t1 = new Thread(producer);
    107         Thread t2 = new Thread(consumer);
    108         t1.start();
    109         t2.start();
    110     }
    111 }
    View Code

     

    运行结果:

     

    这其实就是前面等待唤醒机制的另一种展示!下面在此代码的基础上改成多生产者,多消费者的示例:

     

     1 public class ProducerConsumerDemo {
     2 
     3     public static void main(String[] args) 
     4     {
     5         // 创建共享资源
     6         Product p = new Product();
     7         // 两个生产者,两个消费者
     8         Producer producer = new Producer(p);
     9         Consumer consumer = new Consumer(p);
    10         Thread t0 = new Thread(producer);
    11         Thread t1 = new Thread(producer);
    12         
    13         Thread t2 = new Thread(consumer);
    14         Thread t3 = new Thread(consumer);
    15         
    16         t0.start();
    17         t1.start();
    18         t2.start();
    19         t3.start();
    20     }
    21 }
    View Code

     

    多次运行,会出现下面类似结果:

    产生的问题:

    • bread15865:一个面包被生产两次
    • bread15866:一个面包被吃了两次
    • bread15867:这个面包还没消费掉就去生产下一个面包
    • 还有时生产一大片,却没能消费(多次运行会观察到)

    显然这些问题都是不合理的,问题肯定出在多线程上,下面分分析。为了方便叙述,代码全部整理如下:

      1 package thread.demo;
      2 
      3 /*
      4  * 生产者消费者问题
      5  */
      6 class Product
      7 {
      8     private String name;// 产品名称
      9     private int number = 1; // 产品编号
     10     private boolean notEmpty = false; 
     11     public synchronized void produce(String name)
     12     {
     13         //如果有产品,可以停止生产一会
     14         if (notEmpty)
     15         {
     16             try 
     17             {
     18                 this.wait();
     19             } 
     20             catch (InterruptedException e) 
     21             {
     22                 e.printStackTrace();
     23             }
     24         }
     25         // 如果没有产品,就无需等待,直接生产
     26         // 生产的产品名称
     27         this.name = name + number;
     28         // 编号递增
     29         number++;
     30         // 输出生产的产品信息:线程名(对应在某一个生产者)+产品名
     31         System.out.println(Thread.currentThread().getName() + " 生产出: " + this.name);
     32         // 生产完了以后,就有了产品
     33         notEmpty = true;
     34         //通知消费者来消费
     35         notify();
     36     }
     37     
     38     public synchronized void consume()
     39     {
     40         // 如果没有产品,无法消费,等待
     41         if (!notEmpty)
     42         {
     43             try 
     44             {
     45                 this.wait();
     46             } 
     47             catch (InterruptedException e) 
     48             {
     49                 e.printStackTrace();
     50             }
     51         }
     52         //打印产品被消费的信息:线程名(对应着某一个消费者) + 产品名
     53         System.out.println(Thread.currentThread().getName() + "消费了:->  " + this.name);
     54         //消费完了,通知生产者
     55         notEmpty = false;
     56         notify();
     57     }
     58 }
     59 
     60 // 创建生产者线程
     61 class Producer implements Runnable
     62 {
     63     private Product p;
     64     Producer(Product p)
     65     {
     66         this.p = p;
     67     }
     68     
     69     public void run()
     70     {
     71         while (true)
     72         {
     73             p.produce("bread"); // 假如生产面包
     74         }
     75     }
     76     
     77 }
     78 
     79 //创建消费者线程
     80 class Consumer implements Runnable
     81 {
     82     private Product p;
     83     Consumer(Product p)
     84     {
     85         this.p = p;
     86     }
     87     
     88     public void run()
     89     {
     90         while (true)//消费者消费
     91         {
     92             p.consume();
     93         }
     94     }
     95     
     96 }
     97 public class ProducerConsumerDemo {
     98 
     99     public static void main(String[] args) 
    100     {
    101         // 创建共享资源
    102         Product p = new Product();
    103         // 两个生产者,两个消费者
    104         Producer producer = new Producer(p);
    105         Consumer consumer = new Consumer(p);
    106         Thread t0 = new Thread(producer);
    107         Thread t1 = new Thread(producer);
    108         
    109         Thread t2 = new Thread(consumer);
    110         Thread t3 = new Thread(consumer);
    111         
    112         t0.start();
    113         t1.start();
    114         t2.start();
    115         t3.start();
    116     }
    117 }
    View Code

     

     

    • 假设生产者(t0或者t1线程)得到cpu执行权,开始notEmpty为false,那么就需要去生产,执行到第27行,表示生产出了一个面包。然后number加1变为2 --> 打印信息-->notEmpty变为true-->notify()-->释放线程锁this;
    • 因为nofity是唤醒与锁相关的线程池中的任意线程,所以生产者有可能再次抢到cpu执行权,再次进入第11行的同步函数,但是进入之后发现notEmpty为true,就进入了等待状态。同样如果生产者再次抢到cpu执行权,还是会等待。
    • 继续,如果消费者(t2或者t3线程)抢到执行权,进入第38行的同步函数,判断(!notEmpty)为false,就去53行打印消费信息,代表消费了面包,然后notEmpty变为false,调用notify,释放线程锁,如上面生产者情况类似。倘若消费者再次抢到cpu执行权,就会因为判断标志位而变为等待。
    • 继续,生产者抢到cpu执行权,循环上面的步骤...

     按照上面分析,是不会出现上述运行现象的,于是进一步分析:

    • 假设生产线程t0生产了第一个面包,标志位转换,t0接着又抢到执行权,根据14行标记判断,就转为等待,假设接着生产线程t1又抢到cpu执行权,同样在14行判断标记,t1又转为等待;
    • 然后消费者线程t2此时切入,消费了一次,标志位转换,然后notify去唤醒任意线程,假设此时等待的t0被唤醒,即具有执行资格,但是不一定抢到执行权;
    • 如果t3接着抢到执行权,根据第41行标志位判断,转为等待;
    • 注意此时t1, t2, t3都在等待,被唤醒状态的只有t0;
    • 于是t0很容易得到执行权,从第18行开始继续往下执行,由于没有异常发生,就不会执行catch语句,然后接着27行开始往下执行,生产了第二个面包,标志位转换true,然后notify(),问题来了,由于notify唤醒线程池中的处于等待的t1,t2,t3中的任意一个线程,如果此时唤醒了t2或者t3(消费线程),那就是正常的.
    • 但是如果唤醒了t1, 倘若t0此时继续占有cpu执行权,继续执行,判断标志,t0转为等待, t1同样从18行开始往下执行, 于是第二个面包还没被消费,t1又生产了第三个面包!!  
    • 上面的过程就是t0唤醒t1,然后t1唤醒t0,即两个生产者之间可以互相唤醒,有可能一直生产不消费,也有可能生产线程执行了几次才去唤醒消费线程一次,即生产了多次才消费一次!
    • 同样的道理,两个消费者线程t2和t3也可以互相唤醒,就会导致对同一面包直消费,或者消费多次之后才去生产一次。

    把程序中的四个线程画图分析如下:

    其中双向箭头表示所连接的两线程可以互相唤醒。假如存在A箭头或者B箭头连续执行的情况,就会出现连续生产多个产品而不消费的情况,或者连续消费同一个产品而不生产的情况。很显然只要发生中间四个箭头的情况,就会生产一个,消费一个,从而满足我们的目的。所以解决的原因显而易见:防止A和B情况的发生,即生产者线程不能唤醒生产者线程,只能唤醒消费者线程,而消费者线程也只允许唤醒生产者线程。

     五、多生产多消费问题的解决 目录

    上面分析到,t0唤醒t1后,由于t1从wait处醒过来不判断标记就继续往下执行,就出现了多生产,试想如果t1在被唤醒之后判断一下标记,t1会再次等待,即使t0再次过来也再次判断标记,也会一直等待,而不会去连续多次生产了,所以把14行和41行的 if 改为while,这样,每一个线程被唤醒之后就必须重新判断标记,改动之后运行结果如下:

    现象就是运行若干次程序停止了,即就是在Java多线程技术学习笔记(一)提到的死锁现象,分析原因如下:

    • 生产一次后,t0, t1等待,然后通知消费者
    • 消费一次,t2,t3等待,notEmpty就变为true,
    • 假如唤醒了t0,有可能出现:t0判断标记true, t0等待,唤醒线程t1,t1判断标记true,也等待
    • 由于t1等待,无法执行到下面的notify方法,线程都无法被唤醒,所以四个线程都等待,程序就一直等

    虽然线程每次都重新判断了标记,但是会出现上面死锁的现象,考虑到上面说到notifyAll方法还没有出场过,试着把notify改为notifyAll:

      1 package thread.demo;
      2 
      3 /*
      4  * 生产者消费者问题
      5  */
      6 class Product
      7 {
      8     private String name;// 产品名称
      9     private int number = 1; // 产品编号
     10     private boolean notEmpty = false; 
     11     public synchronized void produce(String name)
     12     {
     13         //如果有产品,可以停止生产一会
     14         while (notEmpty)
     15         {
     16             try 
     17             {
     18                 this.wait();
     19             } 
     20             catch (InterruptedException e) 
     21             {
     22                 e.printStackTrace();
     23             }
     24         }
     25         // 如果没有产品,就无需等待,直接生产
     26         // 生产的产品名称 
     27         this.name = name + number;
     28         // 编号递增
     29         number++;
     30         // 输出生产的产品信息:线程名(对应在某一个生产者)+产品名
     31         System.out.println(Thread.currentThread().getName() + " 生产出: " + this.name);
     32         // 生产完了以后,就有了产品
     33         notEmpty = true;
     34         //通知其他线程
     35         //notify();
     36         notifyAll();
     37         
     38     }
     39     
     40     public synchronized void consume()
     41     {
     42         // 如果没有产品,无法消费,等待
     43         while (!notEmpty)
     44         {
     45             try 
     46             {
     47                 this.wait();
     48             } 
     49             catch (InterruptedException e) 
     50             {
     51                 e.printStackTrace();
     52             }
     53         }
     54         //打印产品被消费的信息:线程名(对应着某一个消费者) + 产品名
     55         System.out.println(Thread.currentThread().getName() + "消费了:->  " + this.name);
     56         //消费完了,通知其他线程
     57         notEmpty = false;
     58         //notify();
     59         notifyAll();
     60     }
     61 }
     62 
     63 // 创建生产者线程
     64 class Producer implements Runnable
     65 {
     66     private Product p;
     67     Producer(Product p)
     68     {
     69         this.p = p;
     70     }
     71     
     72     public void run()
     73     {
     74         while (true)
     75         {
     76             p.produce("bread"); // 假如生产面包
     77         }
     78     }
     79     
     80 }
     81 
     82 //创建消费者线程
     83 class Consumer implements Runnable
     84 {
     85     private Product p;
     86     Consumer(Product p)
     87     {
     88         this.p = p;
     89     }
     90     
     91     public void run()
     92     {
     93         while (true)//消费者消费
     94         {
     95             p.consume();
     96         }
     97     }
     98     
     99 }
    100 public class ProducerConsumerDemo {
    101 
    102     public static void main(String[] args) 
    103     {
    104         // 创建共享资源
    105         Product p = new Product();
    106         // 两个生产者,两个消费者
    107         Producer producer = new Producer(p);
    108         Consumer consumer = new Consumer(p);
    109         Thread t0 = new Thread(producer);
    110         Thread t1 = new Thread(producer);
    111         
    112         Thread t2 = new Thread(consumer);
    113         Thread t3 = new Thread(consumer);
    114         
    115         t0.start();
    116         t1.start();
    117         t2.start();
    118         t3.start();
    119     }
    120 }
    View Code

     

    多次运行会发现,结果正是我们最初想要的:生产一个就消费一个!

    原因很简单:notifyAll会唤醒线程池中所有的线程,假如t0生产了一次,就会唤醒t1,t2,t3,如果t1抢到cpu执行权就会判断标记等待,然后醒着的消费线程抢到执行权, 就去消费一次,然后唤醒所有等待的线程,同样,因为消费了一次,只要消费线程抢到cpu执行权就会根据标记去等待,生产者线程抢到cpu执行权就会判断标记,然后去生产,如此循环!至此,问题得到解决!

    六、JDK1.5之后的新加锁方式 目录

    在API文档中有一个Lock接口:

     

     

    翻译:Lock 实现提供了比使用synchronized方法和语句可获得的更广泛的锁定操作。

    方法如下:

     lock方法获取锁,unlock方法释放锁。

    以前使用同步代码块和一个对象相结合的方式,实现线程同步,有了Lock接口以后,可以通过一个锁对象完成线程的同步。

     使用Lock接口的一个已知实现类ReentrantLock来改写上面的多生产多消费程序,首先看看ReentrantLock的API文档里写的怎么用这个类:

    而Lock接口的描述里面还提到:Lock 可以支持多个相关的 Condition 对象,Condition的API:

    翻译:Condition将Object锁的监视器方法:wait,notify和notifyAll分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合,为每个对象提供多个等待set.其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用。 

    大致意思就是把这些锁的方法wait,notify和notifyAll封装在Condition中,而锁Lock和Condition是什么关系呢?在上面Lock方法的截图中:

     即newCondition方法返回绑定到此Lock实例的新 Condition实例,所以Lock和Condition就是通过这个方法绑定一起,然后就能通过Condition实例调用与该锁想关的wait,notify和notifyAll方法。

    但是注意wait,notify和notifyAll方法在Condition中的名称有所改变,但是功能是一样的:

    好了,根据上面的知识,得出修改的代码:

      1 package thread.demo;
      2 
      3 import java.util.concurrent.locks.Condition;
      4 import java.util.concurrent.locks.Lock;
      5 import java.util.concurrent.locks.ReentrantLock;
      6 
      7 /*
      8  * 生产者消费者问题
      9  */
     10 class NewProduct
     11 {
     12     private String name;// 产品名称
     13     private int number = 1; // 产品编号
     14     private boolean notEmpty = false; 
     15     
     16     // 创建一个锁对象
     17     Lock lock = new ReentrantLock();
     18     
     19     // 通过已有的锁获取该锁上的监视器对象
     20     Condition c = lock.newCondition();
     21     public void produce(String name)
     22     {
     23         lock.lock(); 
     24         try
     25         {
     26             //如果有产品,可以停止生产一会
     27             while (notEmpty)
     28             {
     29                 /*
     30                 try 
     31                 {
     32                     this.wait();
     33                 } 
     34                 catch (InterruptedException e) 
     35                 {
     36                     e.printStackTrace();
     37                 }
     38                 */
     39 
     40                 try {
     41                     c.await();
     42                 } catch (InterruptedException e) {
     43                     e.printStackTrace();
     44                 }
     45             }
     46             // 如果没有产品,就无需等待,直接生产
     47             // 生产的产品名称 
     48             this.name = name + number;
     49             // 编号递增
     50             number++;
     51             // 输出生产的产品信息:线程名(对应在某一个生产者)+产品名
     52             System.out.println(Thread.currentThread().getName() + " 生产出: " + this.name);
     53             // 生产完了以后,就有了产品
     54             notEmpty = true;
     55             //通知其他线程
     56             //notify();
     57             //notifyAll();
     58             c.signalAll();
     59         }
     60         finally
     61         {
     62             lock.unlock();
     63         }
     64     }
     65     
     66     public void consume()
     67     {
     68         lock.lock();
     69         try
     70         {
     71             // 如果没有产品,无法消费,等待
     72             while (!notEmpty)
     73             {
     74                 /*
     75                 try 
     76                 {
     77                     this.wait();
     78                 } 
     79                 catch (InterruptedException e)  
     80                 {
     81                     e.printStackTrace();
     82                 }
     83                 */
     84                 try {
     85                     c.await();
     86                 } catch (InterruptedException e) {
     87                     e.printStackTrace();
     88                 }
     89             }
     90             //打印产品被消费的信息:线程名(对应着某一个消费者) + 产品名
     91             System.out.println(Thread.currentThread().getName() + "消费了:->  " + this.name);
     92             //消费完了,通知其他线程
     93             notEmpty = false;
     94             //notify();
     95             //notifyAll();
     96             c.signalAll();
     97         }
     98         finally
     99         {
    100             lock.unlock();
    101         }
    102     }
    103 }
    104 
    105 // 创建生产者线程
    106 class NewProducer implements Runnable
    107 {
    108     private NewProduct p;
    109     NewProducer(NewProduct p2)
    110     {
    111         this.p = p2;
    112     }
    113     
    114     public void run()
    115     {
    116         while (true)
    117         {
    118             p.produce("bread"); // 假如生产面包
    119         }
    120     }
    121     
    122 }
    123 
    124 //创建消费者线程
    125 class NewConsumer implements Runnable
    126 {
    127     private NewProduct p;
    128     NewConsumer(NewProduct p)
    129     {
    130         this.p = p;
    131     }
    132     
    133     public void run()
    134     {
    135         while (true)//消费者消费
    136         {
    137             p.consume();
    138         }
    139     }
    140     
    141 }
    142 public class LockDemo {
    143 
    144     public static void main(String[] args) 
    145     {
    146         // 创建共享资源
    147         NewProduct p = new NewProduct();
    148         // 两个生产者,两个消费者
    149         NewProducer NewProducer = new NewProducer(p);
    150         NewConsumer NewConsumer = new NewConsumer(p);
    151         Thread t0 = new Thread(NewProducer);
    152         Thread t1 = new Thread(NewProducer);
    153         
    154         Thread t2 = new Thread(NewConsumer);
    155         Thread t3 = new Thread(NewConsumer);
    156         
    157         t0.start();
    158         t1.start();
    159         t2.start();
    160         t3.start();
    161     }
    162 }
    View Code

     

    七、多生产多消费问题的新解决办法 目录

    之前采用同步代码块的时候 ,多个线程都放在同一个锁下面进行同步,而这个锁只能具有一组监视器(wait,notify,notifyAll),同时监视着生产线程和消费线程,比如说这个监视器的wait即能使生产线程处于等待,也可以使消费线程进入等待。同理: 唤醒方法notify和notifyAll对两种线程都起作用。目前线程别分为两类:一组线程(t0,t1)负责生产,一组线程(t2, t3)负责消费。

    现在的思路: 用Lock接口,把两组(共计4个)线程放在同一个锁下同步,但是绑定两个监视器分别监视生产线程和消费线程。关键代码有注释:

      1 package thread.demo;
      2 
      3 import java.util.concurrent.locks.Condition;
      4 import java.util.concurrent.locks.Lock;
      5 import java.util.concurrent.locks.ReentrantLock;
      6 
      7 /*
      8  * 生产者消费者问题
      9  */
     10 class NewProduct
     11 {
     12     private String name;// 产品名称
     13     private int number = 1; // 产品编号
     14     private boolean notEmpty = false; 
     15     
     16     // 创建一个锁对象
     17     Lock lock = new ReentrantLock();
     18     
     19     // 通过已有的锁获取该锁上的监视器对象
     20     //Condition c = lock.newCondition();
     21     //通过已有的锁获得两组监视器,一组监视生产者,一组监视消费者
     22     Condition producerCon = lock.newCondition();
     23     Condition consumerCon = lock.newCondition();
     24     public void produce(String name)
     25     {
     26         lock.lock(); 
     27         try
     28         {
     29             while (notEmpty)
     30             { 
     31                 try {
     32                     producerCon.await();//调用生产者监视,只对生产线程有效
     33                 } catch (InterruptedException e) {
     34                     e.printStackTrace();
     35                 }
     36             }
     37             // 如果没有产品,就无需等待,直接生产
     38             // 生产的产品名称 
     39             this.name = name + number;
     40             // 编号递增
     41             number++;
     42             // 输出生产的产品信息:线程名(对应在某一个生产者)+产品名
     43             System.out.println(Thread.currentThread().getName() + " 生产出: " + this.name);
     44             // 生产完了以后,就有了产品
     45             notEmpty = true;
     46             //通知其他线程
     47             consumerCon.signal();//调用消费者监视器,只能唤醒消费者 
     48         }
     49         finally
     50         {
     51             lock.unlock();
     52         }
     53     }
     54     
     55     public void consume()
     56     {
     57         lock.lock();
     58         try
     59         {
     60             // 如果没有产品,无法消费,等待
     61             while (!notEmpty)
     62             {
     63                 try {
     64                     consumerCon.await();//调用消费者的监视器,使消费线程等待
     65                 } catch (InterruptedException e) {
     66                     e.printStackTrace();
     67                 }
     68             }
     69             //打印产品被消费的信息:线程名(对应着某一个消费者) + 产品名
     70             System.out.println(Thread.currentThread().getName() + "消费了:->  " + this.name);
     71             //消费完了,通知其他线程
     72             notEmpty = false;
     73             //通知生产者
     74             producerCon.signal();
     75         }
     76         finally
     77         {
     78             lock.unlock();
     79         }
     80     }
     81 }
     82 
     83 // 创建生产者线程
     84 class NewProducer implements Runnable
     85 {
     86     private NewProduct p;
     87     NewProducer(NewProduct p2)
     88     {
     89         this.p = p2;
     90     }
     91     
     92     public void run()
     93     {
     94         while (true)
     95         {
     96             p.produce("bread"); // 假如生产面包
     97         }
     98     }
     99     
    100 }
    101 
    102 //创建消费者线程
    103 class NewConsumer implements Runnable
    104 {
    105     private NewProduct p;
    106     NewConsumer(NewProduct p)
    107     {
    108         this.p = p;
    109     }
    110     
    111     public void run()
    112     {
    113         while (true)//消费者消费
    114         {
    115             p.consume();
    116         }
    117     }
    118     
    119 }
    120 public class LockDemo {
    121 
    122     public static void main(String[] args) 
    123     {
    124         // 创建共享资源
    125         NewProduct p = new NewProduct();
    126         // 两个生产者,两个消费者
    127         NewProducer NewProducer = new NewProducer(p);
    128         NewConsumer NewConsumer = new NewConsumer(p);
    129         Thread t0 = new Thread(NewProducer);
    130         Thread t1 = new Thread(NewProducer);
    131         
    132         Thread t2 = new Thread(NewConsumer);
    133         Thread t3 = new Thread(NewConsumer);
    134         
    135         t0.start();
    136         t1.start();
    137         t2.start();
    138         t3.start();
    139     }
    140 }
    View Code

     

    下面做一个简单的总结

    Lock接口

    • 不用自己实现,查看API文档可以发现,库里面已经有几个已经实现了该接口的类,拿来用即可;
    • 它的替代了同步代码块或者同步函数;
    • 将同步的隐式操作变为显示操作;
    • 更为灵活,可以一个锁上加上多组监视器;
    • lock():获取锁
    • unlock:释放锁, 通常需要定义在finally代码块中(因为有时某些线程获取锁以后,程序在执行到unlock之前可能出现问题,但是别的线程还要执行,需要获取锁,所以必须保证锁能够得到释放)

    Condition接口:

    • 替代了Object中的wait,notify和notifyAll方法,将这些监视器方法单独进行了封装,变成了Condition类型的监视器对象。
    • 可以和任意的锁组。
    • await();
    • signal();
    • signalAll();

    下面看一看Java  Condition API文档中给出的范例,本人作出了简单的注释:

     1 package thread.demo;
     2 
     3 import java.util.concurrent.locks.Condition;
     4 import java.util.concurrent.locks.Lock;
     5 import java.util.concurrent.locks.ReentrantLock;
     6 
     7 class BoundedBuffer {
     8         // 创建ReentrantLock锁对象lock
     9        final Lock lock = new ReentrantLock();
    10         // lock锁绑定一个监视器监视数组是否存满
    11        final Condition notFull  = lock.newCondition(); 
    12         // lock锁绑定一个监视器监视数组是否为空
    13        final Condition notEmpty = lock.newCondition(); 
    14         // 建立一个可以存储100个任何对象的数组
    15        final Object[] items = new Object[100];
    16        int putptr, takeptr, count;
    17 
    18        // 向数组中填充对象 x,以供存储线程调用
    19        public void put(Object x) throws InterruptedException {
    20          lock.lock(); // 获取锁
    21          try {
    22             // 当数组存满,调用存满监视器的await,使存储线程等待
    23            while (count == items.length)
    24              notFull.await(); 
    25             // 当数组没有存满,就存入一个对象
    26            items[putptr] = x;
    27            if (++putptr == items.length) putptr = 0;
    28            ++count;
    29                // 存储了对象之后,就去唤醒下面的取出元素的线程
    30                // 大白话就是:俺里面不是空的,你可以来取元素了
    31            notEmpty.signal();
    32          } finally {
    33            lock.unlock();
    34          }
    35        }
    36        
    37        // 取出元素的函数,以供取元素线程调用
    38        public Object take() throws InterruptedException {
    39           // 获取锁
    40          lock.lock();
    41          try {
    42             // 当元素个数为0 时,通知取元素线程等待
    43             // 大白话:里面是空的了,你等会再来取!
    44            while (count == 0)
    45              notEmpty.await();
    46                // 当元素个数不为0,取出元素
    47            Object x = items[takeptr];
    48            if (++takeptr == items.length) takeptr = 0;
    49            --count;
    50                // 取出一个元素之后,就通知存储线程
    51             // 大白话:里面没有满,你可以来存元素了! 
    52            notFull.signal();
    53            return x;
    54          } finally {
    55            lock.unlock();
    56          }
    57        }
    58      }
    59      
    View Code

     

    八、sleep和wait的区别 目录

    • wait可以指定时间,也可以不指定时间,sleep必须指定时间
    • 在同步时,对于cpu执行权和锁的处理不同,
      • wait:释放执行权,释放锁
      • sleep:释放执行权,不释放锁

    九、停止线程的方式 目录

    • stop
    • run方法结束

    在任务中通常都有循环结构,只要控制住循环就可以结束任务。

    控制循环通常就用定义标记来完成。

     1 package thread.demo;
     2 
     3 class StopThread implements Runnable
     4 {
     5     private boolean flag = true;
     6     public void run ()
     7     {
     8         while (flag)
     9         {
    10             System.out.println(Thread.currentThread().getName() + " run...");
    11         }
    12     }
    13     
    14     public void setFlag()
    15     {
    16         flag = false;
    17     }
    18 }
    19 public class StopThreadDemo 
    20 {
    21     /**
    22      * @param args
    23      */
    24     public static void main(String[] args) 
    25     {
    26         StopThread st = new StopThread();
    27         Thread t0 = new Thread(st);
    28         Thread t1 = new Thread(st);
    29         t0.start();
    30         t1.start();
    31         
    32         for (int x = 0; x < 50; x++)
    33         {
    34             if (x == 49)
    35             {
    36                 st.setFlag();
    37                 break;
    38             }
    39             System.out.println(Thread.currentThread().getName() + "..." + x);
    40         }
    41         System.out.println("over");
    42     }
    43 
    44 }
    View Code

    可以看出,虽然主线程结束了,但是其他线程还要执行,然后根据标志位,自己停止。如果不设置标记,如果主线程over,其他线程就无法停止了。如果我们把第34行的改为x == 50,即这个条件就无法满足,标记为无法改变,虽然主线程结束,但是自定义的线程会继续执行。

    假如把上面的程序改为如下:

     1 package thread.demo;
     2 
     3 class StopThread implements Runnable
     4 {
     5     private boolean flag = true;
     6     public synchronized void run ()
     7     {
     8         while (flag)
     9         {
    10             try {
    11                 wait();
    12             } catch (InterruptedException e) {
    13                 System.out.println(Thread.currentThread().getName() + "..." + e);
    14             }
    15              
    16             System.out.println(Thread.currentThread().getName() + " run...");
    17         }
    18     }
    19     
    20     public void setFlag()
    21     {
    22         flag = false;
    23     }
    24 }
    25 public class StopThreadDemo 
    26 {
    27     /**
    28      * @param args
    29      */
    30     public static void main(String[] args) 
    31     {
    32         StopThread st = new StopThread();
    33         Thread t0 = new Thread(st);
    34         Thread t1 = new Thread(st);
    35         t0.start();
    36         t1.start();
    37         
    38         for (int x = 0; x < 50; x++)
    39         {
    40             if (x == 49)
    41             {
    42                 st.setFlag();
    43                 break;
    44             }
    45             System.out.println(Thread.currentThread().getName() + "..." + x);
    46         }
    47         System.out.println("over");
    48     }
    49 
    50 }
    View Code

     发现主线程结束了,但是其他线程却没能停下来,很简单:线程0和线程1进入run之后,都在第11行被置为等待状态,而又没有其他线程来唤醒它们,于是一直等待,即使主线程结束,还是等待。

    下面采用一种方法:

    interrupt方法并不是字面上理解的中断线程(岂不是和stop一样?),注意红框中:如果线程在调用Object类的wait(), wait(long)或者wait(long, int)方法,或者该类的join(), join(long), join(long, int)或者sleep(long), sleep(long, int)方法过程中受阻,则这种受阻的状态会被强制清除,然后收到一个InterruptedException. 

    意思就是interrupt()方法,会把线程从wait或者sleep状态中强制恢复到运行状态中来,让线程具备cpu的执行资格,由于这个“强制”通常是不正常唤醒(正常唤醒notify/notifyAll),所以抛出异常InterruptedException,记得要处理!改动上面程序如下:

     1 package thread.demo;
     2 
     3 class StopThread implements Runnable
     4 {
     5     private boolean flag = true;
     6     public synchronized void run ()
     7     {
     8         while (flag)
     9         {
    10             try {
    11                 wait();
    12             } catch (InterruptedException e) {
    13                 // 打印异常信息
    14                 System.out.println(Thread.currentThread().getName() + "..." + e);
    15                 flag = false; // 处理异常 
    16             }
    17              
    18             System.out.println(Thread.currentThread().getName() + " run...");
    19         }
    20     }
    21     
    22     public void setFlag()
    23     {
    24         flag = false;
    25     }
    26 }
    27 public class StopThreadDemo 
    28 {
    29     /**
    30      * @param args
    31      */
    32     public static void main(String[] args)  
    33     {
    34         StopThread st = new StopThread();
    35         Thread t0 = new Thread(st);
    36         Thread t1 = new Thread(st);
    37         t0.start();
    38         t1.start();
    39         
    40         for (int x = 0; x < 50; x++)
    41         {
    42             if (x == 49)
    43             {
    44                 //st.setFlag();
    45                 t0.interrupt();
    46                 t1.interrupt();
    47                 break;
    48             }
    49             System.out.println(Thread.currentThread().getName() + "..." + x);
    50         }
    51         System.out.println("over");
    52     }
    53 
    54 }
    View Code

     

    看到线程都结束了,由于采用了interrupt方法,抛出了图示的异常!

    十、守护线程 目录

    首先还是看API文档关于守护线程的描述,Thread中的一个方法:

    该方法将该线程标记为守护线程。当正在运行的线程都是守护线程的时候,Java 虚拟机退出

    该方法必须在启动线程前调用。

    例如把上面代码的第47行的t1线程的中断注释掉,但是在在第38行将t1标记为守护线程(可以理解为后台线程):

     1 package thread.demo;
     2 
     3 class StopThread implements Runnable
     4 {
     5     private boolean flag = true;
     6     public synchronized void run ()
     7     {
     8         while (flag)
     9         {
    10             try {
    11                 wait();
    12             } catch (InterruptedException e) {
    13                 // 打印异常信息
    14                 System.out.println(Thread.currentThread().getName() + "..." + e);
    15                 flag = false; // 处理异常 
    16             }
    17              
    18             System.out.println(Thread.currentThread().getName() + " run...");
    19         }
    20     }
    21     
    22     public void setFlag()
    23     {
    24         flag = false;
    25     }
    26 }
    27 public class StopThreadDemo 
    28 {
    29     /**
    30      * @param args
    31      */
    32     public static void main(String[] args)  
    33     {
    34         StopThread st = new StopThread();
    35         Thread t0 = new Thread(st);
    36         Thread t1 = new Thread(st);
    37         t0.start();
    38         t1.setDaemon(true); 
    39         t1.start();
    40         
    41         for (int x = 0; x < 50; x++)
    42         {
    43             if (x == 49)
    44             {
    45                 //st.setFlag();
    46                 t0.interrupt();
    47             //    t1.interrupt();
    48                 break;
    49             }
    50             System.out.println(Thread.currentThread().getName() + "..." + x);
    51         }
    52         System.out.println("over");
    53     }
    54 
    55 }
    View Code

     

    由于线程t1没有能清除wait状态会一直等待,但是由于它是守护线程,Java虚拟机会退出,程序也会停止,但是t1线程就不会再抛出异常:

    注意:守护线程运行时候正常线程一样,抢夺cpu执行权,就是在结束的时候,正常线程需要手动去结束:即stop,或者run方法结束等,但是守护线程是随着虚拟机退出而结束的。就好比我们的操作系统有很多后台进程是不允许我们去操作,在系统运行期间会一直存在并抢夺cpu执行权,但是他是随着系统关闭而停止的。

    十一、线程的其他知识点  目录

     1 package thread.demo;
     2 
     3 
     4 class Demo implements Runnable
     5 {
     6     public void run ()
     7     {
     8         for (int i = 0; i < 50; i++)
     9         {
    10             System.out.println(Thread.currentThread().getName() + "..." + i);
    11         }
    12     }
    13 }
    14 public class JoinDemo 
    15 {
    16 
    17     /**
    18      * @param args
    19      * @throws InterruptedException 
    20      */
    21     public static void main(String[] args) throws InterruptedException 
    22     {
    23         Demo d = new Demo();
    24         Thread t0 = new Thread(d);
    25         Thread t1 = new Thread(d);
    26         
    27         t0.start();
    28         t0.join(); // t0线程申请加入进来运行,此时主线程会释放执行权和执行资格。
    29         t1.start();
    30         //t0.join(); // t0线程申请加入进来运行,此时主线程会释放执行权和执行资格。
    31         
    32         for (int i = 0; i < 50; i++)
    33         {
    34             System.out.println(Thread.currentThread().getName() + "..." + i);
    35         }
    36     }
    37 
    38 }
    View Code

     

    多次运行会发现,都是先执行线程0,这是因为线程0调用join方法之后,主线程会释放执行权和执行资格,一直等到线程0执行完,主线程才重新获取执行资格,如果是t1调用join方法,同理,主线程也会一直等待t1执行完才重新获取执行资格!

    所以join方法常用来临时加入一个线程。

    线程Thread有setPriority方法是用来设定线程的优先级,优先级越高,被随机执行的几率就越大!可以通过线程的toString方法看到线程权限:

     1 package thread.demo;
     2 
     3 
     4 class Demo implements Runnable
     5 {
     6     public void run ()
     7     {
     8         for (int i = 0; i < 50; i++)
     9         {
    10             System.out.println(Thread.currentThread().toString() + "..." + i);
    11         }
    12     }
    13 }
    14 public class JoinDemo 
    15 {
    16 
    17     /**
    18      * @param args
    19      * @throws InterruptedException 
    20      */
    21     public static void main(String[] args) throws InterruptedException 
    22     {
    23         Demo d = new Demo();
    24         Thread t0 = new Thread(d);
    25         Thread t1 = new Thread(d);
    26         
    27         t0.start();
    28         t0.join(); // t0线程申请加入进来运行,此时主线程会释放执行权和执行资格。
    29         t1.start();
    30         //t0.join(); // t0线程申请加入进来运行,此时主线程会释放执行权和执行资格。
    31         
    32         for (int i = 0; i < 50; i++)
    33         {
    34             System.out.println(Thread.currentThread().getName() + "..." + i);
    35         }
    36     }
    37 
    38 }
    View Code

     

    关于线程的优先级有三个字段:

    所以setPriority(MAX_PRIORITY)和setPriority(10)的效果是一样的.

    例如在上述代码中线程1启动后加入:

    t1.setPriority(10);

     

    还可以通过线程的构造函数将线程命名和将线程分组:

     

     

     

    分组作用:加入有10个线程都处于等待状态,先要调用interrupt方法将这10个线程全部强制唤醒就需要调用10次,显然麻烦,如果将这10个线程封装在一个组里面,对着一个组调用interrupt就能够将组里面的线程全部进行中断状态清除(即唤醒)

    还有一个方法:

    暂停当前线程,释放执行权,避免某一个线程一直占有cpu执行权,用的不多.

    参考:传智播客Java SE教程,李刚《疯狂Java讲义》

  • 相关阅读:
    动态规划记录 [动态更新]
    C++ STL map使用的注意事项记录
    20192425 2021202220192425 《网络与系统攻防技术》实验六实验报告
    20192425 202120222 《网络与系统攻防技术》实验五实验报告
    判断数据的增减
    还有多久退休
    按地名找所属市
    多表汇总
    优秀的编码实践
    client go 架构
  • 原文地址:https://www.cnblogs.com/90zeng/p/java_multithread_2.html
Copyright © 2020-2023  润新知