黑马程序员技术交流社区

标题: 一道经典的多线程问题(涉及三个线程) [打印本页]

作者: 胡建伟    时间: 2013-11-10 19:39
标题: 一道经典的多线程问题(涉及三个线程)
本帖最后由 胡建伟 于 2013-11-11 11:36 编辑

需求:要求用三个线程,按顺序打印1,2,3,4,5.... 71,72,73,74, 75. 线程1先打印1,2,3,4,5, 然后是线程2打印6,7,8,9,10, 然后是线程3打印11,12,13,14,15. 接着再由线程1打印16,17,18,19,20....以此类推, 直到线程3打印到75。
求高手们指点下,尽量有注释的代码哈

作者: 起猿    时间: 2013-11-10 21:28

  1. <DIV class=blockcode>
  2. <BLOCKQUOTE>
  3. <BLOCKQUOTE>
  4. 简单的分析一下题目要求。从题目考察的角度看,题目考的是线程同步知识,既然是顺序打印,那么也就是在某一个时刻只有一个线程处于运行状态,其他两个线程必须出于等待状态。一个线程在一轮打印中,打印5个数字后,必须转为等待状态。另一个线程接着打印下5个数字,如此类推,直到75个数字全部打完。如何判断某一个线程应该打印,这里给每一个线程分配一个id号,0,1,2,如果需要打印的数字c / 5 %3 == id,表示当前线程需要打印一轮。每个线程都必须要判断15次,采用这种方法,算法的复杂度是15*3,共45个循环。这是打印程序中的基本逻辑,那怎么实现同步呢?请看下面的四种解法。

  5. 解法一

  6. 既然是同步的打印,很自然的想到synchronized关键字,那么只要把线程的run方法声明为synchronized的,就可以实现线程的同步。这是这个程序的基本解法。具体的代码如下:
  7. Java代码
  8. public class NumberPrinter extends Thread {
  9. static int c = 0;
  10. private int id;

  11. @Override
  12. public synchronized void run() {
  13. while (c < 75) {
  14. if (c / 5 % 3 == id) {
  15. for (int i = 0; i < 5; i++) {
  16. c++;
  17. System.out.format("Thread %d: %d %n", id, c);
  18. }
  19. }
  20. }
  21. }

  22. public NumberPrinter(int id) {
  23. super();
  24. this.id = id;
  25. }

  26. public static void main(String[] args) {
  27. // 启动3个带有id的线程,
  28. for (int i = 0; i < 3; i++) {
  29. new NumberPrinter(i).start();
  30. }
  31. }
  32. }
  33. 解法二

  34. 解法一中,线程的run方法基本全是对变量c进行操作的,因此,使用synchronized关键字声明方法同步,系统的开销是比较大的,因此我们可以选择volatile关键字对c变量进行声明,让变量c具有CompareAndSet的特性,也就是对变量的引用和操作是同步的。但是这个关键字在JDK5才被很好的支持,如果是JDK4,会出现意象不到的结果。具体的实现看下面的代码。
  35. Java代码
  36. class VolatileNumberPrinter extends Thread {
  37. volatile static int c = 0;
  38. private int id;

  39. @Override
  40. public void run() {
  41. while (c < 75) {
  42. if (c / 5 % 3 == id) {
  43. for (int i = 0; i < 5; i++) {
  44. c++;
  45. System.out.format("Thread %d: %d %n", id, c);
  46. }
  47. }
  48. }
  49. }

  50. public VolatileNumberPrinter(int id) {
  51. super();
  52. this.id = id;
  53. }

  54. public static void main(String[] args) {
  55. for (int i = 0; i < 3; i++) {
  56. new VolatileNumberPrinter(i).start();
  57. }
  58. }
  59. }
  60. 解法三

  61. JDK5及以后的版本提供了并发锁的类,最常用的是ReentralLock,在多线程环境下可以保证被锁住的代码在某一个特定的运行时刻只有拿到锁的线程才可以运行该代码。锁的使用比synchronized关键要灵活,但必须保证该锁在需要的时候被释放。采用锁机制实现同步的代码如下。
  62. Java代码
  63. class ReentrantLockNumberPrinter extends Thread {
  64. static int c;
  65. private int id;
  66. private ReentrantLock lock = new ReentrantLock();

  67. public ReentrantLockNumberPrinter(int id) {
  68. super();
  69. this.id = id;
  70. }

  71. @Override
  72. public void run() {
  73. try {
  74. lock.lock(); // 拿到锁的线程在运行,没有拿到的线程在等待
  75. while (c < 75) {
  76. if (c / 5 % 3 == id) {
  77. for (int i = 0; i < 5; i++) {
  78. c++;
  79. System.out.format("Thread %d: %d %n", id, c);
  80. }
  81. }
  82. }
  83. } finally { //确保锁被释放
  84. lock.unlock();
  85. }
  86. }

  87. public static void main(String[] args) {
  88. for (int i = 0; i < 3; i++) {
  89. new ReentrantLockNumberPrinter(i).start();
  90. }
  91. }
  92. }
  93. 解法四

  94. JDK5的并发包除了提供锁机制的类外,还提供了用于原子操作的类,把需要打印的变量放入到一个原子类中去,调用该类的CompareAndSet操作是具有原子性的,那么在多线程的环境下,可以避免不一致的变量引用。

  95. Java代码
  96. class AtomicIntegerNumberPrinter extends Thread {
  97. private int id;
  98. static AtomicInteger c = new AtomicInteger(0); //声明一个原子整数,设置初始值为0

  99. public AtomicIntegerNumberPrinter(int id) {
  100. super();
  101. this.id = id;
  102. }

  103. public void run() {
  104. while (c.get() < 75) {
  105. if (c.get() / 5 % 3 == id) {
  106. for (int i = 0; i < 5; i++) {
  107. c.getAndIncrement(); // 相当于 c++,但是具有原子性
  108. System.out.format("Thread %d: %d %n", id, c.get());
  109. }
  110. }
  111. }
  112. }

  113. public static void main(String args[]) {
  114. for (int i = 0; i < 3; i++) {
  115. new AtomicIntegerNumberPrinter(i).start();
  116. }
  117. }
  118. }
  119. 本例中,AtomaticInteger.getAndIncrement()把原子整数中的变量加1,但是具有原子性,可以保证多线程下不会出现不一致的c变量引用。get方法不具有原子性,不过
  120. Java代码
  121. c.get() / 5 % 3 == id
  122. 这行代码能确保一个值是被期望的线程所打印。

  123. 从上面的四种解法重看,解法一把run方法声明为同步,其实是给该方法加了一把隐形的锁。解法三是采用并发锁的机制,本质上是和解法一是相同的,解法三需要JDK5及其以后的版本,解法一是最稳定的,适应性最广的,任何JDK版本都可以支持。解法二和解法四类似,但是volatile在jdk5后才被很好的支持,也只有jdk5及以后的版本才支持原子类。因此在实际的多线程编程中,首先还是synchronized关键字,wait()和notify()等传统的线程方法。
复制代码

作者: 王雨神    时间: 2013-11-11 08:30
呵呵我也写了一个,跟上面哥们方法一里的一样。不过这个方法没有效率。跟上面的师兄又学了几招,谢谢了。
思路:三个线程,每个线程都执行相同的代码:i++;system.out.println(i);
判断三个线程中哪一个执行这段代码c / 5 %3,然后写三个方法,c / 5 %3可能等于{0,1,2}
然后根据这个判断哪个线程执行。

  1. package com.itheima;

  2. class  AddInt{
  3.         public  int i=1;
  4.         public AddInt(){}
  5. }
  6. class  Test01 implements Runnable{
  7.         private AddInt a;
  8.         public Test01(){
  9.                
  10.         }
  11.         public Test01(AddInt a){
  12.                 this.a=a;
  13.         }
  14.         public  void run(){
  15.                 while(a.i<76)        {
  16.                                                 if(a.i/5%3==0){
  17.                                                         //synchronized(a){
  18.                                                                 for(int j=0;j<5;j++){
  19.                                                                        
  20.                                                                         System.out.println(Thread.currentThread().getName()+"....."+a.i);
  21.                                                                         a.i++;
  22.                                                                 }
  23.                                                         //}
  24.                                                        
  25.                                                 }
  26.                                                
  27.                 }
  28.         }
  29. }
  30. class  Test02 implements Runnable{
  31.         private AddInt a;
  32.         public Test02(){
  33.                
  34.         }
  35.         public Test02(AddInt a){
  36.                 this.a=a;
  37.         }
  38.         public  void run(){
  39.                                                
  40.                
  41.                 while(a.i<76)        {
  42.                                                                 if(a.i/5%3==1){
  43.                                                                         //synchronized(a){
  44.                                                                                 for(int j=0;j<5;j++){
  45.                                                                                        
  46.                                                                                         System.out.println(Thread.currentThread().getName()+"....."+a.i);
  47.                                                                                         a.i++;
  48.                                                                                 }
  49.                                                                         //}
  50.                                                                 }
  51.                 }
  52.         }
  53. }
  54. class  Test03 implements Runnable{
  55.         private AddInt a;
  56.         public Test03(){
  57.                
  58.         }
  59.         public Test03(AddInt a){
  60.                 this.a=a;
  61.         }
  62.         public  void run(){
  63.                 while(a.i<76)        {
  64.                         if(a.i/5%3==2){
  65.                                 //synchronized(a){
  66.                                         for(int j=0;j<5;j++){
  67.                                                
  68.                                                 System.out.println(Thread.currentThread().getName()+"....."+a.i);
  69.                                                 a.i++;
  70.                                         }
  71.                                 //}
  72.                         }
  73.                 }
  74.                

  75.         }
  76. }

  77. class TestThreads{

  78.         /**
  79.          * @param args
  80.          */
  81.         public static void main(String[] args) {
  82.                 AddInt i=new AddInt();
  83.                
  84.                 //创建三个实现了Runnable借口的对象,对一个AddInt对象操作。
  85.                 Test01 t1=new Test01(i);
  86.                 Test02 t2=new Test02(i);
  87.                 Test03 t3=new Test03(i);
  88.                 Thread th1=new Thread(t1);
  89.                 Thread th2=new Thread(t2);
  90.                 Thread th3=new Thread(t3);
  91.                
  92.                 th1.start();
  93.                 th2.start();
  94.                 th3.start();
  95.         }
  96.        
  97.        
  98. }

复制代码

作者: 黄炳期    时间: 2013-11-11 09:04
如果问题已经解决,请及时修改主题为“提问结束”。
修改主题的方法链接
http://bbs.itheima.com/thread-89313-1-1.html




欢迎光临 黑马程序员技术交流社区 (http://bbs.itheima.com/) 黑马程序员IT技术论坛 X3.2