线程通信的前提是:要保证线程安全(加锁)
锁的作用: 就是让线程按找顺序(不分先后,谁先抢到锁,谁先走)进行 线程依次走,不会走到一半,另一个线程突然进来
image.png
image.png
Object类的等待和唤醒方法: notify是通知的意思
image.png

流程:调用wait(假设岳父先抢到锁该线程先走,然后wait之后该线程就会等待,让其他线程先进来)方法,进入等待,让出CPU,然后会把锁让出去,给其他两个线程(刚刚唤醒了) 亲爹,干爹

// 锁是可以跨方法的,如果岳父抢到锁,那么亲爹干爹,小明小红,都会等待,然后 存完钱,进行wait,让出cpu,把锁让出去,把锁交给别的线程竞争(锁释放),4个人抢锁,账户里面也有钱了, 哪天如果小明小红抢到锁,就取钱,(取钱的话,如果有钱,就取走,并更新余额,然后将自己等待(敲晕),用notifyAll唤醒其他对象, (如果没钱,就直接敲晕,等待)唤醒别的线程,让别的线程抢锁,最终就是存钱取钱,这样一存一取,就是线程间的通信)
image.png
image.png

  1. package com.itheima.d4_thread_communication;
  2. public class ThreadDemo {
  3. public static void main(String[] args) {
  4. // 目标: 了解线程通信的流程
  5. // 使用3个爸爸存钱 2个孩子取钱,模拟线程通信思想(一存 一取)
  6. // 1. 创建账户对象 代表5个人公用操作的账户
  7. Account acc = new Account("ICBC-112",0);
  8. // 2. 创建2个取钱线程类的代表:小红和小明
  9. new DrawThread(acc, "小明"); // 创建一个取钱线程类对象,首先要创建取钱线程类
  10. new DrawThread(acc,"小红");
  11. // 3. 创建3个存钱线程代表:亲爹,干爹,岳父
  12. new DepositThread(acc,"亲爹").start();
  13. new DepositThread(acc,"干爹").start();
  14. new DepositThread(acc,"岳父").start();
  15. }
  16. }
  1. package com.itheima.d4_thread_communication;
  2. import java.util.concurrent.locks.Lock;
  3. import java.util.concurrent.locks.ReentrantLock;
  4. public class Account {
  5. private String cardId;
  6. private double money; // 账户余额
  7. public Account() {
  8. }
  9. public Account(String cardId, double money) {
  10. this.cardId = cardId;
  11. this.money = money;
  12. }
  13. public String getCardId() {
  14. return cardId;
  15. }
  16. public void setCardId(String cardId) {
  17. this.cardId = cardId;
  18. }
  19. public double getMoney() {
  20. return money;
  21. }
  22. public void setMoney(double money) {
  23. this.money = money;
  24. }
  25. /**
  26. * 小明,小红来取钱, 两个人取钱要注意线程安全,线程依次走,不会走到一把,另一个线程突然进来
  27. * @param money 取钱的金额
  28. */
  29. // 线程通信的前提是:要保证线程安全
  30. public synchronized void drawMoney(double money) {
  31. // 先要获取当前线程的名称,是哪个线程进来了
  32. try {
  33. String name = Thread.currentThread().getName();
  34. if (this.money >= money){
  35. // 钱够:可取
  36. this.money -= money; // 更新当前余额
  37. System.out.println(name + "来取钱" + money + "成功!余额是:" + this.money);
  38. // 取完钱之后就代表没钱了,应该先 唤醒别人,等待(打晕)自己
  39. this.notifyAll();
  40. this.wait(); // 没有钱可取,就把自己打晕(等待,等待,可以把锁释放,让另一个线程进来,直到另一个线程调用notify()方法或者notifyAll)
  41. }else {
  42. // 钱不够,不可取
  43. // 唤醒别人等待自己
  44. // 要实现唤醒方法(notify),应该使用当前同步锁对象进行调用
  45. this.notifyAll(); // 唤醒所有线程
  46. // 实例方法,this表示当前锁对象
  47. this.wait(); // 锁对象,让当前线程进入等待,
  48. // 先唤醒线程,然后再把自己等待(打晕),然后别的进程才能进来u
  49. }
  50. } catch ( Exception e) {
  51. e.printStackTrace();
  52. }
  53. }
  54. /**
  55. * 亲爹,干爹,岳父三个人来这里存钱,要保证线程安全,加锁
  56. * @param money
  57. */
  58. public synchronized void depositMoney(double money) {
  59. try {
  60. String name = Thread.currentThread().getName();
  61. if (this.money == 0){ // 没钱了,就存钱
  62. this.money += money;
  63. System.out.println(name + "存钱" + money + "成功!存钱后余额是:" + this.money);
  64. // 有钱了: 唤醒别人,等待自己(一定是要先唤醒别人,再让自己等待,
  65. // 如果先等待,这个线程就不会进行,唤醒功能将不能执行了
  66. this.notifyAll(); // 唤醒所有线程
  67. this.wait(); // 锁对象,让当前线程进入等待!
  68. }else {
  69. // 有钱了,不存钱
  70. this.notifyAll(); // 唤醒所有线程
  71. this.wait(); // 锁对象,让当前线程进入等待!
  72. }
  73. } catch ( Exception e) {
  74. e.printStackTrace();
  75. }
  76. }
  77. }
  1. package com.itheima.d4_thread_communication;
  2. /**
  3. * 取钱线程类 draw 是获取的意思
  4. * 记得有时候看导包的路径,别倒错了,会有错误信息
  5. */
  6. public class DrawThread extends Thread{
  7. private Account acc;
  8. public DrawThread() {
  9. }
  10. public DrawThread(Account acc,String name) {
  11. super(name); // 用有参构造器传入的name赋值给父类线程类的getname(相当于为线程命了名字)
  12. this.acc = acc;
  13. }
  14. @Override
  15. public void run() {
  16. // 小红和小明都在这个run方法的线程(子线程)这里取钱
  17. // 调用取钱的方法
  18. // 定义一个死循环,可以模拟一直取钱,避免取钱太快,让线程休眠三秒
  19. while (true) {
  20. acc.drawMoney(100000);
  21. // 让线程睡3秒,避免太快取钱
  22. try {
  23. Thread.sleep(3000);
  24. } catch (Exception e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }
  29. }

存钱线程和取钱线程自己定义,定义后,在主线程main方法中创建线程对象,即可实现多线程

package com.itheima.d4_thread_communication;

/**
 * 存钱线程类
 *   记得有时候看导包的路径,别倒错了,会有错误信息
 */
public class DepositThread extends Thread{
    private Account acc;

    public DepositThread() {
    }

    public DepositThread(Account acc, String name) {
        super(name);  // 用有参构造器传入的name赋值给父类线程类的getname(相当于为线程命了名字)
        this.acc = acc;
    }

    @Override
    public void run() {
        // 亲爹,干爹,岳父都在这个run方法的线程(子线程)这里存钱
        // 调用存钱的方法

        // 定义一个死循环,可以模拟一直存钱,避免取钱太快,让线程休眠三秒
        while (true) {
            acc.depositMoney(100000);
            // 让线程睡3秒,避免太快取钱
            try {
                Thread.sleep(3000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

    }
}