我正在尝试理解线程基础,作为第一个例子,我创建了两个在stdout上写一个String的线程。据我所知,调度程序允许使用循环调度执行线程。这就是我得到的原因:
PING PING 乒乓 乒乓 乒乓 PING PING PING 乒乓 乒乓
现在我想使用一个共享变量,所以每个线程都会知道你的轮到你了:
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 乒乓 乒乓 乒乓 PING PING PING 乒乓 乒乓
有人可以解释我所缺少的内容,以及为什么我没有得到乒乓球......乒乓球。 谢谢!
答案 0 :(得分:12)
总结我与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时间(和能量!)。正确的方法是使用阻塞方法使线程进入休眠状态,直到通知相关事件。
答案 1 :(得分:10)
这一行:
public synchronized void playTurn(){
//code
}
与行为等同于
public void playTurn() {
synchronized(this) {
//code
}
}
这就是为什么没有发生同步的原因,因为正如Brian Agnew指出的那样,线程正在同步两个不同的对象(thread1,thread2),每个对象都在它自己的实例上,导致没有有效的同步。
如果您使用转弯变量进行同步,例如:
private static String turn = ""; // must initialize or you ll get an NPE
public void playTurn() {
synchronized(turn) {
//...
turn = msg; // (1)
//...
}
}
然后情况好多了(运行多次验证),但也没有100%同步。在开始(大多数情况下),你得到一个双击和双乒乓球,然后他们看起来同步,但你仍然可以得到双/ pongs。
同步块锁定值(请参阅此great answer),而不是对该值的引用。 (见编辑)
让我们来看一个可能的场景:
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) {}
几秒钟之后你会看到双击/双击。 Thread.yield()本质上意味着“我已完成处理器,将其他线程用于工作”。这显然是我操作系统上的系统线程调度程序实现。
因此,要正确同步,我们必须删除行
turn = msg;
这样线程总是可以在同一个值上同步 - 不是真的:)正如上面给出的great answer中所解释的 - 字符串(不可变对象)像锁一样危险 - 因为如果你在100上创建字符串“A”程序中的所有100个引用(变量)将指向内存中相同的“A” - 因此您可以过度同步。
因此,要回答您的原始问题,请修改您的代码:
public void playTurn() {
synchronized(PingPongThread.class) {
//code
}
}
并且并行PingPong示例将100%正确实现(请参阅EDIT ^ 2)。
以上代码相当于:
public static synchronized void playTurn() {
//code
}
PingPongThread.class是Class object,例如在每个实例上,您都可以调用getClass(),它总是只有一个实例。
你也可以这样做
public static Object lock = new Object();
public void playTurn() {
synchronized(lock) {
//code
}
}
此外,阅读和编程示例(必要时多次运行)tutorial。
编辑:
synchronized方法与此处的synchronized语句锁定相同。让我们调用synchronized语句“lock”的参数 - 正如Marko指出的那样,“lock”是一个存储对类的对象/实例的引用的变量。引用规范:
synchronized语句计算对象的引用;然后它尝试在该对象的监视器上执行锁定操作..
因此,同步不是在值 - 对象/类实例上,而是在对象监视器与该实例/值相关的情况下进行的。因为
Java中的每个对象都与一个监视器相关联。
效果保持不变。
EDIT ^ 2:
跟进评论备注: “并行PingPong示例将100%正确实现” - 意味着,实现了所需的行为(没有错误)。
恕我直言,如果结果正确,解决方案是正确的。有很多方法可以解决这个问题,所以接下来的标准就是解决方案的简洁/优雅 - 移相器解决方案是更好的方法,因为正如Marko在某些评论中所说的那样使用移相器产生错误的可能性要小很多对象比使用同步机制 - 这可以从本文中的所有(非)解决方案变体中看出。值得注意的还有代码大小和整体清晰度的比较。
总而言之,只要适用于有问题的问题,就应该使用sort of constructs。
答案 2 :(得分:4)
PingPongThread
的每个实例都在自身同步,而共享资源上的不是。为了控制邮件传递,您需要在共享资源上进行同步(例如您的turn
变量?)
但我不认为这确实会起作用。我认为您应该检查wait()
和notify()
来执行此操作(如果您想了解线程基元)。有关示例,请参阅this。
答案 3 :(得分:0)
我的解决方案是:
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();
}
}
}
答案 4 :(得分:0)
一种选择是使用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();
}
}
答案 5 :(得分:0)
这是用Java编写的Ping Pong程序。 Ping和Pong是单独的线程。每个线程既是消费者又是生产者。每个线程运行时都会做两件事
代码基于Oracles ProducerConsumerExample。请注意,Ping和Pong类的代码和行为几乎相同。 OP的代码中的线程仅使用对象监视器的“互斥”部分(如上文Brian Agnew所建议)。他们从不调用等待。因此,它们仅互相排斥,而从不调用Java运行时来允许其他线程运行。
/*
* 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();
}
}
答案 6 :(得分:0)
可能的实现之一:
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();
}
}
}
}
}
在这里,我们正在有效地进行循环输出。
答案 7 :(得分:0)
以下是使用Semaphore
对象来完成同步的版本:
import java.util.concurrent.*;
public class Main {
@FunctionalInterface
public interface QuadFunction<T, U, V, W, R> {
public R apply(T t, U u, V v, W w);
}
public static void main(String[] args) {
ExecutorService svc = Executors.newFixedThreadPool(2);
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
System.out.println("Terminating...");
svc.shutdownNow();
try { svc.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS); }
catch(InterruptedException e) {};
}));
var sem1 = new Semaphore(1);
var sem2 = new Semaphore(0);
QuadFunction<String, String, Semaphore, Semaphore, Runnable> fun =
(name, action, s1, s2) ->
(Runnable) () -> {
try {
while (true) {
s1.acquire();
System.out.format("%s %s\n", name, action);
Thread.sleep(500);
s2.release(1);
}
} catch (InterruptedException e) {}
s2.release(1);
System.out.format("==> %s shutdown\n", name);
};
svc.execute(fun.apply("T1", "ping", sem1, sem2));
svc.execute(fun.apply("T2", "pong", sem2, sem1));
}
}