线程通信的前提是:要保证线程安全(加锁)
锁的作用: 就是让线程按找顺序(不分先后,谁先抢到锁,谁先走)进行 线程依次走,不会走到一半,另一个线程突然进来
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;
}
@Override
public 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();
}
}
}
}