线程间通讯概述
线程间的通讯技术就是通过等待和唤醒机制,来实现多个线程协同操作完成某一项任务,例如经典的生产者和消费者案例。
什么是线程通讯?
在多线程程序中,某个线程进入到"等待状态"时,必须有其他线程来唤醒处于"等待状态"的线程
线程通讯需要使用的API方法:
等待方法
特殊之处:会释放对象锁
java
wait() //无限等待 (只能其他线程唤醒)
wait(long 毫秒) //计时等待 (时间到了自动唤醒)
唤醒方法
特殊之处:不会释放对象锁
java
notify() //唤醒处于"等待状态"的任意一个线程
nitityAll() //唤醒处于"等待状态"的所有线程
使用细节:
wait()方法和notify()方法,都必须绑定在对象锁上
java
Object lock = new Object(); //对象锁
lock.wait();
lock.notify();
注意:
1.等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中使用)。
2.等待和唤醒方法应该使用相同的锁对象调用。
等待和唤醒的方法调用有什么注意事项?
- 等待的方法会释放锁,唤醒的方法不会释放锁
- 等待和唤醒的方法,都要使用锁对象调用(需要在同步代码块中使用)。
- 等待和唤醒方法应该使用相同的锁对象调用。
等待唤醒代码实现
1 线程进入无限等待
java
//线程进入无限等待
//注意:进入无限等待需要使用锁在同步代码中调用wait方法。
public class Test1 {
public static void main(String[] args) {
// new Thread(new Runnable() {
// @Override
// public void run() {}
// });
//使用Lambda表达式
new Thread(()->{
synchronized ("对象锁"){
try {
System.out.println("将进入无线等待状态~");//将进入无线等待状态~
"对象锁".wait();//无线等待
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("继续执行...");//只有没有唤醒就不会输出
}
}).start();
}
}
运行结果:
将进入无线等待状态~
2 线程进入无限等待后被唤醒
java
//注意:等待和唤醒是两个或多个线程之间实现的。进入无限等待的线程是不会自动唤醒,只能通过其他线程来唤醒。
public class Test2 {
public static void main(String[] args) {
//创建线程任务对象
Runnable task = new Runnable() {//匿名内部类
Object lock = new Object();//对象锁
boolean flag = true;//开关键
@Override
public void run() {
synchronized (lock) {
if (flag) {
try {
System.out.println(Thread.currentThread().getName()+"即将进入无线等待状态");//Thread-0即将进入无线等待状态
flag = false;
lock.wait();//等待,同时释放掉对象锁,Thread-0线程在这里等待
System.out.println(Thread.currentThread().getName()+"已经唤醒了");//Thread-0已经唤醒了
System.out.println("程序继续执行。。。");//程序继续执行。。。
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("即将唤醒处于等待状态的线程。。。");//即将唤醒处于等待状态的线程。。。
try {
Thread.sleep(1000);//休眠1秒,1秒后唤醒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
lock.notify();//唤醒处于等待状态的线程
System.out.println(Thread.currentThread().getName()+"在执行");//Thread-1在执行
}
}
}
};
//创建两个线程
new Thread(task).start();
new Thread(task).start();
}
}
运行结果:
Thread-0即将进入无线等待状态
即将唤醒处于等待状态的线程。。。
Thread-1在执行
Thread-0已经唤醒了
程序继续执行。。。
3 线程进入计时等待并唤醒
java
//注意:进入计时等待的线程,时间结束前可以被其他线程唤醒。时间结束后会自动唤醒
public class Test3 {
public static void main(String[] args) {
new Thread(()->{
synchronized("对象锁"){
System.out.println("即将进入倒计时等待状态,休息3秒后,自动唤醒~~~");
try {
"对象锁".wait(3000);//待定3秒后,自动唤醒,继续执行后面代码
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("程序继续执行~");
}
}).start();
}
}
运行结果:
即将进入倒计时等待状态,休息3秒后,自动唤醒~~~
程序继续执行~
注:
java
代码中的
lock.notify();
表示唤醒处于和notify使用同一个对象锁上的,且处于"等待状态"的任意一个线程
生产者消费者案例【经典】

java
//共享资源
public class Resource {
public static int num = 0;
//共享的对象锁
public static final String LOCK = "对象锁";
}
//生产者线程
public class ProdecerTask implements Runnable{
@Override
public void run() {
//必须保证生产者和消费者是同一个对象锁
synchronized (Resource.LOCK) {
//判断桌子上有没有食物
if (Resource.num == 0) {
//没有:生产食物
System.out.println(Thread.currentThread().getName() + "发现没有食物,开始生产~~");
Resource.num = 1;
//唤醒:消费者来吃
Resource.LOCK.notify();
} else {
//有:进入等待
System.out.println(Thread.currentThread().getName() + "发现有食物,等待中~~");
try {
Resource.LOCK.wait();//释放掉锁
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
}
//消费者
public class ConsumerTask implements Runnable{
//必须保证生产者和消费者是同一个对象锁
@Override
public void run() {
//同步代码块
synchronized (Resource.LOCK) {
//判断有没有吃的
if (Resource.num == 0) {
//没有
try {
System.out.println(Thread.currentThread().getName() + "没有发现食物,等待中~~~");
Resource.LOCK.wait();//进入无限等待中直到被唤醒(会释放对象锁)
System.out.println(Thread.currentThread().getName()+"等待完成,开吃");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
} else {
//有食物
System.out.println(Thread.currentThread().getName() + "消费者发现食物,开始消费"+Resource.num);
Resource.num--;
//消费完了要唤醒生产者可以继续生产
Resource.LOCK.notify();
}
}
}
}
public class Test1 {
public static void main(String[] args) {
new Thread(new ConsumerTask(), "消费者").start();
new Thread(new ProdecerTask(), "生产者").start();
}
}
运行结果:
消费者没有发现食物,等待中~~~
生产者发现没有食物,开始生产~~
消费者等待完成,开吃