与网络通信等进程间通信方式不一样,线程间通信又称为进程内通信,多个线程实现互 斥访问共享资源时会互相发送信号或等待信号,比如线程等待数据到来的通知,线程收 到变量改变的信号等。
单线程间通信
假设我们现在需要两个线程,一个负责生产数据,一个负责消费数据,理想状态下我们 希望一边生产一边消费。
/**
* 单线程环境下 实现线程间通信
*/
public class ProduceConsumerVersion2 {
private final Object LOCK = new Object();
private int i = 0;
//标记数据是否生产了数据
private boolean isProduce = false;
public void produce(){
synchronized (LOCK){
//如果生产者生产了数据等待消费者消费数据,否者生产数据
if (isProduce){
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
i++; //生产数据
System.out.println("p -> "+ i);
isProduce = true;
LOCK.notify();
}
}
}
public void consumer(){
synchronized (LOCK){
//如果生产者生产了数据就去消费,否者等待生产
if (isProduce){
System.out.println("c -> "+ i);
isProduce = false;
LOCK.notify();
}else {
try {
LOCK.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
ProduceConsumerVersion2 version = new ProduceConsumerVersion2();
new Thread(()->{
while (true){
version.produce();
}
}).start();
new Thread(()->{
while (true){
version.consumer();
}
}).start();
}
}
运行结果:
p -> 140604
c -> 140604
p -> 140605
c -> 140605
p -> 140606
c -> 140606
p -> 140607
c -> 140607
p -> 140608
c -> 140608
结果正是我们希望的结果,生成一个消费一个。
多线程间通信
但是只是在单线程环境下,如同时有多个消费者,多个生产者这样写就不行。接下来我们把main方法改成这样:
ProduceConsumerVersion version = new ProduceConsumerVersion();
Stream.of("p1","p2").forEach(p -> new Thread(()->{
while (true){
version.produce();
}
},p).start());
Stream.of("c1","c2","c3","c4").forEach(c -> new Thread(()->{
while (true){
version.consumer();
}
},c).start());
}
运行结果:
通过jstack发现这几条线程都处于等待状态
这是因为在唤醒线程的时候使用的是notify()这个方法,当线程在调用notify()的时候发现多条线程处于waiting状态,他不知道该唤醒谁。所以使用notifyAll()唤醒当前全部处于waiting状态的线程便可以解决此问题。
练习案例
/**
* 生产者:生产馒头
* 消费者:消费馒头
* 缓冲区:存放馒头 10个
* 达到效果:生产者生产馒头放到缓冲区里,当缓冲区满了,生产者停止生产,消费者开始消费馒头,当缓冲区有位置,生产者继续生产
*/
public class ProduceConsumerVersion4 {
public static void main(String[] args) {
Box box = new Box();
new Thread(new Producer(box)).start();
new Thread(new Consumer(box)).start();
}
}
class ManTou{
private int id;
public ManTou(int id) {
this.id = id;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
}
class Box{
//盒子容量 10个
private ManTou[] manTous = new ManTou[10];
//盒子最后一个馒头的坐标
private int index = 0;
public synchronized void push(ManTou manTou){
//判断馒头合作是否满了 , 满了停止,否则放馒头
if(this.index == this.manTous.length){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
manTous[index++] = manTou;
System.out.println("Producer 生产了一个馒头 : "+manTou.getId());
System.out.println("生产一个馒头后:盒子还有"+this.index+"个馒头");
//唤醒另一个线程去取馒头
this.notify();
}
}
public synchronized void pop(){
//判断馒头合作是否为空,是空停止,否则取馒头
if (this.index == 0 ){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
System.out.println("Consumer 消费了一个馒头 : "+manTous[--index].getId());
System.out.println("消费一个馒头后:盒子还有"+this.index+"个馒头");
this.manTous[index] = null;
//通知那边可以放馒头了
this.notify();
}
}
}
class Producer implements Runnable{
private Box box;
private int index = 1;
public Producer(Box box) {
this.box = box;
}
@Override
public void run() {
while(true){
box.push(new ManTou(index++));
}
}
}
class Consumer implements Runnable{
private Box box;
public Consumer(Box box) {
this.box = box;
}
@Override
public void run() {
while(true){
box.pop();
}
}
}
wait 和 sleep 的区别
- sleep 是属于 Thread 的方法,wait 属于 Object;
- sleep 方法不需要被唤醒,wait 需要。
- sleep 方法不需要 synchronized,wait 需要;
- sleep 不会释放锁,wait 会释放锁并将线程加入 wait 队列;