3

私はスレッドの基本を理解しようとしています。最初の例として、stdoutに文字列を書き込む2つのスレッドを作成します。私が知っているように、スケジューラーはラウンドロビンスケジュールを使用してスレッドを実行することを許可します。それが私が得た理由です:

PING PING pong pong pong PING PING PING pong pong

今度は共有変数を使用したいので、すべてのスレッドがあなたの番かどうかを知ることができます。

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 pong pong pong PING PING PING pong pong

誰かが私が欠けているものを説明できますか、そしてなぜ私はピンポンを取得していないのですか...ピンポン。ありがとう!

4

8 に答える 8

13

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時間(およびエネルギー)が無駄になります。正しいアプローチは、関連するイベントが通知されるまでスレッドをスリープ状態にするブロッキングメソッドを使用することです。

于 2012-10-08T18:07:16.850 に答える
12

この行:

public synchronized void playTurn(){
    //code
}

動作はと同等です

public void playTurn() {
    synchronized(this) {
         //code
    }
}

Brian Agnewが指摘したように、スレッドは2つの異なるオブジェクト(thread1、thread2)で同期しており、それぞれが独自のインスタンスであるため、効果的な同期が行われないため、同期が発生しないのはそのためです。

同期にターン変数を使用する場合、例:

private static String turn = ""; // must initialize or you ll get an NPE

public void playTurn() {
    synchronized(turn) {
         //...
         turn = msg; // (1)
         //...
    }
}

その場合、状況ははるかに良くなります(検証のために複数回実行します)が、100%の同期もありません。最初は(ほとんどの場合)ダブルpingとダブルポンが発生し、その後は同期しているように見えますが、それでもダブルping/ポンを取得できます。

同期されたブロックは、その値への参照ではなく、(このすばらしい回答を参照)をロックします。(編集を参照)

それでは、考えられる1つのシナリオを見てみましょう。

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) {}

数秒後、二重のping/pongsが表示されます。Thread.yield()は、本質的に「プロセッサを使い終わったので、他のスレッドを動作させる」ことを意味します。これは明らかに私のOSでのシステムスレッドスケジューラの実装です。

したがって、正しく同期するには、行を削除する必要があります

    turn = msg;

スレッドが常に同じ値で同期できるように-実際にはそうではありません:)上記の素晴らしい答えで説明されているように-文字列(不変オブジェクト)はロックとして危険です-プログラムの100か所に文字列「A」を作成すると100か所すべて参照(変数)はメモリ内の同じ「A」を指すため、をオーバーシンクロする可能性があります。

したがって、元の質問に答えるには、次のようにコードを変更します。

 public void playTurn() {
    synchronized(PingPongThread.class) {
         //code
    }
}

並列のPingPongの例は、100%正しく実装されます(EDIT ^ 2を参照)。

上記のコードは次と同等です。

 public static synchronized void playTurn() {
     //code
 }

PingPongThread.classはClassオブジェクトです。たとえば、すべてのインスタンスで、常に1つのインスタンスしかないgetClass()を呼び出すことができます。

また、あなたはこのようにすることができます

 public static Object lock = new Object();

 public void playTurn() {
    synchronized(lock) {
         //code
    }
}

また、このチュートリアルの例(必要に応じて複数回実行)を読んでプログラムしてください。

編集:

技術的に正しいこと:

同期メソッドは、これをロックする同期ステートメントと同じです。同期ステートメントの引数を「lock」と呼びましょう。Markoが指摘したように、「lock」はクラスのオブジェクト/インスタンスへの参照を格納する変数です。仕様を引用するには:

同期ステートメントは、オブジェクトへの参照を計算します。次に、そのオブジェクトのモニターでロックアクションを実行しようとします。

したがって、同期は実際には(オブジェクト/クラスインスタンス)ではなく、そのインスタンス/値に関連付けられたオブジェクトモニターで行われます。なぜなら

Javaの各オブジェクトは、モニターに関連付けられています。

効果は同じままです。

編集^2:

コメントのフォローアップコメント:「並列PingPongの例は100%正しく実装されます」-つまり、目的の動作が(エラーなしで)達成されます。

私見、結果が正しければ解決策は正しいです。問題を解決する方法はたくさんあるので、次の基準はソリューションの単純さ/優雅さです-Markoが言い換えると、いくつかのコメントでフェイザーを使用してエラーが発生する可能性がはるかに少ないため、フェイザーソリューションがより良いアプローチです同期メカニズムを使用するよりもオブジェクト-この投稿のすべての(非)ソリューションバリアントから見ることができます。注目すべきは、コードサイズと全体的な明快さの比較です。

結論として、この種の構成は、問題の問題に適用できる場合は常に使用する必要があります。

于 2012-10-08T17:51:09.870 に答える
4

の各インスタンスは、共有リソースではなくPingPongThread、それ自体で同期しています。メッセージパッシングを制御するには、共有リソース(変数など)で同期する必要があります。turn

しかし、これは実際にはうまくいかないと思います。私はあなたがチェックアウトしてこれを行うべきだと思いますwait()notify()あなたがスレッドプリミティブを理解したいのであれば)。例については、これを参照してください。

于 2012-10-08T16:04:22.407 に答える
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();
        }








}
}
于 2014-10-21T08:17:22.417 に答える
0

1つのオプションは、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();
    }
}
于 2017-04-04T15:24:01.970 に答える
0

これがJavaで書かれたピンポンプログラムです。PingとPongは別々のスレッドです。各スレッドは、コンシューマーとプロデューサーの両方です。各スレッドが実行されると、2つのことが行われます。

  1. 他の(コンシューマーとして)実行を許可するメッセージを生成します
  2. 自分自身を中断させるメッセージを消費します。

このコードは、Oracleの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();
    }


}
于 2019-01-15T14:32:39.990 に答える
0

可能な実装の1つ:

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

}

ここでは、ラウンドロビン出力を効果的に行っています。

于 2019-11-18T10:14:57.740 に答える
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));
    }
}
于 2020-05-15T13:28:57.910 に答える