multithreadingJava的Hello World

我想要了解如何使用关键字:wait,notify / All,synchronized,所以我决定尝试一个简单的例子。 基本上我要做的是创建两个打印字符串的线程。 第一个线程的字符串为“Hello”,而第二个线程的字符串为“World”。

我想要达到的输出如下:Hello World Hello World Hello World …

这是我到目前为止编写的代码,但现在的输出是:Hello Hello Hello … World World World …

错误在哪里/哪个? 谢谢。 🙂

这是代码:

class MyThread implements Runnable { private SimpleSyncThread sync; private String s; public MyThread(SimpleSyncThread sync, String s) { this.sync = sync; this.s = s; } public static void pause(long time) { try {Thread.sleep(time); } catch (InterruptedException e) {Thread.currentThread().interrupt();} } @Override public void run() { synchronized (sync) { for (int i = 0; i < 10; i++) { sync.print(s); } } } } public class SimpleSyncThread { public void print(String s) { System.out.println(s); MyThread.pause(200); } public static void main(String[] args) { SimpleSyncThread sync = new SimpleSyncThread(); MyThread st1 = new MyThread(sync, "Hello"); MyThread st2 = new MyThread(sync, "World"); Thread t1 = new Thread(st1); Thread t2 = new Thread(st2); t1.start(); t2.start(); } } 

您在这里持有锁,因此一次只能打印一个进程

  synchronized (sync) { for (int i = 0; i < 10; i++) { sync.print(s); } } 

而不是这样做你可以暂时释放锁

  synchronized (sync) { for (int i = 0; i < 10; i++) { sync.print(s); // I have done my bit, wake other threads. sync.notifyAll(); try { // give up the lock and let another thread run. sync.wait(10); } catch(InterruptedException ie) { throw new AssertionError(ie); } } } 

您可能想到的是我称之为Ping Pong测试。 你不会在真正的程序中这样做,但这种模式是一个有用的微基准。

 public class PingPongMain { public static void main(String[] args) throws InterruptedException { boolean[] next = {false}; AtomicInteger count = new AtomicInteger(); Thread t1 = new Thread(() -> { try { synchronized (next) { for(;;) { // handle spurious wake ups. while (next[0]) next.wait(); System.out.println("ping"); // state change before notify next[0] = true; next.notifyAll(); } } } catch (InterruptedException e) { // expected } }); Thread t2 = new Thread(() -> { try { synchronized (next) { for(;;) { // handle spurious wake ups. while (!next[0]) next.wait(); System.out.println("pong"); // state change before notify next[0] = false; next.notifyAll(); count.incrementAndGet(); } } } catch (InterruptedException e) { // expected } }); t1.start(); t2.start(); Thread.sleep(5000); t1.interrupt(); t2.interrupt(); System.out.println("Ping ponged " + count + " times in 5 seconds"); } } 

版画

 ping pong ping pong .. deleted ... Ping ponged 323596 times in 5 seconds 

我想你想模拟一个有两个独立线程访问相同资源的作业:例如System.out,但是睡眠部分可能同时运行。

在模拟中,不应将暂停放在synchronized块中:

 public class SimpleSyncThread { public void print(String s) { synchronized(this){ System.out.println(s); } MyThread.pause(200); } 

在run函数中,您不再需要synchronized:

 public void run() { for (int i = 0; i < 10; i++) { sync.print(s); } } 

现在,您将获得“Hello World Hello World”,或者“Hello World World Hello”。

你有一个非常小的难度的例子:通过waitnotify在java中的基本同步旨在同步一个消费者 – 生产者范例:有一些生产者线程和一些消费者线程。 每个生产者在不等待的情况下完成其工作,然后唤醒(通知)正在等待通知的消费者线程。 即使可运行的类同时是消费者和生产者,该方案也将起作用。 但是沟通总是单向的

  producer -> consumer 

相反,您尝试做的是双向替代线程通信:

  producer -> consumer -> producer -> consumer ... 

我认为你需要一种更复杂的方式来传递你的线程:一个令牌管理器 ,一个包含从0到N的整数令牌的类,并将其旋转:

 public class TokenManager { private final int maxTokens; private int token; public TokenManager(int maxTokens, int initialToken) { super(); this.maxTokens=maxTokens; this.token=initialToken % this.maxTokens; } public int getToken() { return this.token; } public void nextToken() { this.token=++this.token % this.maxTokens; } } 

然后是一个可运行的类,它接收TokenManager并将其用于同步:

 public class MyRunnable implements Runnable { private final String text; private final TokenManager token; // Identifier token value for this object. private final int id; public MyRunnable(int id, String text, TokenManager token) { super(); this.id=id; this.text=text; this.token=token; } @Override public void run() { try { for (int i=0; i < 10; i++) { synchronized (this.token) { // Wait until the TokenManager token is equal to the id of this object: while (this.token.getToken() != this.id) { this.token.wait(); } // Now it's our turn to print: System.out.printf("%d: %s\n", i, this.text); this.token.nextToken(); // Ask the TokenManager to progress to the next token: this.token.notifyAll(); } } } catch (InterruptedException e) { throw new Error(e); } } } public static void main(String[] args) { // Instantiate the TokenManager for a specified number of threads: TokenManager token=new TokenManager(2, 0); // Instantiate and start the thread with id=0: new Thread(new MyRunnable(0, "Hello", token)).start(); // Instantiate and start the thread with id=1: new Thread(new MyRunnable(1, "World", token)).start(); } 

通过这种方式,主要方法是通过为实例化的线程分配ID(按升序)来决定激活顺序。 而且,如果你想要一个新类型的线程,你只需要将3传递给TokenManager(而不是2 )并启动一个具有正确ID的新线程:

  new Thread(new MyRunnable(2, "I'm here", token)).start(); 

备注(感谢Andy Brown ):

  • 必须按顺序给出令牌ID,没有间隙。
  • 可能有多个具有相同令牌ID的线程。 在这种情况下,它们将在轮流中随机执行。