当前位置:   article > 正文

Thread多线程(创建,方法,安全,通信,线程池,并发,并行,线程的生命周期)【全详解】

Thread多线程(创建,方法,安全,通信,线程池,并发,并行,线程的生命周期)【全详解】

目录

1.多线程概述

2.多线程的创建

3.Thread的常用方法

4.线程安全

5.线程同步

6.线程通信

7.线程池

8.其它细节知识:并发、并行

9.其它细节知识:线程的生命周期   


1.多线程概述

        线程是什么?

                线程(Thread)是一个程序内部的一条执行流程。

                程序中如果只有一条执行流程,那这个程序就是单线程的程序。

        多线程是什么?

                多线程是指从软硬件上实现的多条执行流程的技术(多条线程由CPU负责调度执行)。

2.多线程的创建

        方式一:继承Thread类

        实现步骤:

                定义一个子类MyThread继承线程类java.lang.Thread,重写run()方法

                创建MyThread类的对象

                调用线程对象的start()方法启动线程(启动后还是执行run方法的)

        代码实现

        

  1. package com.itheima.day11.teacher.thread01;
  2. /*
  3. start() 开启新线程的方法 --- 线程开启功能
  4. run() 线程执行的代码 --- 线程任务
  5. 继承方法
  6. MyThread 既是一个线程对象(负责线程开启) 又是一个线程任务(写需要执行线程任务)
  7. 耦合性高!!
  8. 线程对象 -- 线程任务 分离!!
  9. */
  10. public class MyThread extends Thread{
  11. @Override
  12. public void run() {
  13. /*
  14. 每个新的线程 都循环输出 十次
  15. */
  16. for (int i = 0; i < 10; i++) {
  17. // 代码执行过程中 可以获取到当前正在运行的线程对象
  18. Thread thread = Thread.currentThread();
  19. System.out.println("新的线程"+thread.getName()+"正在执行"+i);
  20. }
  21. }
  22. }
  23. -----------------------
  24. package com.itheima.day11.teacher.thread01;
  25. public class Test {
  26. public static void main(String[] args) {
  27. System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());
  28. System.out.println("程序入口也是一个线程 这个线程叫主线程..");
  29. //主线程执行过程中 再产生新的线程。
  30. /*
  31. 线程的创建方式一
  32. 1: 创建一个类 继承 Thread线程类
  33. 2: 手动重写run方法。----就是新的线程对象要执行的内容.
  34. 3: 创建 子类对象(线程对象)。
  35. 4: 调用start方法 开启这个新的线程。
  36. 当开启完了 程序中出现两个线程了 一个是 主线程 一个新建的线程。
  37. 每个线程将来都是独立的空间。
  38. */
  39. MyThread t1 = new MyThread();
  40. t1.start();
  41. // 开启新的线程
  42. for (int i = 0; i < 10; i++) {
  43. System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);
  44. }
  45. }
  46. }
  47. --------------------
  48. package com.itheima.day11.teacher.thread01;
  49. public class Test2 {
  50. /*
  51. 线程的创建方式一
  52. 1: 创建一个类 继承 Thread线程类
  53. 2: 手动重写run方法。----就是新的线程对象要执行的内容.
  54. 3: 创建 子类对象(线程对象)。
  55. 4: 调用start方法 开启这个新的线程。
  56. 当开启完了 程序中出现两个线程了 一个是 主线程 一个新建的线程。
  57. 每个线程将来都是独立的空间。
  58. 多个线程执行
  59. 每次执行效果 不尽相同 因为 CPU的高速切换 没有规律
  60. Thread 代表线程对象的类
  61. Thread.currentThread() 获取 执行当前代码的线程。
  62. 普通方法
  63. getName() 获取线程的名字 名字默认 Thread-0 Thread-1 ....
  64. 可以设置名字
  65. setName("...")
  66. */
  67. public static void main(String[] args) {
  68. //程序入口也是一个线程 这个线程叫主线程..
  69. System.out.println("当前是一个程序的入口"+Thread.currentThread().getName());
  70. //主线程执行过程中 再产生新的线程。
  71. MyThread t1 = new MyThread();
  72. t1.setName("小迪迪");
  73. t1.start();
  74. // 开启新的线程
  75. // 这还是main中
  76. for (int i = 0; i < 10; i++) {
  77. System.out.println("线程"+Thread.currentThread().getName()+"正在执行"+i);
  78. }
  79. }
  80. }

                优缺点:

                        优点:编码简单

                        缺点:存在单继承的局限性,线程类继承Thread后,不能继承其他类,不便于扩展

        方式二:实现Runnable接口

                实现步骤:

                        定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法

                        创建MyRunnable任务对象

                        把MyRunnable任务对象交给Thread处理。

                        调用线程对象的start()方法启动线程

  1. package com.itheima.day11.teacher.thread02;
  2. /*
  3. 线程任务类 里面只有线程任务 run方法
  4. */
  5. public class MyRunnable implements Runnable{
  6. @Override
  7. public void run() {
  8. for (int i = 0; i < 10; i++) {
  9. System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
  10. }
  11. }
  12. }
  13. -------------------------
  14. package com.itheima.day11.teacher.thread02;
  15. public class RunnableTest {
  16. /*
  17. 创建线程方式二
  18. 1: 定义一个实现Runnable接口的 线程任务类,重写run方法。
  19. 2: 创建一个线程任务对象。
  20. 3: 创建线程对象 并在构造中传递线程任务对象。
  21. 4: 开启新的线程 线程对象.start()
  22. */
  23. public static void main(String[] args) {
  24. // 创建线程任务对象
  25. MyRunnable mr = new MyRunnable();
  26. // 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定
  27. Thread t = new Thread(mr);
  28. //调用start方法
  29. t.start();
  30. //主线程操作
  31. for (int i = 0; i <10 ; i++) {
  32. System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
  33. }
  34. }
  35. }
  36. --------------------
  37. package com.itheima.day11.teacher.thread02;
  38. public class RunnableTest2 {
  39. /*
  40. 创建线程方式二
  41. 1: 定义一个实现Runnable接口的 线程任务类,重写run方法。
  42. 2: 创建一个线程任务对象。
  43. 3: 创建线程对象 并在构造中传递线程任务对象。
  44. 4: 开启新的线程 线程对象.start()
  45. */
  46. public static void main(String[] args) {
  47. // 创建线程任务对象
  48. MyRunnable mr = new MyRunnable();
  49. //上面的操作 创建了一个外部类 实现了接口 并创建该外部类的对象 外部类对象---Runnabel接口的实现类对象
  50. // 创建线程对象的同时 将线程任务传递过去 --- 线程和任务绑定
  51. Thread t1 = new Thread(mr);//mr 是 Runnable接口的实现类对象
  52. //调用start方法
  53. t1.start();
  54. //
  55. // Thread t2 = new Thread(匿名内部类形式) 匿名内部类本质 子类对象
  56. Thread t2 = new Thread(new Runnable() {
  57. @Override
  58. public void run() {
  59. for (int i = 0; i < 10; i++) {
  60. System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
  61. }
  62. }
  63. });
  64. t2.start();
  65. // 能不能用lambda 可以
  66. // lambda 作用简化匿名内部类 使用前提 参数是一个函数式接口 函数式接口 有且只有一个抽象方法的接口
  67. new Thread(()->{
  68. for (int i = 0; i < 10; i++) {
  69. System.out.println("当前在"+Thread.currentThread().getName()+"正在输出:"+i);
  70. }
  71. }).start();
  72. }
  73. }

                优缺点:

                        优点:任务类只是实现接口,可以继续继承其他类、实现其他接口,扩展性强。 

                        缺点:需要多一个Runnable对象。

        方式三:实现Callable接口  

        实现步骤:

        1.创建任务对象:

                1.定义一个类实现Callable接口,重写call方法,封装要做的事情和要返回的数据。        

                2.把Callable类型的对象封装成FutureTask对象(线程任务对象)。

         2.把线程任务对象封装成Thread对象。

         3.调用Thread对象的start方法启动线程。

         4.线程执行完毕后、通过FutureTask对象的的get方法去获取线程任务执行的结果。

 

  1. package com.itheima.day11.teacher.thread03;
  2. import java.util.concurrent.Callable;
  3. /*
  4. Callable接口有个泛型 表示 返回的结果类型。
  5. 返回值 call
  6. */
  7. public class MyCallable implements Callable<Integer> {
  8. // 构造 方法 都可以访问 成员变量
  9. private Integer number;
  10. public MyCallable(Integer number){
  11. // 当能够调用call方法的时候 构造方法执行完了
  12. // 方法中没有参数 构造中可不可以设计参数
  13. // 把构造中传递的number 赋值给成员变量
  14. this.number = number;
  15. }
  16. //任务 求一个数的 绝对值
  17. @Override
  18. public Integer call() throws Exception {
  19. System.out.println("当前的线程:"+Thread.currentThread().getName());
  20. return Math.abs(number);//直接使用
  21. }
  22. }
  23. -----------------------
  24. package com.itheima.day11.teacher.thread03;
  25. import java.util.concurrent.Callable;
  26. /*
  27. Callable接口有个泛型 表示 返回的结果类型。
  28. 返回值 call
  29. */
  30. public class MyCallable1 implements Callable<String> {
  31. //任务 求一个数的 绝对值
  32. @Override
  33. public String call() throws Exception {
  34. System.out.println("当前的线程:"+Thread.currentThread().getName());
  35. return "下载任务执行成功";//直接使用
  36. }
  37. }
  38. -----------------------
  39. package com.itheima.day11.teacher.thread03;
  40. import java.util.concurrent.Callable;
  41. import java.util.concurrent.ExecutionException;
  42. import java.util.concurrent.FutureTask;
  43. public class CallableTest {
  44. /*
  45. 创建线程方式三
  46. 1:创建子类实现Callable接口 完成call方法重写。
  47. 2:创建线程任务(自定义callable)对象.
  48. 3:创建一个 线程任务管理对象 FutureTask封装 callable实现对象。
  49. 4:创建线程对象 绑定线程任务管理对象
  50. 5:调用start方法
  51. */
  52. public static void main(String[] args) throws ExecutionException, InterruptedException {
  53. //创建线程任务对象
  54. MyCallable my = new MyCallable(-10);//带参构造
  55. //传参给 Thread 创建线程对象
  56. // Thread thread = new Thread(my);
  57. // Callable 是一个带有返回值的 任务--需要先交给任务管理对象 因为执行之后该线程是有返回值的 得有对象去处理返回值
  58. FutureTask<Integer> task = new FutureTask<>(my);
  59. // 在把 task对象 绑定到线程对象中
  60. Thread thread = new Thread(task);
  61. // 调用start方法
  62. thread.start();
  63. // 返回值在 Task身上
  64. System.out.println("任务执行结果:"+task.get());
  65. MyCallable1 callable1 = new MyCallable1();
  66. FutureTask<String> stringFutureTask = new FutureTask<String>(callable1);
  67. Thread thread1 = new Thread(stringFutureTask);
  68. thread1.start();
  69. String s = stringFutureTask.get();
  70. System.out.println(s);
  71. //这样写没意义,不能传参数,只能自己定义吗
  72. /* FutureTask<Integer> integerFutureTask = new FutureTask<>(new Callable<Integer>() {
  73. int a=10;
  74. int b=0;
  75. @Override
  76. public Integer call() throws Exception {
  77. return a+b;
  78. }
  79. });
  80. new Thread(integerFutureTask).start(); //不执行线程是拿不到返回会值结果的
  81. // new Thread(new FutureTask<Integer>(()-> 10+0 )).start(); 可以这样简写,但是拿不到结果值没意义
  82. System.out.println(integerFutureTask.get());*/
  83. }
  84. }

3.Thread的常用方法

    

  1. package com.itheima.day12.teacher.thread01;
  2. public class MyThread extends Thread{
  3. public MyThread(String name){
  4. super(name);//把名字传递给父类的构造
  5. }
  6. //线程任务方法 将来哪个线程对象执行 在它的代码中就可以得到哪个线程对象
  7. @Override
  8. public void run() {
  9. // 干吕布
  10. Thread thread = Thread.currentThread();//获取当前线程对象
  11. for (int i = 1; i <=5 ; i++) {
  12. //每人跟吕布过招 5次
  13. System.out.println(thread.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");
  14. // System.out.println(super.getName()+"大呼:吕布小儿,莫跑~~吃我"+i+"招!!");
  15. }
  16. }
  17. }
  18. ----------------------
  19. package com.itheima.day12.teacher.thread01;
  20. public class ThreadDemo {
  21. public static void main(String[] args) throws InterruptedException {
  22. //三英战吕布
  23. System.out.println("接下来请欣赏 三英战吕布!");
  24. // 每秒钟 输出 5 4 3 2 1
  25. for (int i = 5; i >=1 ; i--) {
  26. System.out.println(i);
  27. //休眠一秒
  28. Thread.sleep(1000);
  29. }
  30. MyThread bb = new MyThread("刘备");
  31. System.out.println(bb.getName());
  32. // bb.setName("刘备");
  33. MyThread yy = new MyThread("关羽");
  34. System.out.println(yy.getName());
  35. MyThread ff = new MyThread("张飞");
  36. System.out.println(ff.getName());
  37. bb.start();
  38. yy.start();
  39. ff.start();
  40. }
  41. }
  42. ---------------------
  43. package com.itheima.day12.teacher.thread01;
  44. public class ThreadDemo02 {
  45. public static void main(String[] args) throws InterruptedException {
  46. System.out.println("宇迪和弓箭手 一起打吕布");
  47. //创建宇迪线程
  48. MyThread my = new MyThread("宇迪");
  49. my.start();//宇迪线程执行
  50. my.join();//先执行 当前 my线程对象 再执行其他的线程
  51. //同时总部 排除弓箭手也对 宇迪进行打击
  52. Thread.currentThread().setName("弓箭手");
  53. for (int i = 1; i <= 5; i++) {
  54. System.out.println(Thread.currentThread().getName()+"在发射第"+i+"只箭~");
  55. }
  56. }
  57. }

4.线程安全

        什么是线程安全问题

                多个线程,同时操作同一个共享资源的时候,可能会出现业务安全问题。

        线程安全问题出现的原因?

                1.存在多个线程在同时执行

                2.多个线程同时访问一个共享资源

                3.存在修改该共享资源的情况

        用程序模拟线程安全问题

  1. /**
  2. 出现的问题
  3. 两人同时取钱,卡里10万,结果都取成功,卡里-10
  4. */
  5. package com.itheima.day12.teacher.asynchronize;
  6. // 先定义账户类
  7. public class Account {
  8. private String cardId;
  9. private double money;//余额
  10. public Account() {
  11. }
  12. public Account(String cardId, double money) {
  13. this.cardId = cardId;
  14. this.money = money;
  15. }
  16. /*
  17. 设计一个取钱的方法
  18. 参数 取得钱 金额
  19. 返回值 不需要
  20. */
  21. public void drawMoney(double money){
  22. // 局部位置money 代表取的钱
  23. // 成员位置money 代表余额
  24. // this.money -= money;
  25. // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
  26. String name = Thread.currentThread().getName();
  27. // 判断余额是否充足
  28. if(this.money >= money){//可以取
  29. System.out.println(name+"来取钱:"+money+" 成功!");
  30. this.money -= money;
  31. System.out.println(name+"取钱之后的余额:"+this.money);
  32. }else {
  33. System.out.println(name+"来取钱:余额不足,请充值后再去...");
  34. }
  35. }
  36. /**
  37. * 获取
  38. * @return cardId
  39. */
  40. public String getCardId() {
  41. return cardId;
  42. }
  43. /**
  44. * 设置
  45. * @param cardId
  46. */
  47. public void setCardId(String cardId) {
  48. this.cardId = cardId;
  49. }
  50. /**
  51. * 获取
  52. * @return money
  53. */
  54. public double getMoney() {
  55. return money;
  56. }
  57. /**
  58. * 设置
  59. * @param money
  60. */
  61. public void setMoney(double money) {
  62. this.money = money;
  63. }
  64. @Override
  65. public String toString() {
  66. return "Account{cardId = " + cardId + ", money = " + money + "}";
  67. }
  68. }
  69. ---------------
  70. package com.itheima.day12.teacher.asynchronize;
  71. /*
  72. 取钱线程类
  73. */
  74. public class DrawMoney extends Thread{
  75. // 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来
  76. private Account account;
  77. // 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字
  78. public DrawMoney(Account account,String name){
  79. super(name);
  80. this.account = account;
  81. }
  82. @Override
  83. public void run() {
  84. //取钱就是在账户里面 减少余额
  85. // 调用账户对象 的 取钱方法
  86. // 小明线程 小红线程 操作的 账户是同一个
  87. // 在测试类创建 一个账户 传递进来
  88. account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
  89. }
  90. }
  91. ------------------
  92. package com.itheima.day12.teacher.asynchronize;
  93. public class ThreadTest {
  94. public static void main(String[] args) {
  95. //创建一个账户对象 小明和小红 共享账户 卡号 余额
  96. Account account = new Account("ICBC-114",100000);
  97. //创建两个线程对象 分别代表小红 和 小明
  98. // 线程对象中传递 共享的账户 以及 线程名字
  99. new DrawMoney(account,"小明").start();
  100. new DrawMoney(account,"小红").start();
  101. }
  102. }

5.线程同步

        1.认识线程同步: 线程同步是解决线程安全问题的方案

        2.线程同步的思想:

                1.让多个线程实现先后依次访问共享资源,这样就解决了安全问题。

                2.加锁:每次只允许一个线程加锁,加锁后才能进入访问,访问完毕后自动解锁,然后其他线程才能再加锁进来。

        3.线程同步的解决方案

                方式一:同步代码块

                        作用:把访问共享资源的核心代码给上锁,以此保证线程安全。

                        原理:每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

                        写法:

                                synchronized(同步锁) { 访问共享资源的核心代码 }

                        同步锁的注意事项:对于当前同时执行的线程来说,同步锁必须是同一把(同一个对象),否则会出bug。

                        锁对象的使用规范:

                                建议使用共享资源作为锁对象

                                对于实例方法建议使用this作为锁对象

                                对于静态方法建议使用字节码(类名.class)对象作为锁对象

                

  1. package com.itheima.day12.teacher.synchronize01;
  2. // 先定义账户类
  3. public class Account {
  4. private String cardId;
  5. private double money;//余额
  6. public Account() {
  7. }
  8. public Account(String cardId, double money) {
  9. this.cardId = cardId;
  10. this.money = money;
  11. }
  12. /*
  13. 设计一个取钱的方法
  14. 参数 取得钱 金额
  15. 返回值 不需要
  16. */
  17. public void drawMoney(double money){
  18. // 使用同步代码块方式加锁 保证两个线程只要一个线程在修改共享资源
  19. // 锁对象怎么选择 --- 只要保证 两个线程对象将来公用一把锁
  20. synchronized (this){ //() 里面的对象具备唯一性 "lock" 字符串一旦创建不能能改
  21. //直接写字符串 可以 但是不规范 开发规范当前共享资源 一般普通方法写 this 静态方法写 类名.class
  22. // 局部位置money 代表取的钱
  23. // 成员位置money 代表余额
  24. // this.money -= money;
  25. // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
  26. String name = Thread.currentThread().getName();
  27. // 判断余额是否充足
  28. if(this.money >= money){//可以取
  29. System.out.println(name+"来取钱:"+money+" 成功!");
  30. this.money -= money;
  31. System.out.println(name+"取钱之后的余额:"+this.money);
  32. }else {
  33. System.out.println(name+"来取钱:余额不足,请充值后再去...");
  34. }
  35. }
  36. }
  37. /**
  38. * 获取
  39. * @return cardId
  40. */
  41. public String getCardId() {
  42. return cardId;
  43. }
  44. /**
  45. * 设置
  46. * @param cardId
  47. */
  48. public void setCardId(String cardId) {
  49. this.cardId = cardId;
  50. }
  51. /**
  52. * 获取
  53. * @return money
  54. */
  55. public double getMoney() {
  56. return money;
  57. }
  58. /**
  59. * 设置
  60. * @param money
  61. */
  62. public void setMoney(double money) {
  63. this.money = money;
  64. }
  65. public String toString() {
  66. return "Account{cardId = " + cardId + ", money = " + money + "}";
  67. }
  68. }
  69. -----------------------
  70. package com.itheima.day12.teacher.synchronize01;
  71. /*
  72. 取钱线程类
  73. */
  74. public class DrawMoney extends Thread{
  75. // 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来
  76. private Account account;
  77. // 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字
  78. public DrawMoney(Account account, String name){
  79. super(name);
  80. this.account = account;
  81. }
  82. @Override
  83. public void run() {
  84. //取钱就是在账户里面 减少余额
  85. // 调用账户对象 的 取钱方法
  86. // 小明线程 小红线程 操作的 账户是同一个
  87. // 在测试类创建 一个账户 传递进来
  88. account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
  89. }
  90. }
  91. ---------------------
  92. package com.itheima.day12.teacher.synchronize01;
  93. public class ThreadTest {
  94. public static void main(String[] args) {
  95. //创建一个账户对象 小明和小红 共享账户 卡号 余额
  96. Account account = new Account("ICBC-114",100000);
  97. //创建两个线程对象 分别代表小红 和 小明
  98. // 线程对象中传递 共享的账户 以及 线程名字
  99. new DrawMoney(account,"小明").start();
  100. new DrawMoney(account,"小红").start();
  101. }
  102. }

                方式二:同步方法

                        作用:把访问共享资源的核心方法给上锁,以此保证线程安全。

                        原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

                        写法:        

                                修饰符 synchronized 返回值类型 方法名称(形参列表) { 操作共享资源的代码 }

                        同步方法底层原理:

                                1.同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。

                                2.如果方法是实例方法:同步方法默认用this作为的锁对象。

                                3.如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

                是同步代码块好还是同步方法好一点?

                        范围上:同步代码块锁的范围更小,同步方法锁的范围更大。

                        可读性:同步方法更好。

                

  1. package com.itheima.day12.teacher.synchronize02;
  2. // 先定义账户类
  3. public class Account {
  4. private String cardId;
  5. private double money;//余额
  6. public Account() {
  7. }
  8. public Account(String cardId, double money) {
  9. this.cardId = cardId;
  10. this.money = money;
  11. }
  12. /*
  13. 设计一个取钱的方法
  14. 参数 取得钱 金额
  15. 返回值 不需要
  16. 同步方法
  17. 就是将锁 固定到方法上了 整个方法上锁了
  18. 方法声明位置 加入 synchronized 关键字
  19. */
  20. public synchronized void drawMoney(double money){
  21. // 局部位置money 代表取的钱
  22. // 成员位置money 代表余额
  23. // this.money -= money;
  24. // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
  25. String name = Thread.currentThread().getName();
  26. // 判断余额是否充足
  27. if(this.money >= money){//可以取
  28. System.out.println(name+"来取钱:"+money+" 成功!");
  29. this.money -= money;
  30. System.out.println(name+"取钱之后的余额:"+this.money);
  31. }else {
  32. System.out.println(name+"来取钱:余额不足,请充值后再去...");
  33. }
  34. }
  35. /**
  36. * 获取
  37. * @return cardId
  38. */
  39. public String getCardId() {
  40. return cardId;
  41. }
  42. /**
  43. * 设置
  44. * @param cardId
  45. */
  46. public void setCardId(String cardId) {
  47. this.cardId = cardId;
  48. }
  49. /**
  50. * 获取
  51. * @return money
  52. */
  53. public double getMoney() {
  54. return money;
  55. }
  56. /**
  57. * 设置
  58. * @param money
  59. */
  60. public void setMoney(double money) {
  61. this.money = money;
  62. }
  63. public String toString() {
  64. return "Account{cardId = " + cardId + ", money = " + money + "}";
  65. }
  66. }
  67. -----------------
  68. package com.itheima.day12.teacher.synchronize02;
  69. /*
  70. 取钱线程类
  71. */
  72. public class DrawMoney extends Thread{
  73. // 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来
  74. private Account account;
  75. // 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字
  76. public DrawMoney(Account account, String name){
  77. super(name);
  78. this.account = account;
  79. }
  80. @Override
  81. public void run() {
  82. //取钱就是在账户里面 减少余额
  83. // 调用账户对象 的 取钱方法
  84. // 小明线程 小红线程 操作的 账户是同一个
  85. // 在测试类创建 一个账户 传递进来
  86. account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
  87. }
  88. }
  89. -----------------
  90. package com.itheima.day12.teacher.synchronize02;
  91. public class ThreadTest {
  92. public static void main(String[] args) {
  93. //创建一个账户对象 小明和小红 共享账户 卡号 余额
  94. Account account = new Account("ICBC-114",100000);
  95. //创建两个线程对象 分别代表小红 和 小明
  96. // 线程对象中传递 共享的账户 以及 线程名字
  97. new DrawMoney(account,"小明").start();
  98. new DrawMoney(account,"小红").start();
  99. }
  100. }

                方式三:Lock锁

                        Lock锁是JDK5开始提供的一个新的锁定操作,通过它可以创建出锁对象进行加锁和解锁,更灵活、更方便、更强大。

                        Lock是接口,不能直接实例化,可以采用它的实现类ReentrantLock来构建Lock锁对象。

        

  1. package com.itheima.day12.teacher.lock;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. // 先定义账户类
  5. public class Account {
  6. private String cardId;
  7. private double money;//余额
  8. public Account() {
  9. }
  10. public Account(String cardId, double money) {
  11. this.cardId = cardId;
  12. this.money = money;
  13. }
  14. /*
  15. 设计一个取钱的方法
  16. 参数 取得钱 金额
  17. 返回值 不需要
  18. Lock 都是加锁原理
  19. 底层 都是每次只允许一个线程加锁 加锁之后才能访问,手动解锁 其他线程可以再加锁进来。
  20. 灵活的加锁和释放锁
  21. 两个方法
  22. lock() 加锁 unlock() 释放锁
  23. Lock接口 用它的实现类
  24. */
  25. //成员位置创建一个 Lock锁对象 锁对象不能被改 所以 加上final修饰
  26. private final Lock lock = new ReentrantLock();
  27. public void drawMoney(double money){
  28. // 局部位置money 代表取的钱
  29. // 成员位置money 代表余额
  30. // this.money -= money;
  31. // 知道是谁来取的----哪个线程执行到这里了 谁就是哪个线程
  32. String name = Thread.currentThread().getName();
  33. // 判断余额是否充足
  34. lock.lock();//加锁
  35. if(this.money >= money){//可以取
  36. System.out.println(name+"来取钱:"+money+" 成功!");
  37. this.money -= money;
  38. System.out.println(name+"取钱之后的余额:"+this.money);
  39. }else {
  40. System.out.println(name+"来取钱:余额不足,请充值后再去...");
  41. }
  42. lock.unlock();//释放锁
  43. }
  44. /**
  45. * 获取
  46. * @return cardId
  47. */
  48. public String getCardId() {
  49. return cardId;
  50. }
  51. /**
  52. * 设置
  53. * @param cardId
  54. */
  55. public void setCardId(String cardId) {
  56. this.cardId = cardId;
  57. }
  58. /**
  59. * 获取
  60. * @return money
  61. */
  62. public double getMoney() {
  63. return money;
  64. }
  65. /**
  66. * 设置
  67. * @param money
  68. */
  69. public void setMoney(double money) {
  70. this.money = money;
  71. }
  72. public String toString() {
  73. return "Account{cardId = " + cardId + ", money = " + money + "}";
  74. }
  75. }
  76. ---------------------
  77. package com.itheima.day12.teacher.lock;
  78. /*
  79. 取钱线程类
  80. */
  81. public class DrawMoney extends Thread{
  82. // 定义一个账户的成员变量 --初始化交给了构造 由外界传递进来
  83. private Account account;
  84. // 构造第一个参数 是 取钱的账户对象 第二参数 表示线程的名字
  85. public DrawMoney(Account account, String name){
  86. super(name);
  87. this.account = account;
  88. }
  89. @Override
  90. public void run() {
  91. //取钱就是在账户里面 减少余额
  92. // 调用账户对象 的 取钱方法
  93. // 小明线程 小红线程 操作的 账户是同一个
  94. // 在测试类创建 一个账户 传递进来
  95. account.drawMoney(100000); // 通过账户对象 取钱 (取的钱数)
  96. }
  97. }
  98. ---------------------
  99. package com.itheima.day12.teacher.lock;
  100. public class ThreadTest {
  101. public static void main(String[] args) {
  102. //创建一个账户对象 小明和小红 共享账户 卡号 余额
  103. Account account = new Account("ICBC-114",100000);
  104. //创建两个线程对象 分别代表小红 和 小明
  105. // 线程对象中传递 共享的账户 以及 线程名字
  106. new DrawMoney(account,"小明").start();
  107. new DrawMoney(account,"小红").start();
  108. }
  109. }

  6.线程通信

        1.什么是线程通信?

                当多个线程共同操作共享的资源时,线程间通过某种方式互相告知自己的状态,以相互协调,并避免无效的资源争夺。

        2.线程通信的常见模型(生产者与消费者模型)

                  生产者线程负责生产数据

                  消费者线程负责消费生产者生产的数据。

                   注意:生产者生产完数据应该等待自己,通知消费者消费;消费者消费完数据也应该等待自己,再通知生产者生产!

        3.Object类的等待和唤醒方法:

                

        注意事项:上述方法应该使用当前同步锁对象进行调用。

  1. package com.itheima.day12.teacher.wait_notify;
  2. import java.util.ArrayList;
  3. import java.util.List;
  4. /*
  5. 定义桌子类
  6. 定义一个存储包子的集合 存储包子
  7. 定义一个厨师生产包子的方法
  8. 定义一个吃货吃包子的方法
  9. */
  10. public class Desk {
  11. // 定义一个存储包子的集合 存储包子
  12. private List<String> list = new ArrayList<>();
  13. // 定义一个厨师生产包子的方法
  14. public synchronized void put() {
  15. try{
  16. //获取厨师线程对象的名称
  17. String name = Thread.currentThread().getName();
  18. //判断有没有包子
  19. if(list.size()==0){//没有包子 厨师要做包子
  20. list.add(name+"做的包子..");//生产包子 把包子放到集合中
  21. System.out.println("厨师:"+name+"正在做包子.....");
  22. //模拟做包子的时间
  23. Thread.sleep(2000);
  24. //等待和唤醒方法 一般采用共享资源进行调用
  25. //包子做完了
  26. this.notifyAll();//唤醒所有的吃货
  27. this.wait();//厨师进入休息 等待
  28. }else {
  29. // this.notifyAll();//唤醒所有的吃货
  30. this.wait();//厨师进入休息 等待 因为桌子上有包子了。
  31. }
  32. }catch (InterruptedException e){
  33. e.printStackTrace();
  34. }
  35. }
  36. //定义一个 吃货 吃包子的方法
  37. public synchronized void get() {
  38. try{
  39. //获取吃货的名称
  40. String name = Thread.currentThread().getName();
  41. //先判断有没有包子
  42. if(list.size()==1){
  43. //模拟吃包子
  44. System.out.println("吃货:"+name+"正在吃"+list.get(0));
  45. list.clear();//清空
  46. Thread.sleep(1500);
  47. //吃饭之后
  48. //唤醒厨师
  49. this.notifyAll();
  50. //吃货休息
  51. this.wait();
  52. }else {
  53. //唤醒厨师
  54. // this.notifyAll();
  55. //吃货休息
  56. this.wait();
  57. }
  58. }catch (Exception e){
  59. e.printStackTrace();
  60. }
  61. }
  62. }
  63. ---------------------
  64. package com.itheima.day12.teacher.wait_notify;
  65. public class QingFengBaoZiPu {
  66. public static void main(String[] args) {
  67. //开始准备吃包子
  68. System.out.println("进入庆丰包子铺 坐了下来 点餐");
  69. /*
  70. new Thread().start() 是开启一个新的线程
  71. new Thread(线程任务,线程名字).start()
  72. 给新的线程绑定一个线程任务和线程的名字
  73. new Thread(()->{},线程名字).start()
  74. 因为线程任务是 函数式接口 所以可以使用 lambda去表达
  75. ()->{
  76. //厨师要生产包子
  77. while(true){
  78. desk.put();
  79. }
  80. }
  81. 厨师生产包子 只要包子没有了就可以去生产 所以写了一个循环
  82. ()->{
  83. //吃货要次包子
  84. while(true){
  85. desk.get();
  86. }
  87. }
  88. 吃货吃包子 只要包子还有 就可以吃 所以也写了一个循环
  89. */
  90. //创建共享资源
  91. Desk desk = new Desk();
  92. //有三个厨师 线程
  93. new Thread(()->{
  94. //厨师要生产包子
  95. while(true){
  96. desk.put();
  97. }
  98. },"霍大厨").start();
  99. new Thread(()->{
  100. //厨师要生产包子
  101. while(true){
  102. desk.put();
  103. }
  104. },"雷大厨").start();
  105. new Thread(()->{
  106. //厨师要生产包子
  107. while(true){
  108. desk.put();
  109. }
  110. },"卧龙凤厨").start();
  111. //有两个吃货 线程
  112. new Thread(()->{
  113. //吃货要次包子
  114. while(true){
  115. desk.get();
  116. }
  117. },"乐吃货").start();
  118. new Thread(()->{
  119. //吃货要次包子
  120. while(true){
  121. desk.get();
  122. }
  123. },"李逵吃货").start();
  124. }
  125. }

7.线程池

        1.什么是线程池?        

                线程池就是一个可以复用线程的技术。

        2.如何创建线程池?

                方式一:使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池对象。

  1. package com.itheima.day12.teacher.threadpool;
  2. public class CuoZao implements Runnable{
  3. @Override
  4. public void run() {
  5. //任务是搓澡
  6. String name = Thread.currentThread().getName();
  7. System.out.println("号码为:"+name+"的师傅正在给客人搓澡=====盐搓---奶搓---醋搓--");
  8. //模拟搓澡时间
  9. try {
  10. //5秒搓一个
  11. Thread.sleep(5000);
  12. } catch (InterruptedException e) {
  13. e.printStackTrace();
  14. }
  15. }
  16. }
  17. --------------
  18. package com.itheima.day12.teacher.threadpool;
  19. import java.util.concurrent.*;
  20. public class Demo {
  21. public static void main(String[] args) throws InterruptedException {
  22. // 先构建一个 线程池对象
  23. ExecutorService pool = new ThreadPoolExecutor(
  24. 3,//核心线程数量
  25. 5,// 最大线程数量 = 核心线程数量+临时线程数量;
  26. 8,//临时存活时间 时间的数量
  27. TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉
  28. new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列 阻塞长度是4
  29. Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码
  30. new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最后 忙不过来 找外援
  31. // 线程池 澡堂
  32. // 核心线程数量 老板招聘的 三个搓澡师傅
  33. //执行搓澡任务
  34. CuoZao cz = new CuoZao();
  35. //来一个顾客 搓一个顾客
  36. //接客
  37. pool.execute(cz);//核心
  38. pool.execute(cz);//核心
  39. pool.execute(cz);//核心
  40. //三个客人
  41. pool.execute(cz);//第四个客人 先等待了 核心线程为他服务
  42. pool.execute(cz);//第五个客人
  43. pool.execute(cz);//第六个客人
  44. pool.execute(cz);//第七个客人
  45. // 7-3 = 4
  46. pool.execute(cz);//第八个客人 触发了 招聘临时工 阻塞队列4 一旦超出阻塞队列 就增派人手
  47. pool.execute(cz);//第九个客人 阻塞队列4 超出阻塞队列两个 增派两个人手
  48. // 第九个客人 已经有五个搓澡师傅 已经达到 最大线程数量
  49. pool.execute(cz);//第十个客人 阻塞队列满了 超出阻塞队列的 用两个人手 但是还少一个
  50. // 这个时候拒绝策略 -- 增派人手 main来处理。。。
  51. Thread.sleep(20000);//时间过了十一秒 没有新的任务 肯定有线程没有处理任务的 这种任务就会销毁
  52. System.out.println("至少空闲了12秒 已经有被销毁的线程了...销毁之后 ");
  53. pool.execute(cz);
  54. pool.execute(cz);
  55. pool.execute(cz);
  56. pool.execute(cz);
  57. pool.execute(cz);
  58. pool.shutdown();//都搓完了 把 澡堂关闭
  59. // pool.shutdownNow();//里面关闭 没搓完的任务回到队列中
  60. // 临时线程什么时候创建? 核心线程忙 + 任务队列满了。可以创建临时线程。
  61. // 如果临时线程也满了,触发了拒绝策略,
  62. // 可能1: 找主线程帮忙处理。
  63. // 可能2: 抛弃 说声对不起 异常
  64. // 可能3: 抛弃 什么也不说
  65. // 可能4: 抛弃对头 放入新的
  66. }
  67. }

submit方法:

        

  1. package com.itheima.day12.teacher.threadpool;
  2. import java.util.concurrent.Callable;
  3. public class Download implements Callable<String> {
  4. @Override
  5. public String call() throws Exception {
  6. System.out.println(Thread.currentThread().getName()+"正在下载程序.....");
  7. return "任务下载完成";
  8. }
  9. }
  10. --------------------
  11. package com.itheima.day12.teacher.threadpool;
  12. import java.util.concurrent.*;
  13. public class XunLei {
  14. public static void main(String[] args) throws ExecutionException, InterruptedException {
  15. ExecutorService pool = new ThreadPoolExecutor(
  16. 3,//核心线程数量
  17. 5,// 最大线程数量 = 核心线程数量+临时线程数量;
  18. 8,//临时存活时间 时间的数量
  19. TimeUnit.SECONDS,// 超过核心现场后 如果有线程超过8秒中没有被使用 就销毁掉
  20. new ArrayBlockingQueue<>(4),//指定任务队列 任务阻塞队列 阻塞长度是4
  21. Executors.defaultThreadFactory(),//用户创建线程对象的工程对象 固定代码
  22. new ThreadPoolExecutor.CallerRunsPolicy());//任务拒绝策略 四个 我选取最
  23. //线程池 处理 带返回值的任务
  24. Download d = new Download();//下载任务
  25. Future<String> f1 = pool.submit(d);
  26. pool.submit(d);
  27. pool.submit(d);
  28. pool.submit(d);
  29. pool.submit(d);
  30. System.out.println(f1.get());
  31. }
  32. }

                方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。

                线程池的注意事项: 

                        1、临时线程什么时候创建?

                                新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。

                        2、什么时候会开始拒绝新任务?

                                核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务。

       3.使用ExecutorService线程池对象的常用方法

                        void execute(Runnable command)

        4.新任务拒绝策略

        

        5.线程池处理Callable任务

        使用ExecutorService线程池对象的常用方法---Future<T> submit(Callable<T> task)

        6.Executors工具类实现线程池

                是一个线程池的工具类,提供了很多静态方法用于返回不同特点的线程池对象。

        注意 :这些方法的底层,都是通过线程池的实现类ThreadPoolExecutor创建的线程池对象。

        7.Executors使用可能存在的陷阱

                大型并发系统环境中使用Executors如果不注意可能会出现系统风险。

  1. package com.itheima.day12.teacher.threadpool;
  2. import java.util.concurrent.Callable;
  3. public class Download implements Callable<String> {
  4. @Override
  5. public String call() throws Exception {
  6. System.out.println(Thread.currentThread().getName()+"正在下载程序.....");
  7. return "任务下载完成";
  8. }
  9. }
  10. -------------------
  11. package com.itheima.day12.teacher.threadpool;
  12. import java.util.concurrent.*;
  13. public class XunLei2 {
  14. public static void main(String[] args) throws ExecutionException, InterruptedException {
  15. ExecutorService pool = Executors.newFixedThreadPool(3);
  16. //线程池 处理 带返回值的任务
  17. Download d = new Download();//下载任务
  18. Future<String> f1 = pool.submit(d);
  19. pool.submit(d);
  20. pool.submit(d);
  21. pool.submit(d);
  22. pool.submit(d);
  23. System.out.println(f1.get());
  24. }
  25. }

8.其它细节知识:并发、并行

        1.进程:

                正在运行的程序(软件)就是一个独立的进程。

                线程是属于进程的,一个进程中可以同时运行很多个线程。

                进程中的多个线程其实是并发和并行执行的。

        2.并发的含义

                进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。

        3.并行的理解

                在同一个时刻上,同时有多个线程在被CPU调度执行。

        4.多线程是怎么执行的?

                并发和并行同时进行的

9.其它细节知识:线程的生命周期   

        1.线程的6种状态           

        

       2.线程6中状态互相转换  

          

        

                             

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号