java - 仅使用 wait() 和 notify() 在两个线程之间交替
问题描述
我正在做一个关于java线程的练习,我需要做以下事情:假设你有两个线程,比如说player1和player2,它们都共享一个Ball对象。目标是让他们中的一个人拿到球 1 秒钟,然后放下球,让第二个球员做同样的事情,几次。更具体地说,我有两个这样的类(Player2 是相同的,除了名称):
class Player1 extends Thread{
private Ball ball;
public Player1 (Ball ball) {
this.ball=ball;
this.setName("Player 1");
}
@Override
public void run() {
for (int i=0;i<25;i++){
try{
ball.takeBall(); //Player takes the ball
sleep(1000);
ball.dropBall(); //Player drops the ball
}catch (Exception e){
}
}
}
}
目标是编写方法takeBall()
,并dropBall()
以使两个线程交替的方式。
这是我的尝试。
public class Ball{
boolean ballInUse = false;
public synchronized void takeBall() throws InterruptedException {
System.out.println("Ball taken by -> "+Thread.currentThread().getName());
while (ballInUse){
wait();
}
ballInUse = true;
}
public synchronized void dropBall() throws InterruptedException {
System.out.println("Ball dropped by -> "+Thread.currentThread().getName());
ballInUse = false;
notify();
}
}
//Edited to show the main class
class MyMainClass
public static void main(String args[]) {
Ball ball = new Ball();
Player1 player1 = new Player1(ball);
Player2 player2 = new Player2(ball);
player1.start();
player2.start();
}// main
}
在我的脑海中有点道理,因为变量ballInUse
只有在球被另一个线程丢下后才变为假,所以两个线程应该在这两种方法之间交替。显然这个想法有问题,因为这就是我得到的:
Ball taken by -> Player 1
Ball taken by -> Player 2
Ball dropped by -> Player 1
Ball taken by -> Player 1
Ball dropped by -> Player 1
Ball taken by -> Player 1
Ball dropped by -> Player 1
Ball taken by -> Player 1
似乎第二个线程永远不会从第二种方法得到通知。我肯定在这里错过了重要的一点,所以任何提示都将不胜感激。
解决方案
是的。但是,“唤醒线程”并不意味着“它会向前飞并立即开始运行这一时刻,而您的线程必须执行极其缓慢的动作……循环一个 for 循环”。
它可以(这是线程的问题之一:事情是任意的,任意是不好的,因为它是不可测试的。除了不使用需要同步的线程之外,没有解决办法,例如通过事务和一个数据库)......但它通常不会。
你的 dropBall 运行到它的自然结束,然后它释放锁(因为 dropBall 方法是同步的)。只有现在,其他线程的 takeBall 才能从它的等待中继续前进(等待不只是等待通知 - 它还释放锁并且不能继续,直到它都被通知并且它可以重新获取锁,这最初是它根据定义不能,因为你不能在不持有锁的情况下通知)。
实际发生的是这样的:
- Player2 的线程正在注意球的锁定是否可用;目前不是。
- Player1 的线程释放锁,跳回循环 25 次的 for 循环,执行 takeBall 方法并重新获取锁。
- 过了一会儿,Player2抬起头说:哦,等等,什么?哦,疯了!我错过了!
不能保证锁是“公平的”。因为公平锁需要时间,并且 JVM 旨在尽可能快地实现所有保证(因此,不会给你未承诺的免费赠品),因此 JVM 锁几乎永远不会公平,除非JVM impl 的极其奇怪的组合,操作系统和硬件。
在放下和重新拍摄之间睡一觉,你可能会看到你所期待的东西。现在虚拟机/操作系统/硬件要么 [A] 实际上让 player2 的线程抓住锁并实际拿到球,要么 [B] 旋转它的拇指。
请注意,Thread.sleep
您将要编写的内容不得在您持有锁的任何地方。鉴于整个 takeBall 方法是同步的,不要在那里做;在您的 for 循环中执行此操作。(作为旁注,捕获异常并且什么都不做?这非常糟糕,永远不要这样做1 - 我也为你解决了这个问题):
try {
ball.takeBall();
sleep(1000);
ball.dropBall();
sleep(100);
} catch (Exception e) {
throw new RuntimeException("Unhandled", e);
}
[1] 除非你真的打算让代码默默地忽略错误并像什么都没发生一样继续。有时这是正确的举动,但极为罕见,新手不应该干预。太容易误解某事,然后完全丢失,因为关键信息(即错误发生、发生位置、原因以及周围的细节)被丢弃了。
推荐阅读
- artifactory - Artifactory - 文件版本的概念
- uwp - 线程如何更新作为主线程一部分的页面表单中的文本框?
- c++ - 这个解决方案对产生变化的多种方式(自上而下)有什么问题?
- python - 使用带有 AllenNLP Interpret 或 Textattack 的 spaCy 模型
- java - 从 1.8 迁移到 openJDK11 时,如何解决“sun.security.x509”不可见?
- arrays - 如何将参数从批处理文件传递到perl中的数组?
- css - 芯片或晶圆厂位置在内容上方的中心
- html - 将 sidenav 添加到 Angular 项目
- python - 在 python 循环中迭代浮点变量时出错
- mysql - Laravel Eloquent 按字段排序为 1,2,3,4,1,2,3,4