实验内容
编写java程序,使用Thread 类或Runnable接口创建线程,实现生产者消费者问题。
要求:
1)分析指出问题中的临界资源,并在代码中标注出临界区;
2)能够采用synchronized、wait()和notify()(或notifyall())等方法实现线程的互斥与同步;
3)各线程在生产或者消费时需要有输出标识,格式为“生产/消费线程 + i + 生产/消费了一个商品,目前商品总数为n”,各线程完成生产或消费后,sleep()函数使线程进入阻塞,通过改变sleep()函数的参数,改变生产与消费的执行顺序以及频率,并讲结果截图附简要说明原因。
基础知识简介
在java中,wait()、notify()方法的关系如同开关一样,一个方法让线程进入阻塞状态,一个方法唤醒等待中的线程,让其进行运作。这样的的等待-唤醒的方式,也保护了临界资源,防止并发产生问题。但和sleep()不一样的是,使用wait()以及notify()时,必须先获得锁!只能在synchronized锁范围中进行使用!
- wait():使调用该方法的线程释放共享资源锁(sleep方法调用后不会释放锁),然后从运行状态退出,进入等待队列,直到被再次唤醒。
- notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。
- notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。
希望对该知识有更深入了解的可以参考这篇文章:彻底搞懂Java的等待-通知(wait-notify)机制
实验过程
问题中的临界资源为生产者生产的资源,临界区为存放资源的区域。
在过程中,使用synchronized对线程加锁,保护临界资源在其他线程访问的时候不会被读取,当临界区中已经无法存放临界资源的时候,使用wait()方法,释放锁,直到消费者线程消费资源后唤醒生产者线程。在完成对资源的生产后,使用notify() 方法,不会立即释放锁,当执行完同步代码块就会释放对象的锁。释放后便于消费者线程对资源进行操作。消费者线程中的处理同步互斥问题的操作也同理。各线程完成生产或消费后,sleep()函数使线程进入阻塞,通过改变sleep()函数的参数,能够改变生产与消费的执行顺序以及频率。当生产者线程sleep(1000)时,生产者线程会在生产资源后阻塞1秒钟,然后继续执行,同时当消费者线程sleep(5000)时,消费者线程会在消费资源后阻塞5秒钟,然后继续执行。
代码
public class ProducerConsumer{
private static final int BUFFER_SIZE = 10;//临界区
private static int bufferCount = 0;//临界资源
private static final Object bufferLock = new Object();
public static class Producer implements Runnable{
@Override
public void run() {
while (true) {
synchronized (bufferLock) {//对对象进行加锁
while (bufferCount == BUFFER_SIZE) {//临界区满
try {
bufferLock.wait();//释放锁,进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
bufferCount ++;
System.out.println("生产线程" + Thread.currentThread().getId() + "生产了一个商品,目前商品总数为" + bufferCount);
bufferLock.notifyAll();//完成代码后,释放锁,让下一线程对资源操作
}
try {
Thread.sleep(1000);//设置线程阻塞时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static class Consumer implements Runnable{
@Override
public void run() {
while (true) {
synchronized (bufferLock) {
while (bufferCount == 0) {//临界区无资源消费
try {
bufferLock.wait();//释放锁,进入等待状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
bufferCount--;
System.out.println("消费线程" + Thread.currentThread().getId() + "消费了一个商品,目前商品总数为" + bufferCount);
bufferLock.notifyAll();
}
try {
Thread.sleep(5000);//设置线程阻塞时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
Thread producer = new Thread(new Producer());
Thread consumer = new Thread(new Consumer());
producer.start();
consumer.start();
}
}