多线程技术分享
背景:
以前单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。
- /**
- * 接口调用线程
- *
- * @author maoping
- *
- */
- public class ThreadIfExcuse extends Thread {
- // 线程名称
- 10. private String threadName;
- 11.
- 12. // 待处理接口数据公共类
- 13. private PendingIfData pendingIfData;
- 14.
- 15. ThreadIfExcuse(String threadName, PendingIfData pendingIfData) {
- 16. this.threadName = threadName;
- 17. this.pendingIfData = pendingIfData;
- 18.
- 19. }
- 20.
- 21. @Override
- 22. public void run() {
- 23. while (true) {
- 24. pendingIfData.ifExcuse(threadName);
- 25. }
- 26.
- 27. }
- 28.
29. }
详细描述共享资源的内容。实现互斥操作的方式有多种,实例中使用await / signal来控制。一般而言,公用共享资源类中包含
数据共享的存储介质,需要线程安全,本示例中为LinkedBlockingQueue类型的interfaceUUIDQueue 。
互斥执行方法fExcuse (进入方法使用lock将代码锁住,使用条件condition的await,让代码线程处于等待状态。然后是从数据共享的存储介质中获取待处理数据,然后执行互斥的业务方法。最后lock解锁(lock.unlock)。
唤醒方法:如果是一个类调用唤醒方法,则是简单的多线程处理,如果是多个线程调用唤醒方法,则为生产者,消费者,仓库模型了。唤醒方法的详情。首先lock锁定,其次给共享的存储介质中加入待处理数据单元,然后唤醒添加下等待的线程同时醒来进行一次控制权抢夺。其中一个获取控制权去处理一个数据。
- /**
- * 待处理接口数据线程
- *
- * @author maoping
- *
- */
- public class PendingIfData {
- private ReentrantLock lock = new ReentrantLock();
- 10.
- 11. private Condition condition = lock.newCondition();
- 12.
- 13. private LinkedBlockingQueue<String> interfaceUUIDQueue = new LinkedBlockingQueue<String>();
- 14.
- 15. // condition 必须在lock锁范围内
- 16. public void ifExcuse(String threadName) {
- 17.
- 18. try {
- 19. lock.lock();
- 20. condition.await();
- 21. // 接口调用
- 22. String IfUUID = interfaceUUIDQueue.take();
- 23. // 静态类 不能注入服务 需要使用getBean方式回去服务类
- 24. IfExcuseService ifExcuseService = new IfExcuseService();
- 25. ifExcuseService.ifExcuse(IfUUID);
- 26. } catch (InterruptedException e) {
- 27. // TODO Auto-generated catch block
- 28. e.printStackTrace();
- 29. } finally {
- 30. lock.unlock();
- 31. }
- 32.
- 33. }
- 34.
- 35. /**
- 36. * 添加待调用的线程UUID 并激活等待线程
- 37. *
- 38. * @param IfUUID
- 39. */
- 40. public void putIfUUID(String IfUUID) {
- 41.
- 42. lock.lock();
- 43. try {
- 44. System.out.println("添加待调用接口UUID 激活调用线程 ... ...");
- 45. interfaceUUIDQueue.put(IfUUID);
- 46. condition.signal();
- 47. } catch (InterruptedException e) {
- 48. e.printStackTrace();
- 49. }
- 50. lock.unlock();
- 51.
- 52. }
- 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)情况
- 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。
此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。
5. 线程的各种机制下的示例
任务场景:一个机构有1000替代币(类似比特币),每次放出20个比特币,放币的时间间隔为 次数*3秒,放完为止。三个矿工集团(A、B、C)使用计算机挖比特币。
用一个线程模拟机构,按照时间规律放替代币,另外三个线程模拟三个矿工公司挖替代币。
- 公共服务数据类
- /**
- * 数据共享服务
- *
- * @author maoping
- *
- */
- public class MyCommonData {
- 10. // 锁
- 11. private ReentrantLock lock = new ReentrantLock();
- 12.
- 13. // 条件
- 14. private Condition condition = lock.newCondition();
- 15.
- 16. // 仓库
- 17. private LinkedBlockingQueue<Integer> bitMoneyQueue = new LinkedBlockingQueue<Integer>();
- 18.
- 19. // 矿工挖到比特币
- 20. public void getBitMoney(String threadName) {
- 21. try {
- 22. lock.lock();
- 23. condition.await();// 处于阻塞状态
- 24. Integer moneyCode = bitMoneyQueue.take();// 比特币编码
- 25. System.out.println(threadName + " 挖到了比特币,编码为:" + moneyCode);
- 26. } catch (InterruptedException e) {
- 27. e.printStackTrace();
- 28. } finally {
- 29. lock.unlock();
- 30. }
- 31. }
- 32.
- 33. public void createBitCoin(Integer bitCoinCode) {
- 34. try {
- 35. lock.lock();
- 36. bitMoneyQueue.put(bitCoinCode);
- 37. condition.signal();
- 38. } catch (InterruptedException e) {
- 39. e.printStackTrace();
- 40. } finally {
- 41. lock.unlock();
- 42. }
- 43.
- 44. }
- 45.
46. }
2.生产数据类(此处使用单线程模拟不断生成新的比特币)
- /**
- * 创建替代币公司
- *
- * @author maoping
- *
- */
- public class CreateBitCoinThread extends Thread {
- // 共享数据
- 10. private MyCommonData commonData;
- 11.
- 12. private int count = 0;
- 13.
- 14. CreateBitCoinThread(MyCommonData commonData) {
- 15. this.commonData = commonData;
- 16.
- 17. }
- 18.
- 19. @Override
- 20. public void run() {
- 21. System.out.println("begin ...");
- 22. long nextCreateBitCoinTime = System.currentTimeMillis();
- 23. long2date("首次时间为 ", nextCreateBitCoinTime);
- 24. while (true) {
- 25. if (count <= 500 && nextCreateBitCoinTime < System.currentTimeMillis()) {
- 26. for (int i = 0; i < 20; i++) {
- 27. System.out.println("");
- 28. commonData.createBitCoin(count * 20 + i + 1);
- 29. }
- 30. nextCreateBitCoinTime += ((count + 1) * 1000) * 10;
- 31. long2date("下次时间为 ", nextCreateBitCoinTime);
- 32. count++;
- 33. }
- 34. }
- 35. }
- 36.
- 37. private void long2date(String str, long dateTime) {
- 38. SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
- 39. java.util.Date dt = new Date(dateTime);
- 40. String sDateTime = sdf.format(dt); // 得到精确到秒的表示:08/31/2006 21:08:00
- 41. System.out.println(str + " " + sDateTime);
- 42. }
- 43.
44. }
3.多线程消费数据类
- /**
- * 创建替代币公司
- *
- * @author maoping
- *
- */
- public class CreateBitCoinThread extends Thread {
- // 共享数据
- 10. private MyCommonData commonData;
- 11.
- 12. private int count = 0;
- 13.
- 14. CreateBitCoinThread(MyCommonData commonData) {
- 15. this.commonData = commonData;
- 16.
- 17. }
- 18.
- 19. @Override
- 20. public void run() {
- 21. System.out.println("begin ...");
- 22. long nextCreateBitCoinTime = System.currentTimeMillis();
- 23. long2date("首次时间为 ", nextCreateBitCoinTime);
- 24. while (true) {
- 25. if (count <= 500 && nextCreateBitCoinTime < System.currentTimeMillis()) {
- 26. for (int i = 0; i < 20; i++) {
- 27. System.out.println("");
- 28. commonData.createBitCoin(count * 20 + i + 1);
- 29. }
- 30. nextCreateBitCoinTime += ((count + 1) * 1000) * 10;
- 31. long2date("下次时间为 ", nextCreateBitCoinTime);
- 32. count++;
- 33. }
- 34. }
- 35. }
- 36.
- 37. private void long2date(String str, long dateTime) {
- 38. SimpleDateFormat sdf = new SimpleDateFormat("MM/dd/yyyy HH:mm:ss");
- 39. java.util.Date dt = new Date(dateTime);
- 40. String sDateTime = sdf.format(dt); // 得到精确到秒的表示:08/31/2006 21:08:00
- 41. System.out.println(str + " " + sDateTime);
- 42. }
- 43.
44. }
- main方法主类
- /**
- * main 方法主类
- *
- * @author maoping
- *
- 10. */
11. public class MainTest {
- 12.
- 13. public static void main(String[] args) {
- 14.
- 15. MyCommonData commonData = new MyCommonData();
- 16. new Thread(new CreateBitCoinThread(commonData)).start();
- 17. // 矿工挖矿
- 18. new Thread(new DigBitcoinThread(commonData, "挖矿公司 A")).start();
- 19. new Thread(new DigBitcoinThread(commonData, "挖矿公司 B")).start();
- 20. new Thread(new DigBitcoinThread(commonData, "挖矿公司 C")).start();
- 21.
- 22. }
- 23.
24. }
- 运行结果类
- begin ...
- 首次时间为 03/26/2018 09:23:37
- 挖矿公司 A 挖到了比特币,编码为:1
- 挖矿公司 B 挖到了比特币,编码为:2
- 挖矿公司 C 挖到了比特币,编码为:3
- 挖矿公司 A 挖到了比特币,编码为:4
- 挖矿公司 B 挖到了比特币,编码为:5
- 挖矿公司 C 挖到了比特币,编码为:6
- 挖矿公司 A 挖到了比特币,编码为:7
- 挖矿公司 B 挖到了比特币,编码为:8
- 挖矿公司 C 挖到了比特币,编码为:9
- 挖矿公司 A 挖到了比特币,编码为:10
- 挖矿公司 B 挖到了比特币,编码为:11
- 挖矿公司 C 挖到了比特币,编码为:12
- 挖矿公司 A 挖到了比特币,编码为:13
- 挖矿公司 B 挖到了比特币,编码为:14
- 挖矿公司 C 挖到了比特币,编码为:15
- 挖矿公司 A 挖到了比特币,编码为:16
- 挖矿公司 B 挖到了比特币,编码为:17
- 挖矿公司 C 挖到了比特币,编码为:18
- 挖矿公司 A 挖到了比特币,编码为:19
- 挖矿公司 B 挖到了比特币,编码为:20
- 下次时间为 03/26/2018 09:23:47
- -----------------------
- 挖矿公司 C 挖到了比特币,编码为:21
- 挖矿公司 A 挖到了比特币,编码为:22
- 挖矿公司 B 挖到了比特币,编码为:23
- 挖矿公司 C 挖到了比特币,编码为:24
- 挖矿公司 A 挖到了比特币,编码为:25
- 挖矿公司 B 挖到了比特币,编码为:26
- 挖矿公司 C 挖到了比特币,编码为:27
- 挖矿公司 A 挖到了比特币,编码为:28
- 挖矿公司 B 挖到了比特币,编码为:29
- 挖矿公司 C 挖到了比特币,编码为:30
- 挖矿公司 A 挖到了比特币,编码为:31
- 挖矿公司 B 挖到了比特币,编码为:32
- 挖矿公司 C 挖到了比特币,编码为:33
- 挖矿公司 A 挖到了比特币,编码为:34
- 挖矿公司 B 挖到了比特币,编码为:35
- 挖矿公司 C 挖到了比特币,编码为:36
- 挖矿公司 A 挖到了比特币,编码为:37
- 下次时间为 03/26/2018 09:24:07
- -----------------------------
- 挖矿公司 B 挖到了比特币,编码为:38
- 挖矿公司 C 挖到了比特币,编码为:39
- 挖矿公司 A 挖到了比特币,编码为:40
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个挖矿公司为相互竞争每次放出固定数量的比特币。比特币只能被其中的一个公司挖走。使用多线程描述这一情景。