线程通信
线程通信,就是线程之间的通信。
生产者和消费者问题是典型的线程通信的问题:
- 假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。
- 如果仓库中没有产品,则生产者将生产产品并放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。
- 如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。
这是一个典型的线程通信问题,生产者和消费者共享同一个资源,并且生产者和消费者之间互为依赖,互为条件。
- 对于生产者,没有生产产品之前,要通知消费者等待;生产了产品之后,又需要马上通知消费者消费。
- 对于消费者,在消费之后,要通知生产者需要生产新的产品供再次消费。
在这个问题中,仅有synchronized关键字就不够了,synchronized能阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现不同线程之间的消息传递。
Java提供了几种方法来解决线程之间的通信问题
方法名 | 作用 |
---|---|
wait() | 表示线程一直等待,直到其他线程通知,与sleep不同的是,该方法会释放锁 |
wait(long timeout) | 指定等待的毫秒数 |
notify() | 唤醒一个处于等待状态的线程 |
notifyAll() | 唤醒同一个对象上所有调用wait()方法的线程,优先级别高的线程优先调度 |
注意:以上方法均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出IllegalMonitorStateException异常。
管程法
就是在生产者和消费者之间用一个缓冲区来存放产品,代码实现如下:
//生产者消费者问题 管程法
public class PCTest01 {
public static void main(String[] args) {
//创建一个容器
Container container = new Container();
//两个线程模拟生产者和消费者,它们操作同一个容器
new Producer(container).start();
new Consumer(container).start();
}
}
//生产者
class Producer extends Thread {
Container container;
public Producer(Container container) {
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟生产延时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//放入缓冲区
System.out.println("生产了第" + container.push(new Product(i)) + "个产品");
}
}
}
//消费者
class Consumer extends Thread {
Container container;
public Consumer(Container container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟消费延时
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("消费了第" + container.pop().id + "个产品-->");
}
}
}
//产品
class Product {
int id; //产品编号
public Product(int id) {
this.id = id;
}
}
//缓冲区
class Container {
//容器大小
Product[] products = new Product[10];
//容器计数器 记录数组下标
int count = -1;
//生产者放入产品
public synchronized int push(Product product) {
//如果容器满了 等待消费者消费
if (count == (products.length - 1)) {
//生产者等待消费者消费
try {
this.wait(); //生产者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//容器没满 继续生产并放入缓冲区
products[++count] = product;
//通知消费者消费
this.notifyAll(); //唤醒消费者线程
//返回产品编号
return product.id;
}
//消费者消费产品
public synchronized Product pop() {
//判断能否消费
if (count == -1) { //缓冲区中没有 不能消费
//消费者等待生产者生产
try {
this.wait(); //消费者线程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//缓冲区中可以消费
Product product = products[count--];
//通知生产者生产
this.notifyAll(); //唤醒生产者线程
//返回消费产品
return product;
}
}
这个方法就是采用了中间媒介(容器对象)从而实现两个线程之间的通信。
信号灯法
信号灯法一般通过标志位来解决问题,代码如下:
//生产者消费者问题:信号灯法
public class PCTest02 {
public static void main(String[] args) {
//创建产品对象
Product product = new Product();
//模拟生产者消费者
new Producer(product).start();
new Consumer(product).start();
}
}
//生产者
class Producer extends Thread {
Product product;
public Producer(Product product){
this.product = product;
}
//生产
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟生产延时
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//生产产品
this.product.production(i);
}
}
}
//消费者
class Consumer extends Thread {
Product product;
public Consumer(Product product){
this.product = product;
}
//消费
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
//模拟消费延时
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//消费
this.product.consumption();
}
}
}
//产品
class Product {
int id; //产品编号
//设置标志位 true时生产产品 false时消费产品
boolean flag = true;
//生产
public synchronized void production(int id) {
//判断标志位
if (!flag) {
//生产者等待生产
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("生产了第" + id + "个产品");
//通知消费者消费
this.notifyAll();//唤醒消费者线程
//改变id和标志位
this.id = id;
this.flag = !this.flag;
}
//消费
public synchronized void consumption() {
//判断标志位
if (flag) {
//消费者等待消费
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费了第" + this.id + "个产品");
//通知生产者生产
this.notifyAll();//唤醒生产者线程
//改变标志位
this.flag = !this.flag;
}
}
这种方式就是通过一个标志位,根据标志位的状态来执行不同的线程,从而实现线程之间的通信。
当然线程之间的通信应该还有很多种,博主暂时只了解到这两种,如有其它方式,欢迎留言交流。