“嗨,阿米戈!”
“我想和你深入探讨 wait-notify。wait-notify 方法为线程交互提供了一种方便的机制。它们还可以用于构建复杂的线程交互高级机制。”
“我将从一个小例子开始。假设我们有一个服务器程序,必须执行用户通过网站创建的各种任务。用户可以在不同时间添加各种任务。任务是资源密集型的,但我们服务器的 8 -核心处理器可以应付。我们应该如何在服务器上执行任务?”
“首先,我们将创建一组工作线程,与处理器内核的数量一样多。每个线程都能够在自己的内核上运行:线程不会相互干扰,处理器内核也不会坐以待毙。”
“其次,我们将创建一个队列对象,其中将添加用户的任务。不同类型的任务将对应不同的对象,但它们都将实现 Runnable 接口,以便它们可以运行。”
“你能给我一个任务对象的例子吗?”
“一探究竟:”
class Factorial implements Runnable
{
public int n = 0;
public long result = 1;
public Factorial (int n)
{
this.n = n;
}
public void run()
{
for (int i = 2; i <= n; i++)
result *= i;
}
}
“到目前为止,一切都很好。”
“太好了。那么让我们检查一下队列对象应该是什么样子。你能告诉我什么吗?”
“它必须是线程安全的。它由一个从用户那里接收任务对象的线程加载任务对象,然后任务由工作线程接收。”
“是的。如果我们暂时没有任务怎么办?”
“那么工作线程应该等到有更多线程。”
“没错。现在想象一下,所有这些都可以构建在一个队列中。检查一下:”
public class JobQueue
{
ArrayList jobs = new ArrayList();
public synchronized void put(Runnable job)
{
jobs.add(job);
this.notifyAll();
}
public synchronized Runnable getJob()
{
while (jobs.size() == 0)
this.wait();
return jobs.remove(0);
}
}
“我们有一个getJob方法来检查任务列表是否为空。线程然后进入睡眠状态(等待),直到列表中出现某些内容。”
“还有put方法,它可以让你向列表中添加一个新任务。一旦添加了一个新任务,就会调用notifyAll方法。调用这个方法会唤醒所有在 getJob 方法中休眠的工作线程。”
“你能记起 wait 和 notify 方法是如何工作的吗?”
“wait 方法仅在同步块内调用,在互斥对象上。在我们的例子中:this。此外,还会发生两件事:
1)线程睡着了。
2)线程暂时释放互斥量(直到被唤醒)。
“在那之后,其他线程可以进入同步块并获取相同的互斥量。”
“ notifyAll方法也只能在互斥对象的同步块内调用。在我们的例子中:这个。此外,还会发生两件事:”
1)所有等待这个互斥对象的线程都被唤醒。
2) 一旦当前线程退出同步块,其中一个被唤醒的线程获取互斥量并继续其工作。当它释放互斥锁时,另一个被唤醒的线程获取互斥锁,等等。
“这很像公共汽车。你进去想付钱,但没有司机。所以你“睡着了”。最后,公共汽车挤满了人,但仍然没有人付钱。然后司机到达并说,«Fare, please»。这是……的开始。
“有趣的比较。但什么是公共汽车?”
“胡里奥对此进行了解释。21 世纪使用了这些奇怪的东西。”
GO TO FULL VERSION