[英]How can the wait() and notify() methods be called on Objects that are not threads?
如何在不是线程的对象上调用wait()
和notify()
方法? 这真的没有意义,不是吗?
当然,这一定是有意义的,因为这两种方法可用于所有 Java 对象。 有人可以提供解释吗? 我无法理解如何使用wait()
和notify()
在线程之间进行通信。
锁定是关于保护共享数据。
锁在被保护的数据结构上。 线程是访问数据结构的东西。 锁位于数据结构对象上,以防止线程以不安全的方式访问数据结构。
任何对象都可以用作内在锁(意味着与synchronized
结合使用)。 通过这种方式,您可以通过向访问共享数据的方法添加同步修饰符来保护对任何对象的访问。
对用作锁的对象调用wait
和notify
方法。 锁是一个共享的通信点:
当一个拥有锁的线程调用notifyAll
时,其他等待同一个锁的线程会得到通知。 当一个拥有锁的线程调用notify
时,等待同一个锁的线程之一会得到通知。
当有锁的线程调用wait
时,该线程释放锁并进入休眠状态,直到 a) 它收到通知,或 b) 它只是任意唤醒(“虚假唤醒”); 由于这两个原因之一,等待线程一直停留在等待调用中,直到它被唤醒,然后线程必须重新获取锁才能退出等待方法。
请参阅有关受保护块的Oracle 教程,Drop 类是共享数据结构,使用生产者和消费者可运行的线程正在访问它。 锁定 Drop 对象控制线程如何访问 Drop 对象的数据。
线程在 JVM 实现中被用作锁,建议应用程序开发人员避免将线程用作锁。 例如, Thread.join的文档说:
此实现使用以 this.isAlive 为条件的 this.wait 调用循环。 当线程终止时,调用 this.notifyAll 方法。 建议应用程序不要在 Thread 实例上使用 wait、notify 或 notifyAll。
Java 5 引入了实现java.util.concurrent.locks.Lock
显式锁。 这些比隐式锁更灵活; 有类似于等待和通知(等待和信号)的方法,但它们是在条件上,而不是在锁上。 具有多个条件可以仅针对等待特定类型通知的线程。
您可以使用wait()
和notify()
来同步您的逻辑。 举个例子
synchronized (lock) {
lock.wait(); // Will block until lock.notify() is called on another thread.
}
// Somewhere else...
...
synchronized (lock) {
lock.notify(); // Will wake up lock.wait()
}
lock
是类成员Object lock = new Object();
想想使用一个现实生活中的例子,一个洗手间。 当您想在办公室使用洗手间时,您有两种选择,以确保您使用后没有其他人会来洗手间。
你会选择哪个?
是的,Javaland 也是一样!
所以在上面的故事中,
所以就像在现实生活中一样,当你有一些私人事务时,你锁定那个对象。 当您完成该对象时,您松开锁!
(是的,是的!,这是对发生的事情的非常简单的描述。当然真正的概念与此略有不同,但这是一个起点)
您可以根据需要使用静态Thread
类方法sleep()
停止线程一段时间。
public class Main {
//some code here
//Thre thread will sleep for 5sec.
Thread.sleep(5000);
}
如果你想停止一些对象,你需要在syncronized
块中调用这个方法。
public class Main {
//some code
public void waitObject(Object object) throws InterruptedException {
synchronized(object) {
object.wait();
}
}
public void notifyObject(Object object) throws InterruptedException {
synchronized(object) {
object.notify();
}
}
}
PS如果我理解错了你的问题,我很抱歉(英语不是我的母语)
当你在 synchronized 块中放入一些代码时:
sychronized(lock){...}
想要执行此块内任何内容的线程首先获取对象上的锁,并且一次只有一个线程可以执行锁定在同一对象上的代码。 任何对象都可以用作锁,但您应该小心选择与作用域相关的对象。 例如,当您有多个线程向帐户添加内容时,它们都有一些代码负责在一个块中执行此操作,例如:
sychronized(this){...}
然后没有同步发生,因为它们都锁定在不同的对象上。 相反,您应该使用帐户对象作为锁。 现在考虑这些线程也有从帐户中提取的方法。 在这种情况下,可能会出现想要提取某些东西的线程遇到空帐户的情况。 它应该等到有一些钱并释放锁给其他线程以避免死锁。 这就是等待和通知方法的用途。 在这个例子中,遇到空账户的线程释放锁并等待来自某个进行存款的线程的信号:
while(balance < amountToWithdraw){
lock.wait();
}
当其他线程存入一些钱时,它会向等待同一锁的其他线程发出信号。 (当然,负责存款和取款的代码必须在同一个锁上同步才能工作并防止数据损坏)。
balance += amountToDeposit;
lock.signallAll;
如您所见,方法 wait 和 notify 仅在同步块或方法中有意义。
在 Java 中所有 Object 都实现了这两个方法,显然如果没有监视器这两个方法是没有用的。
实际上, wait
、 notify
成员函数不应该属于线程,它应该属于的东西命名为来自posix thread 的条件变量。 你可以看看 cpp 如何包装它,它将它包装成一个专用的类std::condition_variable 。
Java 没有做这种封装,而是用更高级的方式包装条件变量: monitor (直接把功能放到Object类中)。
如果您不知道监视器或条件变量,那么一开始确实会让人们感到困惑。
类比: Java 线程是用户,厕所是线程希望执行的代码块。 Java 提供了一种使用同步密钥锁定当前正在执行它的线程的代码的方法,并使希望使用它的其他线程等待直到第一个线程完成。 这些其他线程处于等待状态。 Java 不像服务站那样公平,因为没有等待线程的队列。 任何一个等待线程都可以获取下一个监视器,而不管它们要求的顺序如何。 唯一的保证是所有线程迟早都会使用受监控的代码。
如果您查看以下生产者和消费者代码:
sharedQueue
对象充当producer and consumer
线程之间的线程间通信。
import java.util.Vector;
import java.util.logging.Level;
import java.util.logging.Logger;
public class ProducerConsumerSolution {
public static void main(String args[]) {
Vector<Integer> sharedQueue = new Vector<Integer>();
int size = 4;
Thread prodThread = new Thread(new Producer(sharedQueue, size), "Producer");
Thread consThread = new Thread(new Consumer(sharedQueue, size), "Consumer");
prodThread.start();
consThread.start();
}
}
class Producer implements Runnable {
private final Vector<Integer> sharedQueue;
private final int SIZE;
public Producer(Vector<Integer> sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}
@Override
public void run() {
for (int i = 0; i < 7; i++) {
System.out.println("Produced: " + i);
try {
produce(i);
} catch (InterruptedException ex) {
Logger.getLogger(Producer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private void produce(int i) throws InterruptedException {
// wait if queue is full
while (sharedQueue.size() == SIZE) {
synchronized (sharedQueue) {
System.out.println("Queue is full " + Thread.currentThread().getName() + " is waiting , size: "
+ sharedQueue.size());
sharedQueue.wait();
}
}
// producing element and notify consumers
synchronized (sharedQueue) {
sharedQueue.add(i);
sharedQueue.notifyAll();
}
}
}
class Consumer implements Runnable {
private final Vector<Integer> sharedQueue;
private final int SIZE;
public Consumer(Vector<Integer> sharedQueue, int size) {
this.sharedQueue = sharedQueue;
this.SIZE = size;
}
@Override
public void run() {
while (true) {
try {
System.out.println("Consumed: " + consume());
Thread.sleep(50);
} catch (InterruptedException ex) {
Logger.getLogger(Consumer.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
private int consume() throws InterruptedException {
//wait if queue is empty
while (sharedQueue.isEmpty()) {
synchronized (sharedQueue) {
System.out.println("Queue is empty " + Thread.currentThread().getName()
+ " is waiting , size: " + sharedQueue.size());
sharedQueue.wait();
}
}
//Otherwise consume element and notify waiting producer
synchronized (sharedQueue) {
sharedQueue.notifyAll();
return (Integer) sharedQueue.remove(0);
}
}
}
“此方法只能由作为此对象监视器所有者的线程调用。” 所以我认为你必须确保有一个线程是对象上的监视器。
对象类是为每个对象提供锁的正确位置。 假设有一个联名银行账户,因此多个用户可以使用同一个账户通过多个渠道进行交易。 目前,该账户的余额为 1500/-,账户中保留的最低余额为 1000/-。 现在,第一个用户试图通过 ATM 提取 500/- 的金额,另一个用户试图通过刷卡机购买价值 500/- 的任何商品。 这里首先访问账户进行交易的通道首先获取账户的锁,另一个通道将等待交易完成并释放账户的锁,因为无法知道哪个通道已经获得了锁以及哪个通道正在等待获取锁。 因此,锁定始终应用于帐户本身而不是频道。 在这里,我们可以将帐户视为对象,将通道视为线程。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.