多线程Java线程乒乓示例
我试图理解线程的基本知识,作为第一个例子,我创建了两个线程,它们在标准输出上写一个字符串。据我所知,调度程序允许使用循环调度执行线程。这就是为什么我得到:
PING PING pong pong pong PING PING PING pong pong
现在我想使用一个共享变量,这样每个线程都会知道轮到你了:
public class PingPongThread extends Thread {
private String msg;
private static String turn;
public PingPongThread(String msg){
this.msg = msg;
}
@Override
public void run() {
while(true) {
playTurn();
}
}
public synchronized void playTurn(){
if (!msg.equals(turn)){
turn=msg;
System.out.println(msg);
}
}
}
主要类别:
public class ThreadTest {
public static void main(String[] args) {
PingPongThread thread1 = new PingPongThread("PING");
PingPongThread thread2 = new PingPongThread("pong");
thread1.start();
thread2.start();
}
}
我同步了“轮换管理器”,但仍然得到如下结果:
PING PING pong pong pong PING PING PING pong pong
有人能解释一下我错过了什么,为什么我没有打乒乓球。。。乒乓球。 谢谢
# 1 楼答案
在与Brian Agnew讨论的最后,我提交了以下代码,使用
java.util.concurrent.Phaser
来协调您的乒乓球线程:此解决方案与您试图编写的解决方案之间的关键区别在于,您的解决方案忙检查标志,从而浪费CPU时间(和精力!)。正确的方法是使用阻塞方法,使线程处于休眠状态,直到收到相关事件的通知
# 2 楼答案
我的解决方案是:
# 3 楼答案
这是一个用Java编写的乒乓球程序。乒乓球和乒乓球是分开的。每个线程既是消费者又是生产者。当每个线程运行时,它做两件事
代码基于Oracles ProducerConsumereSample。请注意,Ping和Pong类的代码和行为几乎相同。 OP代码中的线程只使用对象监视器的“互斥”部分(正如Brian Agnew在上面所建议的)。他们从不要求等待。因此,它们只相互排斥,而从不调用java运行时来允许另一个线程运行
# 4 楼答案
一种选择是使用SynchronousQueue
# 5 楼答案
这一行:
在行为上等同于
这就是为什么没有同步发生,因为正如Brian Agnew指出的,线程在两个不同的对象(thread1、thread2)上同步,每个对象都在自己的实例上,导致没有有效的同步
如果要使用turn变量进行同步,例如:
然后情况会好得多(运行多次以验证),但也没有100%的同步。在乞讨中(大部分情况下),你会得到一个双乒乓球和双乒乓球,之后它们看起来是同步的,但你仍然可以得到双乒乓球/乒乓球
同步块锁定值(参见此great answer),而不是对该值的引用。(见编辑)
让我们来看看一个可能的场景:
来验证我是否试着
前后
看起来是同步的?!但是,如果你把
几秒钟后,你会看到两个乒乓球Thread.yield()本质上意味着“我已经完成了处理器,让其他线程工作”。这显然是我操作系统上的系统线程调度程序实现
所以,为了正确地同步,我们必须删除行
因此,线程总是可以在同一个值上进行同步——实际上不是:)正如上面给出的great answer中所解释的那样——字符串(不可变对象)与锁一样危险——因为如果在程序中的100个位置上创建字符串“A”,那么所有100个引用(变量)都将指向内存中相同的“A”——因此可以过同步
所以,要回答最初的问题,请修改代码如下:
并行乒乓球示例将100%正确实现(参见编辑^2)
上述代码相当于:
乒乓球线。类是一个Class object,例如,在每个实例上,您都可以调用getClass(),它总是只有一个实例
你也可以这样做
此外,阅读和编程示例(必要时多次运行)这tutorial
编辑:
将technically correct
synchronized方法与在此基础上锁定synchronized语句相同。让我们将synchronized语句的参数称为“lock”——正如Marko指出的,“lock”是一个变量,用于存储对类的对象/实例的引用。引用规范:
因此,同步不是在对象/类实例的值上进行的,而是在与该实例/值关联的对象监视器上进行的。因为
效果不变
编辑^2:
就评论意见采取后续行动: “并行乒乓球示例将100%正确实现”——这意味着实现了预期的行为(没有错误)
如果结果是正确的,那么解决方案就是正确的。解决问题的方法有很多种,因此下一个标准是解决方案的简单性/优雅性——相量解决方案是更好的方法,因为正如Marko在一些评论中所说的,使用phaser对象比使用同步机制出错的可能性小得多——这可以从本文中所有(非)解决方案变体中看出。值得注意的是代码大小和整体清晰度的比较
总之,只要适用于所讨论的问题,就应该使用这个sort of constructs
# 6 楼答案
PingPongThread
的每个实例都在自身上同步,而不是在共享资源上同步。为了控制消息传递,您需要在共享资源上进行同步(例如,您的turn
变量?)然而,我认为这是行不通的。我认为您应该查看
wait()
和notify()
来实现这一点(如果您想了解线程原语)。有关示例,请参见this