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();
                }
            }
        }
    }

}