zoukankan      html  css  js  c++  java
  • 多线程 简洁版

    多线程技术分享

    背景:

    以前单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个挖矿公司为相互竞争每次放出固定数量的比特币。比特币只能被其中的一个公司挖走。使用多线程描述这一情景。

  • 相关阅读:
    OpenCR 固件修复
    E-PUCK2机器人-固件更新
    E-puck2机器人系列教程-2.软件的安装与使用
    E-PUCK2机器人-硬件
    E-puck2机器人系列教程-固件修复升级
    GridView
    TimePicker 和TimePickerDiag
    android中实现简单的播放
    ListView的使用
    android的activity的跳转
  • 原文地址:https://www.cnblogs.com/maopneo/p/8648816.html
Copyright © 2011-2022 走看看