线程通信
多个线程因为在同一个进程中,所以互相通信比较容易
线程通信的经典模型:生产者与消费者问题
生产者负责生成商品,消费者负责消费商品,生产不能过剩(仍有数据未被消费时不能生产),消费不能没有(不能消费还没有生产的数据)
模拟案例:
两名消费者拥有一个共享账户,共享资源,三名生产者负责生产资源。
两名消费者去获取资源,资源存在就取出,不存在就等待,唤醒生产者继续生产资源。
生产者生产资源时,发现仍然存在资源就不继续生产,如果没有资源就生产,然后等待,唤醒消费者来消费
注意:
- 线程通信一定是多个线程操作同一个资源才需要进行通信
- 线程通信必须先保证线程安全,否则毫无意义,代码也会报错
线程通信的Object提供三种核心方法
- wait()方法:让当前线程进入等待状态,此方法必须由锁对象调用
- notify()方法:唤醒当前锁对象上等待状态的某个线程,此方法必须由锁对象调用
- notifyAll()方法:唤醒当前锁对象上等待状态的全部线程,此方法必须由锁对象调用
代码实现
账户类,定义了存钱和取钱的操作
代码语言:javascript复制package ThreadSafety;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//定义账户类
public class Account {
private int cardID;
private double Money;
public synchronized void DrawMoney(int m){
String name =Thread.currentThread().getName();
try {
//判断余额是否足够
if (Money >= m) {
//开始支付
System.out.println(name "用户执行取钱操作,余额充足,支付" m "元成功!");
Money -= m;
} else {
//余额不足
System.out.println(name "用户执行取钱操作,余额不足,支付失败!");
}
System.out.println(name "用户结束操作,余额" Money "元");
}catch (Exception e){
e.printStackTrace();
}finally {
//取完钱后,唤醒别人,等待自己
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//新定义的存钱类
public synchronized void SaveMoney(int m){
String name =Thread.currentThread().getName();
try {
//账户没钱的情况下才执行存钱操作
if (Money == 0) {
System.out.println(name "用户执行存钱操作,充入" m "元成功!");
Money = m;
} else {
System.out.println(name "用户执行存钱操作,余额充足,充值失败!");
}
System.out.println(name "用户结束操作,余额" Money "元");
}catch (Exception e){
e.printStackTrace();
}finally {
//取完钱后,唤醒别人,等待自己
this.notifyAll();
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public Account() {
}
public Account(int cardID, double money) {
this.cardID = cardID;
Money = money;
}
public int getCardID() {
return cardID;
}
public void setCardID(int cardID) {
this.cardID = cardID;
}
public double getMoney() {
return Money;
}
public void setMoney(double money) {
Money = money;
}
}
线程类:分别规定了存钱线程和取钱线程
代码语言:javascript复制package ThreadSafety;
//线程类:将存钱行为看作是一条单独的线程创建
public class SaveThread extends Thread{
//定义一个成员变量,接收账户对象
private Account acc;
public SaveThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run() {
//执行存钱操作,每个用户不断尝试存1000元
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acc.SaveMoney(1000);
}
}
}
代码语言:javascript复制package ThreadSafety;
//线程类:将取钱行为看作是一条单独的线程创建
public class DrawThread extends Thread{
//定义一个成员变量,接收账户对象
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run() {
//执行取钱操作,每个用户不断尝试取1000元
while (true) {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
acc.DrawMoney(1000);
}
}
}
主线程
代码语言:javascript复制package ThreadSafety;
//模拟经典案例:生产者与消费者模型
public class Demo2 {
public static void main(String[] args) {
//创建共享账户
Account acc=new Account(111,0);
//创建两个消费者对象
Thread consumer1=new DrawThread(acc,"consumer1");
consumer1.start();
Thread consumer2=new DrawThread(acc,"consumer2");
consumer2.start();
//创建三个生产者
Thread producer1=new SaveThread(acc,"producer1");
producer1.start();
Thread producer2=new SaveThread(acc,"producer2");
producer2.start();
Thread producer3=new SaveThread(acc,"producer3");
producer3.start();
}
}
运行结果
代码语言:javascript复制producer2用户执行存钱操作,充入1000元成功!
producer2用户结束操作,余额1000.0元
consumer2用户执行取钱操作,余额充足,支付1000元成功!
consumer2用户结束操作,余额0.0元
consumer1用户执行取钱操作,余额不足,支付失败!
consumer1用户结束操作,余额0.0元
producer3用户执行存钱操作,充入1000元成功!
producer3用户结束操作,余额1000.0元
producer1用户执行存钱操作,余额充足,充值失败!
producer1用户结束操作,余额1000.0元
consumer2用户执行取钱操作,余额充足,支付1000元成功!
consumer2用户结束操作,余额0.0元
producer2用户执行存钱操作,充入1000元成功!
producer2用户结束操作,余额1000.0元
producer3用户执行存钱操作,余额充足,充值失败!
producer3用户结束操作,余额1000.0元
consumer1用户执行取钱操作,余额充足,支付1000元成功!
consumer1用户结束操作,余额0.0元
······
线程状态
线程状态 | 导致状态发生条件 |
---|---|
NEW(新建) | 线程刚刚被创建,但是尚未启动(没有调用start()方法)。只有线程对象,没有线程特征 |
Runnable(可运行的) | 线程可以在Java虚拟机中运行的状态,可能正在运行自己的代码,也可能没有,取决于操作系统处理器。调用了start()方法。 |
Blocked(锁阻塞) | 当一个线程试图获取一个对象锁,而该对象锁被其他的线程锁持有,则该线程进入Blocked状态,当该线程持有锁时,状态将改变为Runnable |
Waiting(无限等待) | 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态,进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify()方法或notifyAll()方法才能将线程唤醒 |
Timed Waiting(计时等待) | 同Waiting()状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态,这一状态将一致保持到超时期满或者接收到唤醒通知,带有超时参数的常用方法有:Thread.sleep,Object.wait |
Terminated(被终止) | 因为run()方法正常退出而死亡,或者因为没有捕获的异常终止了run()方法而死亡 |
注意:
可运行状态还可以被细分为两个状态:就绪状态和运行状态,就绪状态只是成功开启线程还没有真正运行,运行状态表示开始正常执行
sleep()和wait()区别在于sleep()休眠后不释放当前锁对象,所以在当前线程苏醒后可以直接继续当前锁对象内容,但是wait()表示释放当前锁对象,所以即便在苏醒后,也需要与其他线程争抢当前锁对象,成功抢到则可以执行,否则又变为锁阻塞状态