目录:

  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. }

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

问题来了,程序中明明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. }

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

但是注意到,输出是连续一堆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. }

运行结果:

达到了预期。

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

再考虑上面写的代码,其实并不好,同步的目的是为了防止某个线程对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. }

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

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

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

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

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

  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. }

运行结果:

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

  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. }

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

产生的问题:

  • 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. }
  • 假设生产者(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. }

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

原因很简单: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. }

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

之前采用同步代码块的时候 ,多个线程都放在同一个锁下面进行同步,而这个锁只能具有一组监视器(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. }

下面做一个简单的总结:

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. }

八、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. }

可以看出,虽然主线程结束了,但是其他线程还要执行,然后根据标志位,自己停止。如果不设置标记,如果主线程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. }

发现主线程结束了,但是其他线程却没能停下来,很简单:线程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. }

看到线程都结束了,由于采用了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. }

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

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

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

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

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

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

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

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

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

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

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

  1. t1.setPriority(10);

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

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

还有一个方法:

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

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

Java多线程技术学习笔记(二)的更多相关文章

  1. Java多线程技术学习笔记(一)

    目录: 概述 多线程的好处与弊端 JVM中的多线程解析 多线程的创建方式之一:继承Thread类 线程的状态 多线程创建的方式之二:实现Runnable接口 使用方式二创建多线程的好处 多线程示例 线 ...

  2. java之jvm学习笔记二(类装载器的体系结构)

    java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...

  3. Java多线程编程(学习笔记)

    一.说明 周末抽空重新学习了下多线程,为了方便以后查阅,写下学习笔记. 有效利用多线程的关键是理解程序是并发执行而不是串行执行的.例如:程序中有两个子系统需要并发执行,这时候需要利用多线程编程. 通过 ...

  4. 《深入理解Java虚拟机》学习笔记(二)

    垃圾回收的前提是判断对象是否存活,对象不再存活时将会被回收,下面是2种判断的方法. 引用计数法: 主流的Java虚拟机并没有使用引用计数法来管理内存,重要的原因就是循环引用的问题难以解决. 可达性分析 ...

  5. java 多线程——同步 学习笔记

      一.实例的同步方法 public synchronized void add(int value){ this.count += value; } Java 实例方法同步是同步在拥有该方法的对象上 ...

  6. Java多线程基础学习(二)

    9. 线程安全/共享变量——同步 当多个线程用到同一个变量时,在修改值时存在同时修改的可能性,而此时该变量只能被赋值一次.这就会导致出现“线程安全”问题,这个被多个线程共用的变量称之为“共享变量”. ...

  7. 【10-26】java调试技术学习笔记

    调试工具 jdk自带的工具 jmap jconsole VisualVM jmap jmap -histo:live pid 列出该进程的所有活动实例统计信息 jmap -dump:live,file ...

  8. 赶紧收藏!王者级别的Java多线程技术笔记,我java小菜鸡愿奉你为地表最强!

    Java多线程技术概述 介绍多线程之前要介绍线程,介绍线程则离不开进程. 首先 , 进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元: 线程:就 ...

  9. java之jvm学习笔记六-十二(实践写自己的安全管理器)(jar包的代码认证和签名) (实践对jar包的代码签名) (策略文件)(策略和保护域) (访问控制器) (访问控制器的栈校验机制) (jvm基本结构)

    java之jvm学习笔记六(实践写自己的安全管理器) 安全管理器SecurityManager里设计的内容实在是非常的庞大,它的核心方法就是checkPerssiom这个方法里又调用 AccessCo ...

随机推荐

  1. 怎样预防Ddos攻击

    一.为何要DDOS? 随着Internet互联网络带宽的增加和多种DDOS黑客工具的不断发布,DDOS拒绝服务攻击的实施越来越容易,DDOS攻击事件正在成上升趋势.出于商业竞争.打击报复和网络敲诈等多 ...

  2. loadrunner中lr_log_message和lr_output_message 的区别

    LoadRunner中lr_output_message和lr_log_message(1)在vgen中,我们必须写输出函数输出信息,将我们所想要了解的信息用函数输出,主要有这么几个函数输出信息: l ...

  3. c++11之智能指针

    在c++98中,智能指针通过一个模板“auto_ptr”来实现,auto_ptr以对象的方式来管理堆分配的内存,在适当的时间(比如析构),释放所获得的内存.这种内存管理的方式只需要程序员将new操作返 ...

  4. Linux-sed用法(2)

    本文为转载,原地址为:http://www.cnblogs.com/ggjucheng/archive/2013/01/13/2856901.html 简介 sed 是一种在线编辑器,它一次处理一行内 ...

  5. 有趣的库:pipe(类似linux | 管道)库

    pipe并不是Python内置的库,如果你安装了easy_install,直接可以安装它,否则你需要自己下载它:http://pypi.python.org/pypi/pipe 之所以要介绍这个库,是 ...

  6. 【boost】使用装饰者模式改造boost::thread_group

    在项目中使用boost::thread_group的时候遇到几个问题: 1.thread_group不提供删除全部thread列表的方法,一直使用create会是其内部列表不断增加. 2.thread ...

  7. Ubuntu上安装zsh

    先安装zsh,同时移除之前可能的oh-my-zsh的安装 sudo apt-get install zsh if [ -d ~/.oh-my-zsh ]; then rm -r ~/.oh-my-zs ...

  8. 《学习OpenCV》练习题第四章第二题

    #include <highgui.h> #include <cv.h> #pragma comment (lib,"opencv_calib3d231d.lib&q ...

  9. PartialView

    一.客户端直接请求分部视图(如使用AJAX) Return PartialView()  不加载布局页面,不执行_ViewStart.cshtml AJAX  /Home/LoginPart 二.视图 ...

  10. 第三百六十天 how can I 坚持

    看了两集linux视频,有点懵啊,下班还想走去天安门,想啥呢,太远了. 居住证没法办,哎,要入职两年. 考研要是也不能考,这一年也太.. 点不会那么背吧. 好像没啥了,睡觉.