多线程 简洁版

Posted maopneo

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程 简洁版相关的知识,希望对你有一定的参考价值。

多线程技术分享

背景:

以前单CPU,单任务的条件下,一段时间只能执行单一的程序。之后发展到多任务阶段。多个任务共享一个CPU,本质上是有操作系统来完成CPU对多个用户的切换。保证每个任务都有一定的时间片来完成任务。

1.      概念和原理

 

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。

 

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

2.      线程的两种实现方法

1、扩展java.lang.Thread类。

 

此类中有个run()方法,应该注意其用法:

public void run()

如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;否则,该方法不执行任何操作并返回。

Thread 的子类应该重写该方法。

2、实现java.lang.Runnable接口。

 

void run()

使用实现接口 Runnable 的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的 run 方法。

方法 run 的常规协定是,它可能执行任何所需的操作。

 

两种方式的区别:既然都能实现线程的功能,怎样区别使用呢。java规定只能单继承,如果自定义类需要继承其他类,只能选择实现Runnable接口。

3. 线程的理解

线程继承Thread 或者实现Runnable接口。既然是线程,就必然有写的run方法,其次,多线程是多个线程处理公共的资源,所以,每个线程都共享公用资源,所以每个线程里面都有同一个资源对象。通常,公共资源有线程的构造方法传递的。如下,共享资源pendingIfData。

  1. /** 
  2.  * 接口调用线程 
  3.  *  
  4.  * @author maoping 
  5.  * 
  6.  */  
  7. public class ThreadIfExcuse extends Thread {  
  8.   
  9.     // 线程名称  
  10. 10.     private String threadName;  
  11. 11.   
  12. 12.     // 待处理接口数据公共类  
  13. 13.     private PendingIfData pendingIfData;  
  14. 14.   
  15. 15.     ThreadIfExcuse(String threadName, PendingIfData pendingIfData) {  
  16. 16.         this.threadName = threadName;  
  17. 17.         this.pendingIfData = pendingIfData;  
  18. 18.   
  19. 19.     }  
  20. 20.   
  21. 21.     @Override  
  22. 22.     public void run() {  
  23. 23.         while (true) {  
  24. 24.             pendingIfData.ifExcuse(threadName);  
  25. 25.         }  
  26. 26.   
  27. 27.     }  
  28. 28.   

29. }  

详细描述共享资源的内容。实现互斥操作的方式有多种,实例中使用await / signal来控制。一般而言,公用共享资源类中包含

 

数据共享的存储介质,需要线程安全,本示例中为LinkedBlockingQueue类型的interfaceUUIDQueue 。

 

互斥执行方法fExcuse (进入方法使用lock将代码锁住,使用条件condition的await,让代码线程处于等待状态。然后是从数据共享的存储介质中获取待处理数据,然后执行互斥的业务方法。最后lock解锁(lock.unlock)。

 

唤醒方法:如果是一个类调用唤醒方法,则是简单的多线程处理,如果是多个线程调用唤醒方法,则为生产者,消费者,仓库模型了。唤醒方法的详情。首先lock锁定,其次给共享的存储介质中加入待处理数据单元,然后唤醒添加下等待的线程同时醒来进行一次控制权抢夺。其中一个获取控制权去处理一个数据。

 

  1. /** 
  2.  * 待处理接口数据线程 
  3.  *  
  4.  * @author maoping 
  5.  * 
  6.  */  
  7. public class PendingIfData {  
  8.   
  9.     private ReentrantLock lock = new ReentrantLock();  
  10. 10.   
  11. 11.     private Condition condition = lock.newCondition();  
  12. 12.   
  13. 13.     private LinkedBlockingQueue<String> interfaceUUIDQueue = new LinkedBlockingQueue<String>();  
  14. 14.   
  15. 15.     // condition 必须在lock锁范围内  
  16. 16.     public void ifExcuse(String threadName) {  
  17. 17.   
  18. 18.         try {  
  19. 19.             lock.lock();  
  20. 20.             condition.await();  
  21. 21.             // 接口调用  
  22. 22.             String IfUUID = interfaceUUIDQueue.take();  
  23. 23.             // 静态类 不能注入服务 需要使用getBean方式回去服务类  
  24. 24.             IfExcuseService ifExcuseService = new IfExcuseService();  
  25. 25.             ifExcuseService.ifExcuse(IfUUID);  
  26. 26.         } catch (InterruptedException e) {  
  27. 27.             // TODO Auto-generated catch block  
  28. 28.             e.printStackTrace();  
  29. 29.         } finally {  
  30. 30.             lock.unlock();  
  31. 31.         }  
  32. 32.   
  33. 33.     }  
  34. 34.   
  35. 35.     /** 
  36. 36.      * 添加待调用的线程UUID 并激活等待线程 
  37. 37.      *  
  38. 38.      * @param IfUUID 
  39. 39.      */  
  40. 40.     public void putIfUUID(String IfUUID) {  
  41. 41.   
  42. 42.         lock.lock();  
  43. 43.         try {  
  44. 44.             System.out.println("添加待调用接口UUID 激活调用线程 ... ...");  
  45. 45.             interfaceUUIDQueue.put(IfUUID);  
  46. 46.             condition.signal();  
  47. 47.         } catch (InterruptedException e) {  
  48. 48.             e.printStackTrace();  
  49. 49.         }  
  50. 50.         lock.unlock();  
  51. 51.   
  52. 52.     }  
  53. 53.   

54. }  

 

 

4. 线程状态的切换

线程的状态转换是线程控制的基础。线程状态总的可分为五大状态:分别是生、死、可运行、运行、等待/阻塞。用一个图来描述如下:

 

 

 

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

线程在Running的过程中可能会遇到阻塞(Blocked)情况

  1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
  2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。

 

 

 

 

5. 线程的各种机制下的示例

任务场景:一个机构有1000替代币(类似比特币),每次放出20个比特币,放币的时间间隔为 次数*3秒,放完为止。三个矿工集团(A、B、C)使用计算机挖比特币。

用一个线程模拟机构,按照时间规律放替代币,另外三个线程模拟三个矿工公司挖替代币。

 

  1. 公共服务数据类
  2. /** 
  3.  * 数据共享服务 
  4.  *  
  5.  * @author maoping 
  6.  * 
  7.  */  
  8. public class MyCommonData {  
  9.   
  10. 10.     // 锁  
  11. 11.     private ReentrantLock lock = new ReentrantLock();  
  12. 12.   
  13. 13.     // 条件  
  14. 14.     private Condition condition = lock.newCondition();  
  15. 15.   
  16. 16.     // 仓库  
  17. 17.     private LinkedBlockingQueue<Integer> bitMoneyQueue = new LinkedBlockingQueue<Integer>();  
  18. 18.   
  19. 19.     // 矿工挖到比特币  
  20. 20.     public void getBitMoney(String threadName) {  
  21. 21.         try {  
  22. 22.             lock.lock();  
  23. 23.             condition.await();// 处于阻塞状态  
  24. 24.             Integer moneyCode = bitMoneyQueue.take();// 比特币编码  
  25. 25.             System.out.println(threadName + " 挖到了比特币,编码为:" + moneyCode);  
  26. 26.         } catch (InterruptedException e) {  
  27. 27.             e.printStackTrace();  
  28. 28.         } finally {  
  29. 29.             lock.unlock();  
  30. 30.         }  
  31. 31.     }  
  32. 32.   
  33. 33.     public void createBitCoin(Integer bitCoinCode) {  
  34. 34.         try {  
  35. 35.             lock.lock();  
  36. 36.             bitMoneyQueue.put(bitCoinCode);  
  37. 37.             condition.signal();  
  38. 38.         } catch (InterruptedException e) {  
  39. 39.             e.printStackTrace();  
  40. 40.         } finally {  
  41. 41.             lock.unlock();  
  42. 42.         }  
  43. 43.   
  44. 44.     }  
  45. 45.   

46. }  

 

2.生产数据类(此处使用单线程模拟不断生成新的比特币)

  1. /** 
  2.  * 创建替代币公司 
  3.  *  
  4.  * @author maoping 
  5.  * 
  6.  */  
  7. public class CreateBitCoinThread extends Thread {  
  8.   
  9.     // 共享数据  
  10. 10.     private MyCommonData commonData;  
  11. 11.   
  12. 12.     private int count = 0;  
  13. 13.   
  14. 14.     CreateBitCoinThread(MyCommonData commonData) {  
  15. 15.         this.commonData = commonData;  
  16. 16.   
  17. 17.     }  
  18. 18.   
  19. 19.     @Override  
  20. 20.     public void run() {  
  21. 21.         System.out.println("begin ...");  
  22. 22.         long nextCreateBitCoinTime = System.currentTimeMillis();  
  23. 23.         long2date("首次时间为 ", nextCreateBitCoinTime);  
  24. 24.         while (true) {  
  25. 25.             if (count <= 500 && nextCreateBitCoinTime < System.currentTimeMillis()) {  
  26. 26.                 for (int i = 0; i < 20; i++) {  
  27. 27.                     System.out.println("");  
  28. 28.                     commonData.createBitCoin(count * 20 + i + 1);  
  29. 29.                 }  
  30. 30.                 nextCreateBitCoinTime += ((count + 1) * 1000) * 10;  
  31. 31.                 long2date("下次时间为 ", nextCreateBitCoinTime);  
  32. 32.                 count++;  
  33. 33.             }  
  34. 34.         }  
  35. 35.     }  
  36. 36.   
  37. 37.     private void long2date(String str, long dateTime) {  
  38. 38.         SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");  
  39. 39.         java.util.Date dt = new Date(dateTime);  
  40. 40.         String sDateTime = sdf.format(dt); // 得到精确到秒的表示:08/31/2006 21:08:00  
  41. 41.         System.out.println(str + " " + sDateTime);  
  42. 42.     }  
  43. 43.   

44. }  

3.多线程消费数据类

  1. /** 
  2.  * 创建替代币公司 
  3.  *  
  4.  * @author maoping 
  5.  * 
  6.  */  
  7. public class CreateBitCoinThread extends Thread {  
  8.   
  9.     // 共享数据  
  10. 10.     private MyCommonData commonData;  
  11. 11.   
  12. 12.     private int count = 0;  
  13. 13.   
  14. 14.     CreateBitCoinThread(MyCommonData commonData) {  
  15. 15.         this.commonData = commonData;  
  16. 16.   
  17. 17.     }  
  18. 18.   
  19. 19.     @Override  
  20. 20.     public void run() {  
  21. 21.         System.out.println("begin ...");  
  22. 22.         long nextCreateBitCoinTime = System.currentTimeMillis();  
  23. 23.         long2date("首次时间为 ", nextCreateBitCoinTime);  
  24. 24.         while (true) {  
  25. 25.             if (count <= 500 && nextCreateBitCoinTime < System.currentTimeMillis()) {  
  26. 26.                 for (int i = 0; i < 20; i++) {  
  27. 27.                     System.out.println("");  
  28. 28.                     commonData.createBitCoin(count * 20 + i + 1);  
  29. 29.                 }  
  30. 30.                 nextCreateBitCoinTime += ((count + 1) * 1000) * 10;  
  31. 31.                 long2date("下次时间为 ", nextCreateBitCoinTime);  
  32. 32.                 count++;  
  33. 33.             }  
  34. 34.         }  
  35. 35.     }  
  36. 36.   
  37. 37.     private void long2date(String str, long dateTime) {  
  38. 38.         SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");  
  39. 39.         java.util.Date dt = new Date(dateTime);  
  40. 40.         String sDateTime = sdf.format(dt); // 得到精确到秒的表示:08/31/2006 21:08:00  
  41. 41.         System.out.println(str + " " + sDateTime);  
  42. 42.     }  
  43. 43.   

44. }  

  1. main方法主类

 

  1. /** 
  2.  * main 方法主类 
  3.  *  
  4.  * @author maoping 
  5.  * 
  6. 10.  */  

11. public class MainTest {  

  1. 12.   
  2. 13.     public static void main(String[] args) {  
  3. 14.   
  4. 15.         MyCommonData commonData = new MyCommonData();  
  5. 16.         new Thread(new CreateBitCoinThread(commonData)).start();  
  6. 17.         // 矿工挖矿  
  7. 18.         new Thread(new DigBitcoinThread(commonData, "挖矿公司 A")).start();  
  8. 19.         new Thread(new DigBitcoinThread(commonData, "挖矿公司 B")).start();  
  9. 20.         new Thread(new DigBitcoinThread(commonData, "挖矿公司 C")).start();  
  10. 21.   
  11. 22.     }  
  12. 23.   

24. }  

  1. 运行结果类
  2. begin ...  
  3. 首次时间为  03/26/2018 09:23:37  
  4.   
  5.   
  6.   
  7. 挖矿公司 A 挖到了比特币,编码为:1  
  8. 挖矿公司 B 挖到了比特币,编码为:2  
  9.   
  10. 挖矿公司 C 挖到了比特币,编码为:3  
  11.   
  12. 挖矿公司 A 挖到了比特币,编码为:4  
  13.   
  14. 挖矿公司 B 挖到了比特币,编码为:5  
  15.   
  16. 挖矿公司 C 挖到了比特币,编码为:6  
  17.   
  18. 挖矿公司 A 挖到了比特币,编码为:7  
  19.   
  20. 挖矿公司 B 挖到了比特币,编码为:8  
  21.   
  22. 挖矿公司 C 挖到了比特币,编码为:9  
  23.   
  24. 挖矿公司 A 挖到了比特币,编码为:10  
  25.   
  26. 挖矿公司 B 挖到了比特币,编码为:11  
  27.   
  28. 挖矿公司 C 挖到了比特币,编码为:12  
  29.   
  30. 挖矿公司 A 挖到了比特币,编码为:13  
  31.   
  32. 挖矿公司 B 挖到了比特币,编码为:14  
  33.   
  34. 挖矿公司 C 挖到了比特币,编码为:15  
  35.   
  36. 挖矿公司 A 挖到了比特币,编码为:16  
  37.   
  38. 挖矿公司 B 挖到了比特币,编码为:17  
  39.   
  40. 挖矿公司 C 挖到了比特币,编码为:18  
  41.   
  42. 挖矿公司 A 挖到了比特币,编码为:19  
  43. 挖矿公司 B 挖到了比特币,编码为:20  
  44. 下次时间为  03/26/2018 09:23:47  
  45.   
  46. -----------------------  
  47.   
  48.   
  49.   
  50. 挖矿公司 C 挖到了比特币,编码为:21  
  51. 挖矿公司 A 挖到了比特币,编码为:22  
  52.   
  53.   
  54.   
  55. 挖矿公司 B 挖到了比特币,编码为:23  
  56. 挖矿公司 C 挖到了比特币,编码为:24  
  57. 挖矿公司 A 挖到了比特币,编码为:25  
  58.   
  59.   
  60.   
  61. 挖矿公司 B 挖到了比特币,编码为:26  
  62. 挖矿公司 C 挖到了比特币,编码为:27  
  63. 挖矿公司 A 挖到了比特币,编码为:28  
  64.   
  65.   
  66.   
  67.   
  68. 挖矿公司 B 挖到了比特币,编码为:29  
  69. 挖矿公司 C 挖到了比特币,编码为:30  
  70. 挖矿公司 A 挖到了比特币,编码为:31  
  71.   
  72. 挖矿公司 B 挖到了比特币,编码为:32  
  73.   
  74.   
  75.   
  76.   
  77.   
  78. 挖矿公司 C 挖到了比特币,编码为:33  
  79. 挖矿公司 A 挖到了比特币,编码为:34  
  80. 挖矿公司 B 挖到了比特币,编码为:35  
  81.   
  82. 挖矿公司 C 挖到了比特币,编码为:36  
  83. 挖矿公司 A 挖到了比特币,编码为:37  
  84. 下次时间为  03/26/2018 09:24:07  
  85.   
  86. -----------------------------  
  87.   
  88.   
  89. 挖矿公司 B 挖到了比特币,编码为:38  
  90.   
  91.   
  92. 挖矿公司 C 挖到了比特币,编码为:39  
  93. 挖矿公司 A 挖到了比特币,编码为:40  
  94.   

100.挖矿公司 B 挖到了比特币,编码为:41  

102.挖矿公司 C 挖到了比特币,编码为:42  

105.挖矿公司 A 挖到了比特币,编码为:43  

106.挖矿公司 B 挖到了比特币,编码为:44  

108.挖矿公司 C 挖到了比特币,编码为:45  

110.挖矿公司 A 挖到了比特币,编码为:46  

112.挖矿公司 B 挖到了比特币,编码为:47  

114.挖矿公司 C 挖到了比特币,编码为:48  

116.挖矿公司 A 挖到了比特币,编码为:49  

118.挖矿公司 B 挖到了比特币,编码为:50  

120.挖矿公司 C 挖到了比特币,编码为:51  

122.挖矿公司 A 挖到了比特币,编码为:52  

124.挖矿公司 B 挖到了比特币,编码为:53  

126.挖矿公司 C 挖到了比特币,编码为:54  

128.挖矿公司 A 挖到了比特币,编码为:55  

130.挖矿公司 B 挖到了比特币,编码为:56  

131.挖矿公司 C 挖到了比特币,编码为:57  

132.下次时间为  03/26/2018 09:24:37  

由结果可以看出,每次放出20个比特币,且每次放出时间间隔逐渐变成,第一次间隔10s 第二次间隔20s,第三次间隔30s 依次重复。且每次放出的20个比特币被三个挖矿公司挖走。3个挖矿公司为相互竞争每次放出固定数量的比特币。比特币只能被其中的一个公司挖走。使用多线程描述这一情景。

 

以上是关于多线程 简洁版的主要内容,如果未能解决你的问题,请参考以下文章

Python 多线程 多进程 GIL

Python 多线程 多进程 GIL

ServerSocketChannel实现多Selector高并发server

多线程 简洁版

Qt: 多线程, 就是这么简单(确实非常简洁明了)

初识单例模式