Java多线程是个很复杂的问题,尤其在多线程在任何给定的时间访问共享资源需要更加注意。Java 5引入了一些类比如BlockingQueue Executors 类提供了易于使用的API,避免了一些复杂性。使用这些类比直接使用wait()和notify()同步处理的让程序员感到更加自信。我也推荐使用这些新的API来同步,但是很多时候我们出于各种原因需要旧方式,例如维护遗留代码。在这些情况下,熟悉这些方法将有助于你理解。在本教程中,我讨论关于wait(),notify()和notifyall()的一些概念。

什么是wait(),notify()和notifyall()方法?

在进入概念之前,让我们记下这些方法的基本定义。
在Java中Object类有三个final方法允许线程了解资源的锁定状态,它们是:

1. wait():

在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。然后该线程将等到重新获得对监视器的所有权后才能继续执行。
它告诉调用线程放弃锁。我们不可能用纯Java实现wait()法:它是一个native method。
它被用来使线程等待某个条件,它必须在同步区域内部被调用,这个同步区域将对象锁定在了调用wait方法的对象上。始终应该使用wait循环模式来调用wait方法,永远不要在循环之外调用wait方法。循环会在等待之前和之后测试条件。
在等待之前测试条件,当条件已经成立时立即就跳过等待,这对确保活性时必要的。
在等待之后测试条件,如果条件不成立的话继续等待,这对于确保安全性时必要的。
一般使用wait()方法的语法是如下:

synchronized( lockObject )
{
while( ! condition )
{
lockObject.wait();
} //take the action here;
}

2.notify():

唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
唤醒在同一对象上调用wait()处于等待的线程。应该指出的是,调用notify()实际上并没有放弃对资源上的锁。它告诉等待线程可以唤醒。然而,如果对某一资源调用notify(),但同步块内的资源的执行还需要进行10秒,那么线程需要等待额外的10秒,对象上的锁被释放,被唤醒的线程才能执行。

一般调用notify()方法的语法如下:

synchronized(lockObject)
{
//establish_the_condition; lockObject.notify(); //any additional code if needed
}

3.notifyall():

它唤醒所有在同一对象上调用wait()的线程。在大多数情况下,优先级最高的线程将首先运行,但没有保证。其他的都和notify()方法一样。
一般调用notifyall()方法的语法如下:

synchronized(lockObject)
{
establish_the_condition; lockObject.notifyAll();
}

在一般情况下,总应该调用notifyAll将唤醒所有需要被唤醒的线程。你可能也会唤醒其他一些线程,但这不影响程序的正确性,这些线程醒来之后,会检查他们正在等待的条件,如果发现条件不满足,就会继续等待
到目前为止,我们学到了一些你可能已经知道的基本知识。让我们编写一个小程序来理解如何使用这些方法获得预期的结果。

如何使用wait(),notify()和notifyall()方法

在这个练习中,我们将使用wait()和notify()方法解决生产者消费者问题。让程序简单,关注于wait()和notify()方法的使用,我们将只涉及一个生产者和消费者线程。
该程序的其他特点是:
1)生产者线程每秒生产新资源,并放在taskQueue中。
2)消费者线程需要1秒的过程从taskQueue中消耗资源。
3)对taskqueue最大容量是5,即在任何给定的时间最大可以存在5个资源在taskQueue中,。
4)两个线程无限运行。

设计生产者线程

下面是基于我们需求的生产者线程代码:

class Producer implements Runnable
{
private final List<Integer> taskQueue;
private final int MAX_CAPACITY; public Producer(List<Integer> sharedQueue, int size)
{
this.taskQueue = sharedQueue;
this.MAX_CAPACITY = size;
} @Override
public void run()
{
int counter = 0;
while (true)
{
try
{
produce(counter++);
}
catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
} private void produce(int i) throws InterruptedException
{
synchronized (taskQueue)
{
while (taskQueue.size() == MAX_CAPACITY)
{
System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
taskQueue.wait();
} Thread.sleep(1000);
taskQueue.add(i);
System.out.println("Produced: " + i);
taskQueue.notifyAll();
}
}
}

1)在这里,produce(counter++)代码被写入无限循环中,这样生产者就可以定期生成元素。
2)我们已经在produce()方法中第一节提到的一般准则添加了wait()方法。
3)一旦等待结束,生产者将一个元素加入taskQueue,然后调用 notifyall()方法唤醒其他线程。因为上次wait()方法被消费者线程调用(这就是为什么生产者从等待状态),消费者被唤醒。
4)如果准备好消费元素,消费线程被唤醒。
5)注意两个线程都使用了sleep()方法来模拟创造和消费要素的时间延迟。

设计消费者线程

下面是基于我们需求的消费者线程代码:

class Consumer implements Runnable
{
private final List<Integer> taskQueue; public Consumer(List<Integer> sharedQueue)
{
this.taskQueue = sharedQueue;
} @Override
public void run()
{
while (true)
{
try
{
consume();
} catch (InterruptedException ex)
{
ex.printStackTrace();
}
}
} private void consume() throws InterruptedException
{
synchronized (taskQueue)
{
while (taskQueue.isEmpty())
{
System.out.println("Queue is empty " + Thread.currentThread().getName() + " is waiting , size: " + taskQueue.size());
taskQueue.wait();
}
Thread.sleep(1000);
int i = (Integer) taskQueue.remove(0);
System.out.println("Consumed: " + i);
taskQueue.notifyAll();
}
}
}

1)这里的consume()代码被写在无限循环,让消费者保持无论何时消费taskqueue中的元素..
2)一旦等待结束,消费者从taskQueue中移除一个元素,然后调用notifyall()方法唤醒其他线程。因为上次wait()方法被生产者线程调用(这就是为什么生产者在等待状态),生产者线程收到通知被唤醒。
3)如果准备好生产元素,生产者线程获得通知。

测试应用

现在测试生产者和消费者线程。

public class ProducerConsumerExampleWithWaitAndNotify
{
public static void main(String[] args)
{
List<Integer> taskQueue = new ArrayList<Integer>();
int MAX_CAPACITY = 5;
Thread tProducer = new Thread(new Producer(taskQueue, MAX_CAPACITY), "Producer");
Thread tConsumer = new Thread(new Consumer(taskQueue), "Consumer");
tProducer.start();
tConsumer.start();
}
}

Output:

Produced: 0

Consumed: 0

Queue is empty Consumer is waiting , size: 0

Produced: 1

Produced: 2

Consumed: 1

Consumed: 2

Queue is empty Consumer is waiting , size: 0

Produced: 3

Produced: 4

Consumed: 3

Produced: 5

Consumed: 4

Produced: 6

Consumed: 5

Consumed: 6

Queue is empty Consumer is waiting , size: 0

Produced: 7

Consumed: 7

Queue is empty Consumer is waiting , size: 0

我建议您将生产者和消费者线程所花费的时间改为不同的时间,并在不同的场景中检查不同的输出。

wait(),notify()和notifyall()方法在面试中的问题

当notify()被调用,但没有线程等待会发生什么?

在一般情况下,如果这些方法被正确使用,大多数情况下都不会出现这种情况。但如果notify()方法被调用时,没有其它线程等待,notify()只是返回,并丢失通知。

由于等待和通知机制不知道它发送通知的条件,所以假设没有线程等待,通知将被忽略。一个后来执行的wait()方法的线程必须等待下一个通知发生。

wait()方法释放或获取锁的时候是否存在一个竞争条件?

wait()方法与锁机制紧密结合。在等待线程处于接收通知的状态之前,对象锁实际上不会释放。这意味着只有当线程状态发生变化时,它才能够接收通知,锁才被保存。该系统这个机制防止任何竞争条件发生。

类似地,系统确保在将线程移动到等待状态之前完全锁定对象。

如果一个线程接受到通知,那么是否就保证了条件是被正确设置的?

简单地说,不保证,调用wait()方法之前,一个线程在拥有同步锁的情况下应该一直测试条件。在从wait()方法返回,线程应该一直测试条件来决定是否应该再等待。这是因为另一个线程也可以测试条件并确定等待是不必要的——处理由通知线程设置的有效数据。

当多个线程等待通知时会发生什么情况?调用notify()方法是实际上是哪个线程被唤醒?

这取决于许多因素。Java规范不确定哪个线程被唤醒。在运行时,该线程被唤醒基于几个因素,包括在程序执行过程中的Java虚拟机和调度和时序问题的实现。即使在单个处理器平台上,也无法确定多个线程被唤醒的方式。

就像notify()方法,notifyall()方法不允许我们决定哪个线程被唤醒:他们都被唤醒。当所有的线程被唤醒,可以制定一个机制,线程之间选择那个线程应该继续,那个或者那些线程应该又调用wait()方法继续等待

notifyall()方法是否真正唤醒所有的线程?

是也不是,所有等待的线程唤醒,但他们仍需要获取对象锁。因此线程不能并行运行:它们必须等待对象锁被释放。在一个时间只有一个线程可以运行。

如果只有一个线程要执行,为什么要唤醒所有线程呢?

有几个原因。例如,可能有不止一个条件需要等待。由于我们无法控制哪个线程被唤醒,所以通知完全可以唤醒等待完全不同状态的线程。通过唤醒所有线程,我们可以设计程序,以便线程自行决定下一个线程应该执行什么。另一种选择可能是,当生产者生成能够满足多个消费者的数据时。由于很难确定有多少消费者可以被通知,一个选择是通知他们所有人,让消费者自行处理。

Happy Learning !!

如何使用wait(), notify() and notifyAll() – Java的更多相关文章

  1. 如何在 Java 中正确使用 wait, notify 和 notifyAll(转)

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  2. Java Thread wait, notify and notifyAll Example

    Java Thread wait, notify and notifyAll Example Java线程中的使用的wait,notify和nitifyAll方法示例. The Object clas ...

  3. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  4. java wait()和notify()、notifyAll()

    图见<JAVA并发编程的艺术>P98-101 这三个方法都是java.lang.Object的方法,用于协调多个线程对共享数据的存取,必须在synchronized语句块中使用!这三个方法 ...

  5. 通过生产者消费者模式例子讲解Java基类方法wait、notify、notifyAll

    wait(),notify()和notifyAll()都是Java基类java.lang.Object的方法. 通俗解释wait():在当前线程等待其它线程唤醒.notify(): 唤醒一个线程正在等 ...

  6. Java的wait(), notify()和notifyAll()使用小结

    wait(),notify()和notifyAll()都是java.lang.Object的方法: wait(): Causes the current thread to wait until an ...

  7. Java多线程 wait, notify 和 notifyAll

    Java的Object类 public class Object { public final native void notify(); public final native void notif ...

  8. java线程中的wait和notify以及notifyall

    一.区别与联系 1.1.wait(),notify()和notifyAll()都是java.lang.Object的方法,而确实sleep方法是Thread类中的方法,这是为什么呢?  因为wait和 ...

  9. [译]Java Thread wait, notify和notifyAll示例

    Java Thread wait, notify和notifyAll示例 Java上的Object类定义了三个final方法用于不同线程间关于某资源上的锁状态交互,这三个方法是:wait(), not ...

随机推荐

  1. (知识点)JavaScript原型和原型链

    〇 每个函数都拥有prototype属性,而该属性所储存的就是原型对象 1)原型属性—— 上面我们测试了foo()函数的 1) length属性(length属性除了可以用在数组中,还可以用于记录函数 ...

  2. 经验分享:如何用grep对PHP进行代码审计

    这是一个常见的误解- 企业需要购买复杂和昂贵的软件来发现应用程序中安全漏洞:而这些专门的软件应用程序,无论是黑盒或白盒,开源或商业,都能很快的发现安全漏洞. 事实是:所有这些专业的漏洞扫描工具都有其特 ...

  3. 关于在"a"标签中添加点击事件的一些问题

    昨天做修改页面跳转时遇到一个问题,如果a标签的"href"属性为空的话,比如这样<a href="" onclick="roleupdate() ...

  4. c#实现windows远程桌面连接程序

    c#实现windows远程桌面连接程序 使用winform制作windows远程桌面连接程序,windows自带了远程桌面连接,我们需要将远程桌面连接集成 到自己的winform程序,并实现管理远程主 ...

  5. list和map集合

    List特点:元素有放入顺序,元素可重复Set特点:元素无放入顺序,元素不可重复(注意:元素虽然无放入顺序,但是元素在set中的位置是有该元素的HashCode决定的,其位置其实是固定的)Map特点: ...

  6. 使用Tomcat-redis-session-manager来实现Tomcat集群部署中的Session共享

    一.工作中因为要使用到Tomcat集群部署,此时就涉及到了Session共享问题,主要有三种解决方案: 1.使用数据库来存储Session 2.使用Cookie来存储Session 3.使用Redis ...

  7. 读《Java并发编程的艺术》(一)

    离开博客园很久了,自从找到工作,到现在基本没有再写过博客了.在大学培养起来的写博客的习惯在慢慢的消失殆尽,感觉汗颜.所以现在要开始重新培养起这个习惯,定期写博客不仅是对自己学习知识的一种沉淀,更是在督 ...

  8. Mongodb安装启动详解

    最近在倒腾node+mongodb,安装mongodb的时候开始遇到很多问题,然后折腾了好几次,直到可以很顺利完成安装 ,所以把安装的过程记录下来. 线上系统基本上都是linux的,所以只安装了lin ...

  9. elasticsearch系列(三)分表分库

    首先ES没有库和表的概念,只有index,type,document(详细术语可以看ES的系列一 http://www.cnblogs.com/ulysses-you/p/6736926.html), ...

  10. Flume简介及安装

    Hadoop业务的大致开发流程以及Flume在业务中的地位: 从Hadoop的业务开发流程图中可以看出,在大数据的业务处理过程中,对于数据的采集是十分重要的一步,也是不可避免的一步,从而引出我们本文的 ...