我是否可以获得一个完整的简单场景,即教程,建议如何使用它,特别是使用队列?
答案 0 :(得分:260)
wait()
和notify()
方法旨在提供一种机制,允许线程阻塞,直到满足特定条件。为此我假设你想要编写一个阻塞队列实现,你有一些固定大小的元素后备存储。
您要做的第一件事是确定您希望方法等待的条件。在这种情况下,您需要阻止put()
方法,直到商店中有可用空间,并且您希望阻止take()
方法,直到有一些元素要返回。
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public synchronized void put(T element) throws InterruptedException {
while(queue.size() == capacity) {
wait();
}
queue.add(element);
notify(); // notifyAll() for multiple producer/consumer threads
}
public synchronized T take() throws InterruptedException {
while(queue.isEmpty()) {
wait();
}
T item = queue.remove();
notify(); // notifyAll() for multiple producer/consumer threads
return item;
}
}
关于必须使用等待和通知机制的方式,有几点需要注意。
首先,您需要确保对wait()
或notify()
的任何调用都在同步的代码区域内(wait()
和notify()
调用在同一个对象)。其原因(除了标准的线程安全问题)是由于被称为遗漏信号的原因。
这样的一个示例是,当队列恰好满时,线程可以调用put()
,然后检查条件,看到队列已满,但是在它可以阻止另一个线程被调度之前。然后,第二个线程take()
是队列中的一个元素,并通知等待的线程队列不再满。因为第一个线程已经检查了条件,所以它会在重新安排后调用wait()
,即使它可以取得进展。
通过同步共享对象,可以确保不会发生此问题,因为第二个线程的take()
调用在第一个线程实际被阻止之前无法进行。
其次,由于称为虚假唤醒的问题,您需要将您正在检查的条件置于while循环中,而不是if语句。这是在没有调用notify()
的情况下有时可以重新激活等待线程的地方。将此检查放入while循环将确保如果发生虚假唤醒,将重新检查该条件,并且线程将再次调用wait()
。
正如其他一些答案所提到的,Java 1.5引入了一个新的并发库(在java.util.concurrent
包中),旨在提供对等待/通知机制的更高级别的抽象。使用这些新功能,您可以像这样重写原始示例:
public class BlockingQueue<T> {
private Queue<T> queue = new LinkedList<T>();
private int capacity;
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
public void put(T element) throws InterruptedException {
lock.lock();
try {
while(queue.size() == capacity) {
notFull.await();
}
queue.add(element);
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T take() throws InterruptedException {
lock.lock();
try {
while(queue.isEmpty()) {
notEmpty.await();
}
T item = queue.remove();
notFull.signal();
return item;
} finally {
lock.unlock();
}
}
}
当然,如果您确实需要阻塞队列,那么您应该使用BlockingQueue接口的实现。
此外,对于这样的内容,我强烈推荐Java Concurrency in Practice,因为它涵盖了您可能想要了解的有关并发相关问题和解决方案的所有内容。
答案 1 :(得分:145)
不是队列示例,但非常简单:)
class MyHouse {
private boolean pizzaArrived = false;
public void eatPizza(){
synchronized(this){
while(!pizzaArrived){
wait();
}
}
System.out.println("yumyum..");
}
public void pizzaGuy(){
synchronized(this){
this.pizzaArrived = true;
notifyAll();
}
}
}
一些要点:
1)永远不要
if(!pizzaArrived){
wait();
}
始终使用while(条件),因为
while(!pizzaExists){ wait(); }
。2)您必须在调用wait / nofity之前保持锁定(同步)。线程还必须在唤醒之前获取锁定。
3)尽量避免在同步块中获取任何锁定,并努力不调用外来方法(你不知道他们在做什么的方法)。如果必须,请务必采取措施避免死锁。
4)注意notify()。坚持使用notifyAll()直到你知道自己在做什么。
5)最后但并非最不重要的是,请阅读Java Concurrency in Practice!
答案 2 :(得分:34)
即使您具体要求wait()
和notify()
,我觉得这句话仍然很重要:
Josh Bloch, Effective Java 2nd Edition ,第69项:首选并发实用程序wait
和notify
(强调他的):
鉴于正确使用
wait
和notify
的困难,您应该使用更高级别的并发工具 [...]使用wait
和与notify
提供的高级语言相比,java.util.concurrent
直接类似于“并发汇编语言”编程。 很少(如果有的话)有理由在新代码中使用wait
和notify
。
答案 3 :(得分:6)
您是否看过这个Java Tutorial?
此外,我建议你不要在真实软件中使用这种东西。玩它很好,所以你知道它是什么,但并发性在整个地方都有陷阱。如果要为其他人构建软件,最好使用更高级别的抽象和同步集合或JMS队列。
这至少就是我所做的。我不是一个并发专家,所以我尽可能不用手工处理线程。
答案 4 :(得分:2)
示例
public class myThread extends Thread{
@override
public void run(){
while(true){
threadCondWait();// Circle waiting...
//bla bla bla bla
}
}
public synchronized void threadCondWait(){
while(myCondition){
wait();//Comminucate with notify()
}
}
}
public class myAnotherThread extends Thread{
@override
public void run(){
//Bla Bla bla
notify();//Trigger wait() Next Step
}
}
答案 5 :(得分:0)
线程中的wait()和notifyall()示例。
同步静态数组列表用作资源,如果数组列表为空,则调用wait()方法。一旦为数组列表添加了元素,就会调用notify()方法。
public class PrinterResource extends Thread{
//resource
public static List<String> arrayList = new ArrayList<String>();
public void addElement(String a){
//System.out.println("Add element method "+this.getName());
synchronized (arrayList) {
arrayList.add(a);
arrayList.notifyAll();
}
}
public void removeElement(){
//System.out.println("Remove element method "+this.getName());
synchronized (arrayList) {
if(arrayList.size() == 0){
try {
arrayList.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else{
arrayList.remove(0);
}
}
}
public void run(){
System.out.println("Thread name -- "+this.getName());
if(!this.getName().equalsIgnoreCase("p4")){
this.removeElement();
}
this.addElement("threads");
}
public static void main(String[] args) {
PrinterResource p1 = new PrinterResource();
p1.setName("p1");
p1.start();
PrinterResource p2 = new PrinterResource();
p2.setName("p2");
p2.start();
PrinterResource p3 = new PrinterResource();
p3.setName("p3");
p3.start();
PrinterResource p4 = new PrinterResource();
p4.setName("p4");
p4.start();
try{
p1.join();
p2.join();
p3.join();
p4.join();
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("Final size of arraylist "+arrayList.size());
}
}