我想要了解如何使用关键字:wait,notify / All,synchronized,所以我决定尝试一个简单的例子。基本上我要做的是创建两个打印字符串的线程。第一个线程的字符串为“Hello”,而第二个线程的字符串为“World”。
我想要达到的输出如下: 你好 世界 你好 世界 你好 世界 ...
这是我到目前为止编写的代码,但现在的输出是: 你好 你好 你好 ... 世界 世界 世界 ...
错误/ s在哪里?谢谢。 :)
以下是代码:
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();
}
}
答案 0 :(得分:2)
您在这里持有锁,因此一次只能打印一个进程
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/Widget"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/WidgetItem"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:divider="#D3D3D3"
android:dividerHeight="1dp"/>
<TextView
android:id="@+id/empty_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/darker_gray"
android:gravity="center"
android:text="@string/emptytext"
android:visibility="gone"/>
</LinearLayout>
您可以使用
暂时释放锁定,而不是这样做 synchronized (sync) {
for (int i = 0; i < 10; i++) {
sync.print(s);
}
}
你可能想到的是我称之为Ping Pong测试。你不会在真正的程序中这样做,但这种模式是一个有用的微观基准。
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);
}
}
}
打印
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");
}
}
答案 1 :(得分:1)
我想你想模拟一个有两个独立线程访问相同资源的作业:例如System.out,但睡眠部分可能同时运行。
在模拟中,您不应该将暂停放在同步块中:
public class SimpleSyncThread {
public void print(String s) {
synchronized(this){
System.out.println(s);
}
MyThread.pause(200);
}
在运行功能中,您不再需要同步:
public void run() {
for (int i = 0; i < 10; i++) {
sync.print(s);
}
}
现在,您将获得“Hello World Hello World”,或者“Hello World World Hello&#39;”
答案 2 :(得分:1)
你已经找到了一个非常小的难度的例子:通过wait
和notify
在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):