线程通信的前提是:要保证线程安全(加锁)
锁的作用: 就是让线程按找顺序(不分先后,谁先抢到锁,谁先走)进行 线程依次走,不会走到一半,另一个线程突然进来

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

package com.itheima.d4_thread_communication;public class ThreadDemo {public static void main(String[] args) {// 目标: 了解线程通信的流程// 使用3个爸爸存钱 2个孩子取钱,模拟线程通信思想(一存 一取)// 1. 创建账户对象 代表5个人公用操作的账户Account acc = new Account("ICBC-112",0);// 2. 创建2个取钱线程类的代表:小红和小明new DrawThread(acc, "小明"); // 创建一个取钱线程类对象,首先要创建取钱线程类new DrawThread(acc,"小红");// 3. 创建3个存钱线程代表:亲爹,干爹,岳父new DepositThread(acc,"亲爹").start();new DepositThread(acc,"干爹").start();new DepositThread(acc,"岳父").start();}}
package com.itheima.d4_thread_communication;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;private double money; // 账户余额public Account() {}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}/*** 小明,小红来取钱, 两个人取钱要注意线程安全,线程依次走,不会走到一把,另一个线程突然进来* @param money 取钱的金额*/// 线程通信的前提是:要保证线程安全public synchronized void drawMoney(double money) {// 先要获取当前线程的名称,是哪个线程进来了try {String name = Thread.currentThread().getName();if (this.money >= money){// 钱够:可取this.money -= money; // 更新当前余额System.out.println(name + "来取钱" + money + "成功!余额是:" + this.money);// 取完钱之后就代表没钱了,应该先 唤醒别人,等待(打晕)自己this.notifyAll();this.wait(); // 没有钱可取,就把自己打晕(等待,等待,可以把锁释放,让另一个线程进来,直到另一个线程调用notify()方法或者notifyAll)}else {// 钱不够,不可取// 唤醒别人等待自己// 要实现唤醒方法(notify),应该使用当前同步锁对象进行调用this.notifyAll(); // 唤醒所有线程// 实例方法,this表示当前锁对象this.wait(); // 锁对象,让当前线程进入等待,// 先唤醒线程,然后再把自己等待(打晕),然后别的进程才能进来u}} catch ( Exception e) {e.printStackTrace();}}/*** 亲爹,干爹,岳父三个人来这里存钱,要保证线程安全,加锁* @param money*/public synchronized void depositMoney(double money) {try {String name = Thread.currentThread().getName();if (this.money == 0){ // 没钱了,就存钱this.money += money;System.out.println(name + "存钱" + money + "成功!存钱后余额是:" + this.money);// 有钱了: 唤醒别人,等待自己(一定是要先唤醒别人,再让自己等待,// 如果先等待,这个线程就不会进行,唤醒功能将不能执行了this.notifyAll(); // 唤醒所有线程this.wait(); // 锁对象,让当前线程进入等待!}else {// 有钱了,不存钱this.notifyAll(); // 唤醒所有线程this.wait(); // 锁对象,让当前线程进入等待!}} catch ( Exception e) {e.printStackTrace();}}}
package com.itheima.d4_thread_communication;/*** 取钱线程类 draw 是获取的意思* 记得有时候看导包的路径,别倒错了,会有错误信息*/public class DrawThread extends Thread{private Account acc;public DrawThread() {}public DrawThread(Account acc,String name) {super(name); // 用有参构造器传入的name赋值给父类线程类的getname(相当于为线程命了名字)this.acc = acc;}@Overridepublic void run() {// 小红和小明都在这个run方法的线程(子线程)这里取钱// 调用取钱的方法// 定义一个死循环,可以模拟一直取钱,避免取钱太快,让线程休眠三秒while (true) {acc.drawMoney(100000);// 让线程睡3秒,避免太快取钱try {Thread.sleep(3000);} catch (Exception e) {e.printStackTrace();}}}}
存钱线程和取钱线程自己定义,定义后,在主线程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();
}
}
}
}
