读这篇文章之前,首先要弄明白java对象的两个方法,wait和notify或者notifyAll,那弄懂这两个方法又要知道一个概念,java中Object类有个对象锁,所有的对象都继承自Object类,因此每个对象都有个锁,而且java中的对象锁在同一时刻只能由一个线程持有,这是java在多线程编程中实现互斥的基础。 那一个线程如何获得一个对象的锁呢?根据JDK源码,一个线程可以有3种方法获得对象锁,第一种是执行该对象实例的synchronized方法,第二种是执行对象中的synchronized代码块,第三种是执行类(非实例)的静态synchronized方法,最后需要注意的是一个对象的锁在同一个时间只能由一个线程持有,我们今天将要讨论的3个方法就是在线程获得了对象锁的情况下执行的。
先说一下wait方法,该方法的作用就是暂停当前线程,并释放占有的对象锁(当前线程一定要占有对象锁,否则会抛出异常),区别于sleep方法,sleep方法是Thread类的方法,也是暂停当前线程,但并不会释放锁(如果占有的话)。
接下来说notifyAll方法,作用是唤醒所有等待当前线程持有的对象锁的线程,同样的道理,调用notifyAll方法的线程必须持有对象锁,对象锁并不会在调用完notifyAll方法之后立刻被释放,需要等待当前线程(或者持有对象锁的方法,代码块)结束后才会被释放,锁被释放后,其它被唤醒的线程就会随机竞争,先得到该对象锁的线程将会进入执行, notify和notifyAll的区别就是notify只会随机唤醒一个线程,notifyAll会唤醒所有调用wait的线程。
以下是运用wait和notify实现的生产者消费者的代码
package cn.javass.web.controller;
import java.util.Stack;
public class TestThead2
{
public static void main(String[] args)
{
ProductStack ps = new ProductStack();
Thread p = new Thread(new Producer(ps));
Thread c = new Thread(new Consumer(ps));
p.start();
c.start();
}
}
//模拟一个产品,包括一个属性id
class Product
{
String id;
Product(String id)
{
this.id = id;
}
}
//仓库类
class ProductStack
{
//仓库有个最大存储容量
int index = 6;
//仓库中货架,通过java.util.Stack来实现,主要用到push和pop两个方法
Stack<Product> products = new Stack<Product>();
//生产产品,并放入货架
public synchronized void push(Product pc)
{
//如果货架上的产品已经满了,则停止生产
if (products.size() == index)
{
try
{
//一旦调用wait方法,当前线程将暂停,同时释放ProductStack的对象锁,
//直到其它拥有该对象锁的线程调用notify或者notifyAll,当前线程才会
//尝试再次获取ProductStack的对象锁,并从此处继续执行
wait();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//生产一个产品
products.push(pc);
System.out.println("生产了 " + pc.id);
//此时货架肯定不是空的,通知消费者消费, 之前讲到,notify和notifyAll的区别是
//前者随机唤醒一个等待ProductStack对象锁的线程,而后者是唤醒所有线程,此时当前
//进程中只有pop一个线程在等待对象锁,因此此处也可以使用notify,
//思考一下,如果把notifyAll方法拿到products.push(pc);前面呢?
//其实效果是一样的,上面说过,调用notify或者notifyAll方法虽然唤醒了等待线程,
//但并不会释放对象锁,直到当前线程(或者持有对象锁的方法,代码块)结束后才会被释放,
//因此pop线程虽然被唤醒,但由于拿不到ProductStack对象锁,依旧无法执行
notifyAll();
}
//从货架上取出产品供消费
public synchronized void pop()
{
//如果货架上是空的,则停止消费
if (products.isEmpty())
{
try
{
wait();
}
catch (InterruptedException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//消费一个产品
Product pc = products.pop();
System.out.println("消费了 " + pc.id);
//此时货架肯定不是满的,通知生产者生产
notifyAll();
}
}
//生产者
class Producer implements Runnable
{
ProductStack ps;
Producer(ProductStack ps)
{
this.ps = ps;
}
@Override
public void run()
{
//本次生产计划为20件商品
for (int i = 0; i < 20; i++)
{
ps.push(new Product(String.valueOf(i)));
}
}
}
//消费者
class Consumer implements Runnable
{
ProductStack ps;
Consumer(ProductStack ps)
{
this.ps = ps;
}
@Override
public void run()
{
//消费计划为20件商品
for (int i =0;i<20;i++)
{
ps.pop();
//消费完一个商品之后停顿一段时间,接着消费,可以把这段代码注释掉,比较下输出结果有什么不同
try
{
Thread.sleep((int) (Math.random() * 20));
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
}
}
由于ProductStack的push和pop方法都是synchronized的,而前面我们介绍synchronized方法要求必须获取对象锁才能执行,而同一时间又只能有一个线程可以获取对象锁,因此同一个时间只能有一个方法执行,要么在生产,要么在消费。
分享到:
相关推荐
下面是一个简单的生产者消费者模型示例,演示了`wait()`, `notify()` 的使用: ```java public class Buffer { private int data; private boolean available = false; public synchronized void put(int item)...
`wait()`、`notify()`和`notifyAll()`是Java中的三个关键字,它们属于Object类的方法,主要用于线程间的通信,尤其在实现生产者消费者模式时发挥着重要作用。本文将深入探讨这些方法以及如何在实际场景中应用它们。 ...
了解并掌握生产者消费者模型和`wait-notify`机制对于编写高效的多线程Java应用程序至关重要,尤其是在处理并发任务和大数据流时。通过实践和理解这些概念,开发者可以更好地设计和实现线程安全、性能优良的系统。
通过这样的设计,我们可以有效地控制生产者和消费者的执行顺序,确保了数据的正确性,并且避免了先消费后生产的情况。在实际应用中,可能需要根据具体需求进行优化,例如添加异常处理、使用更高级的同步工具(如`...
5. **设计模式**:生产者消费者模型可以看作是一种“生产者-消费者”设计模式的实例,它属于行为设计模式,用于协调异步处理和共享资源。此外,还可以结合观察者模式,让消费者订阅生产者,当有新数据时,生产者通知...
使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)
通过理解和掌握这些知识点,开发者能够有效地实现生产者-消费者模式,解决并发编程中的数据共享和协作问题。在实际项目中,这个模式常用于优化系统性能,尤其是在I/O密集型或计算密集型的应用中。
这些实现通常会用到`wait()`和`notify()`方法,或者`Lock`和`Condition`对象,以实现线程的等待和通知机制。 总之,生产者消费者模型是并发编程中的基础设计模式,通过合理使用同步工具,如Java的`BlockingQueue`,...
完整的程序应该包括生产者和消费者线程如何处理 `wait()`, `notify()` 以及如何在适当的时候调用 `suspend()` 和 `resume()` 的逻辑。 在多线程环境中,可视化是一个有用的工具,可以帮助开发者观察和理解线程间的...
在实际开发中,除了使用synchronized和wait/notify外,还可以利用Java并发库提供的高级工具,如`ExecutorService`,`CountDownLatch`,`CyclicBarrier`等,来更优雅地实现生产者消费者模型。例如,可以使用`...
`Buffer`类中的`put()`和`take()`方法使用`synchronized`关键字保证了线程安全,并使用`wait()`和`notifyAll()`来控制生产者和消费者的执行顺序。 在`Main`类中,我们创建了两个线程,一个作为生产者,另一个作为...
在这个模型中,数据通常存储在一个缓冲区中,生产者将数据放入缓冲区,消费者则从缓冲区取出数据。关键在于确保生产者不会在缓冲区满时继续生产,消费者也不会在缓冲区空时尝试消费。为解决这个问题,可以使用互斥锁...
以下是简化版的生产者和消费者线程实现: ```cpp void producer(int id) { while (true) { std::unique_lock<std::mutex> lock(mtx); cv.wait(lock, [] { return !empty; }); // 等待缓冲区非空 // 创建数据并...
生产者消费者模型是一种经典的线程同步问题,它模拟了实际生活中的生产过程和消费过程,使得生产者线程可以将数据生产出来,而消费者线程则负责消耗这些数据,两者之间通过共享数据结构进行协同工作。 生产者消费者...
在Java编程中,生产者消费者模型是一种典型的多线程问题,用于解决资源的共享和并发控制。这个模型中,生产者负责生成数据,而...通过分析和运行这个项目,可以加深对生产者消费者模型和`synchronized`关键字的理解。
8. **死锁和饥饿**:在实现多线程生产者消费者模型时,必须警惕可能出现的死锁和饥饿问题。死锁是两个或更多线程相互等待对方释放资源导致的僵局,而饥饿则是指某一线程因为资源分配不公平而无法获得执行的机会。...
生产者消费者模型是一种常见的并发编程模型,在Java中可以通过使用同步机制和wait/notify机制来实现。在本文中,我们将通过一个简单的示例代码介绍Java实现简易生产者消费者模型的过程。 二、Java实现生产者消费者...
在CSDN.NET博客文章中,作者详细讨论了如何使用线程同步和等待的应用来解决此问题,并提供了示例代码ProducerConsumer.java和ProducerConsumerx.java,这些代码可以帮助读者更好地理解和实现生产者消费者模型。...
例如,`ArrayBlockingQueue`、`LinkedBlockingQueue`等,它们可以自然地实现生产者消费者模型,无需手动调用`wait()`和`notify()`。 在提供的`P_C.java`文件中,我们可以预期看到一个简单的生产者消费者模型的实现...
此外,`wait()`和`notify()`方法可能被用来在生产者和消费者之间进行更精细的控制。当生产者发现队列已满,它会调用`wait()`,将自身置于等待状态,同时释放锁。消费者取走元素后,如果发现队列为空,也会调用`wait...