Java线程乒乓示例
我试图理解线程的基本知识,作为第一个例子,我创建了两个线程,它们在标准输出上写一个字符串。据我所知,调度程序允许使用循环调度执行线程。这就是为什么我得到: 平 发出砰的声响 庞 庞 庞 发出砰的声响 发出砰的声响 发出砰的声响 庞 庞 现在我想使用一个共享变量,这样每个线程都会知道轮到你了:Java线程乒乓示例,java,multithreading,concurrency,Java,Multithreading,Concurrency,我试图理解线程的基本知识,作为第一个例子,我创建了两个线程,它们在标准输出上写一个字符串。据我所知,调度程序允许使用循环调度执行线程。这就是为什么我得到: 平 发出砰的声响 庞 庞 庞 发出砰的声响 发出砰的声响 发出砰的声响 庞 庞 现在我想使用一个共享变量,这样每个线程都会知道轮到你了: public class PingPongThread extends Thread { private String msg; private static String turn; public Pi
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();
}
}
我同步了“转身经理”,但我仍然得到了如下内容:
平
发出砰的声响
庞
庞
庞
发出砰的声响
发出砰的声响
发出砰的声响
庞
庞
有人能解释一下我错过了什么,为什么我没有打乒乓球。。。乒乓球。
谢谢 PingPongThread的每个实例都在自身而不是共享资源上进行同步。为了控制消息传递,您需要在共享资源上进行同步(例如,您的
turn
变量?)
然而,我认为这是行不通的。我认为您应该签出wait()
和notify()
来执行此操作(如果您想了解线程原语)。有关示例,请参见 这一行:
public synchronized void playTurn(){
//code
}
在行为上等同于
public void playTurn() {
synchronized(this) {
//code
}
}
这就是为什么没有同步发生的原因,因为正如Brian Agnew指出的,线程在两个不同的对象(thread1、thread2)上同步,每个对象都在它自己的实例上,导致没有有效的同步
如果要使用turn变量进行同步,例如:
private static String turn = ""; // must initialize or you ll get an NPE
public void playTurn() {
synchronized(turn) {
//...
turn = msg; // (1)
//...
}
}
然后情况会好得多(运行多次以验证),但也没有100%的同步。在乞讨中(大多数情况下),你会得到一个双乒乓球和双乒乓球,之后它们看起来是同步的,但你仍然可以得到双乒乓球/乒乓球
同步块锁定值(请参见此),而不是对该值的引用。(见编辑)
让我们来看看一个可能的场景:
thread1 locks on ""
thread2 blocks on ""
thread1 changes the value of turn variable to "PING" - thread2 can continue since "" is no longer locked
为了证实我试着
try {
Thread.currentThread().sleep(1000); // try with 10, 100 also multiple times
}
catch (InterruptedException ex) {}
前后
turn = msg;
而且看起来是同步的?!但是,如果你把
try {
Thread.yield();
Thread.currentThread().sleep(1000); // also try multiple times
}
catch (InterruptedException ex) {}
几秒钟后,你会看到两个乒乓球。本质上意味着“我已经完成了处理器,让其他线程工作”。这显然是我的操作系统上的系统线程调度程序实现
所以,要正确同步,我们必须删除行
turn = msg;
因此,线程可以始终在相同的值上进行同步(实际上不是:),如上文所述-字符串(不可变对象)与锁一样危险-因为如果在程序中的100个位置上创建字符串“A”,所有100个引用(变量)都将指向内存中相同的“A”-因此您可能会过度同步
因此,要回答您最初的问题,请修改您的代码,如下所示:
public void playTurn() {
synchronized(PingPongThread.class) {
//code
}
}
并行乒乓球示例将100%正确实现(请参见编辑^2)
上述代码相当于:
public static synchronized void playTurn() {
//code
}
PingPongThread.class是一个,例如,在您可以调用的每个实例上,始终只有一个实例
你也可以这样做
public static Object lock = new Object();
public void playTurn() {
synchronized(lock) {
//code
}
}
此外,阅读并编程示例(必要时多次运行)以了解这一点
编辑:
将是:
synchronized方法与在此基础上锁定synchronized语句相同。让我们将synchronized语句的参数称为“lock”——正如Marko所指出的,“lock”是一个存储对类的对象/实例的引用的变量。引用规范:
synchronized语句计算对对象的引用;然后,它尝试在该对象的监视器上执行锁定操作
因此,同步不是根据值(对象/类实例)进行的,而是根据与该实例/值关联的对象监视器进行的。因为
Java中的每个对象都与一个监视器相关联
效果不变
编辑^2:
就评论意见采取后续行动:
“并行乒乓球示例将100%正确实现”-这意味着,实现了所需的行为(没有错误)
如果结果正确,那么解决方案就是正确的。解决问题的方法有很多种,因此下一个标准是解决方案的简单性/优雅性-相位器解决方案是更好的方法,因为正如Marko在一些评论中所说的,使用相位器对象比使用同步机制出错的机会小得多-这可以从所有(非同步)机制中看出本文中的解决方案变体。值得注意的是代码大小和整体清晰度的比较
总之,只要它们适用于所讨论的问题,就应该使用这种方法 作为我与Brian Agnew讨论的结论,我提交了以下代码,该代码使用
java.util.concurrent.Phaser
来协调您的乒乓球线程:
static final Phaser p = new Phaser(1);
public static void main(String[] args) {
t("ping");
t("pong");
}
private static void t(final String msg) {
new Thread() { public void run() {
while (true) {
System.out.println(msg);
p.awaitAdvance(p.arrive()+1);
}
}}.start();
}
此解决方案与您试图编写的解决方案之间的关键区别在于,您的解决方案忙检查标志,从而浪费CPU时间(和精力!)。正确的方法是使用阻塞方法,使线程处于休眠状态,直到收到相关事件的通知。我的解决方案是:
public class InfinitePingPong extends Thread {
private static final Object lock= new Object();
private String toPrintOut;
public InfinitePingPong(String s){
this.toPrintOut = s;
}
public void run(){
while (true){
synchronized(lock){
System.out.println(this.toPrintOut +" -->"+this.getId());
lock.notifyAll();
try {
lock.wait();
} catch (InterruptedException e) {}
}
}
}
public static void main(String[] args) throws InterruptedException {
InfinitePingPong a = new InfinitePingPong("ping");
InfinitePingPong b = new InfinitePingPong("pong");
a.start();
b.start();
b.wait();
try {
a.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
一种选择是使用SynchronousQueue
import java.util.concurrent.SynchronousQueue;
public class PingPongPattern {
private SynchronousQueue<Integer> q = new SynchronousQueue<Integer>();
private Thread t1 = new Thread() {
@Override
public void run() {
while (true) {
// TODO Auto-generated method stub
super.run();
try {
System.out.println("Ping");
q.put(1);
q.put(2);
} catch (Exception e) {
}
}
}
};
private Thread t2 = new Thread() {
@Override
public void run() {
while (true) {
// TODO Auto-generated method stub
super.run();
try {
q.take();
System.out.println("Pong");
q.take();
} catch (Exception e) {
}
}
}
};
public static void main(String[] args) {
// TODO Auto-generated method stub
PingPongPattern p = new PingPongPattern();
p.t1.start();
p.t2.start();
}
}
import java.util.concurrent.SynchronousQueue;
公共类乒乓模式{
private SynchronousQueue q=新的SynchronousQueue();
私有线程t1=新线程(){
@凌驾
公开募捐{
while(true){
//TODO自动生成的方法存根
super.run();
试一试{
系统输出打印项次(“Ping”);
q、 put(1);
q、 put(2);
}捕获(例外e){
}
}
}
};
私有线程t2=新线程(){
@凌驾
公开募捐{
while(true){
//TODO自动生成的方法存根
super.run();
试一试{
q、 take();
系统输出打印项次(“Pong”);
q、 take();
/*
* Copyright (c) 1995, 2008, Oracle and/or its affiliates. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle or the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* based on oracle example on sync-wait-notify
* cf. https://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html
* run with java ProducerConsumerExample
*
*
*/
public class ProducerConsumerExample {
public static void main(String[] args) {
Drop drop = new Drop();
DropCtoP dropCtoP = new DropCtoP();
(new Thread(new Ping(drop,dropCtoP))).start();
(new Thread(new Pong(drop,dropCtoP))).start();
}
}
public class Pong implements Runnable {
private Drop drop;
private DropCtoP dropCtoP;
private int count=0;
public Pong(Drop drop,DropCtoP dropCtoP) {
this.drop = drop;
this.dropCtoP = dropCtoP;
}
public void run() {
String message;
for (;;) {
count++;
message = drop.take();
System.out.format("Pong running - : %s - ran num times %d %n", message,count);
dropCtoP.put("Run ping token");
}
}
}
public class Ping implements Runnable {
private Drop drop;
private DropCtoP dropCtoP;
private int count=0;
public Ping(Drop drop,DropCtoP dropCtoP) {
this.drop = drop;
this.dropCtoP = dropCtoP;
}
public void run() {
String message;
for (;;) {
count++;
drop.put("Run pong token");
message = dropCtoP.take();
System.out.format("PING running - : %s- ran num times %d %n", message,count);
}
}
}
public class DropCtoP {
// Message sent from producer
// to consumer.
private String message;
// True if consumer should wait
// for producer to send message,
// false if producer should wait for
// consumer to retrieve message.
private boolean empty2 = true;
public synchronized String take() {
// Wait until message is
// available.
while (empty2) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty2 = true;
// Notify producer that
// status has changed.
notifyAll();
return message;
}
public synchronized void put(String message) {
// Wait until message has
// been retrieved.
while (!empty2) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty2 = false;
// Store message.
this.message = message;
// Notify consumer that status
// has changed.
notifyAll();
}
}
public class Drop {
// Message sent from producer
// to consumer.
private String message;
// True if consumer should wait
// for producer to send message,
// false if producer should wait for
// consumer to retrieve message.
private boolean empty = true;
public synchronized String take() {
// Wait until message is
// available.
while (empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = true;
// Notify producer that
// status has changed.
notifyAll();
return message;
}
public synchronized void put(String message) {
// Wait until message has
// been retrieved.
while (!empty) {
try {
wait();
} catch (InterruptedException e) {}
}
// Toggle status.
empty = false;
// Store message.
this.message = message;
// Notify consumer that status
// has changed.
notifyAll();
}
}
public class PingPongDemo {
private static final int THREADS = 2;
private static int nextIndex = 0;
private static String getMessage(int index) {
return index % 2 == 0 ? "ping" : "pong";
}
public static void main(String[] args) throws Throwable {
var lock = new ReentrantLock();
var conditions = new Condition[THREADS];
for (int i = 0; i < conditions.length; i++) {
conditions[i] = lock.newCondition();
}
for (int i = 0; i < THREADS; i++) {
var index = i;
new Thread(() -> {
lock.lock();
try {
while (true) {
System.out.println(getMessage(index));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
nextIndex = (nextIndex + 1) % THREADS;
conditions[nextIndex].signal();
while (nextIndex != index) {
conditions[index].awaitUninterruptibly();
}
}
} finally {
lock.unlock();
}
}).start();
if (index < THREADS - 1) {
lock.lock();
try {
while (nextIndex != (index + 1)) {
conditions[index + 1].awaitUninterruptibly();
}
} finally {
lock.unlock();
}
}
}
}
}