3

以下のコードは、Java でのバトンの受け渡しプログラムの一部です。

メイン P1(ライター) P2(ライター) P3(リーダー) P4(リーダー)

主要();

package ReadersPreference;

import java.util.concurrent.Semaphore;

/**
  * * @author me
 */
public class Main {

public static void main(String[] args) {


    AnyData x = new AnyData(5.7);//gives writers something to write, 
                                   //readers something to read
    Semaphore e = new Semaphore(1);//control entry
    Semaphore r = new Semaphore(0);//used to delay readers
    Semaphore w = new Semaphore(0);//used to delay writers

    int nr = 0;//readers active
    int nw = 0;//writers active

    int dr = 0;//readers waiting
    int dw = 0;//writers waiting

    P1 r1 = new P1(e, r, w, x, nr, nw, dr, dw); // #reader thread 1
    P2 r2 = new P2(e, r, w, x, nr, nw, dr, dw); // #reader thread 2
    P5 r3 = new P5(e, r, w, x, nr, nw, dr, dw); // #reader thread 3
    P6 r4 = new P6(e, r, w, x, nr, nw, dr, dw); // #reader thread 4

    P3 w1 = new P3(e, r, w, x, nr, nw, dr, dw); // #writer thread 1
    P4 w2 = new P4(e, r, w, x, nr, nw, dr, dw); // #writer thread 2

System.out.println("threads commanded to start");

r1.start();     // calls run() method in Thread
r2.start();
r3.start();     
r4.start();   
w1.start();
w2.start();
}//end of main
}//end of class

読者のプロセス

package ReadersPreference;

import java.util.concurrent.Semaphore;

public class P1 extends Thread {

private Semaphore e;
private Semaphore r;
private Semaphore w;
private AnyData pillarbox;
private int nw;
private int nr;
private int dr;
private int dw;

public P1(Semaphore e, Semaphore r, Semaphore w, AnyData pbox,
        int nw, int nr, int dr, int dw) {

    this.nw = nw;
    this.nr = nr;
    this.dr = dr;
    this.dw = dw;

    this.e = e;
    this.r = r;
    this.w = w;
    pillarbox = pbox;
}// end of constructor

public void run() {

PERFORM OPERATIONS          

}// end of run method
}// end of class

今、私の出力によると、それはうまくいくようです。しかし、私の講師は 2 つの大きな欠点を指摘しています。1 つは、カウンター (nr、nw、dr、dw) が参照ではなく値によって渡されることです。各スレッドは、共有変数を操作するのではなく、データの独自のコピーをチェックすることを意味します。これにより、プログラムが正常に動作しなくなります。

私は値と参照による受け渡しについて多くのことを読んできましたが、最初は頭が腫れているように感じました。今ではほとんど理解していると思いますが、まだ解決策が見られません。セマフォがすでに参照であるため、セマフォが機能する理由を理解できます (むしろ、渡される値は参照です)。カウンターの問題が正確にどこにあるのか、またはそれを解決する方法がわかりません。

彼がスレッドと言うとき、彼はプロセス クラス (コンストラクター/アルゴリズム) またはメイン クラスのインスタンス化、またはその両方を指しますか?

プリミティブをオブジェクトとして共有することを読んだことに基づいて、私が思いついた解決策に最も近いのは次のとおりです。

public void setCounters(int nr){nr = newNR;}   

繰り返しますが、私はそれを実装する方法について漠然としています

2 つ目の重大な欠陥と思われるのは、2 つ (リーダー/ライター) を作成して必要な数のオブジェクトを使用するのではなく、いくつかのプロセス クラス ファイルを作成し、それぞれをスレッドとして実行したことです。私はこれが何を意味するのかあいまいですが、出力の印刷ステートメントが各プロセスに固有であることを考慮して、検証のために出力でそれぞれを一意に識別します。目的のために行われたときに、なぜこの方法が欠陥と見なされるのでしょうか。リーダー/ライターへのソリューションに影響しますか?

4

2 に答える 2

2

始める前に、良い変数名は常にコメントよりも優れています。このルールを守れば、間違いはありません。コードのどこかであなたの変数に出くわしたと想像してください。eクラスの一番上までスクロールし、コメントを読んでその目的を確認し、元の場所に戻る必要があります。これにより、コードがほとんど読めなくなります...

最初の問題は、intどちらがプリミティブ型であるかを使用していることです。これは値によって渡されます。@Marcinのソリューションはスレッドセーフではありません。このようなint++ことを行うと、複数のスレッドから呼び出されたときに、あらゆる種類の奇妙なこと (インクリメントされない、間違った値を返すなど) が発生する可能性があります。マルチスレッド操作では常にスレッドセーフなオブジェクトを使用してください。

@Marcinが示唆するように、データをクラスにラップしてコードの量を減らすことができます。

public class SharedData<T> {

    private final T data;
    private final Semaphore entryControl = new Semaphore(1);
    private final Semaphore readerDelay = new Semaphore(0);
    private final Semaphore writerDelay = new Semaphore(0);
    private final AtomicInteger activeReaders = new AtomicInteger(0);
    private final AtomicInteger activeWriters = new AtomicInteger(0);
    private final AtomicInteger waitingReaders = new AtomicInteger(0);
    private final AtomicInteger waitingWriters = new AtomicInteger(0);

    public SharedData(final T data) {
        this.data = data;
    }
    //getters
}

私はこれをジェネリック クラスにしましたが、ジェネリックを削除して、必要に応じて「データ」を「オブジェクト」にすることができます。ジェネリックはタイプ セーフを提供するため、こちらをお読みください

クラスはAtomicIntegerオブジェクトを使用します。これは、次のようなアトミック操作を可能にするスレッドセーフな整数です。これにより、単一のgetAndSet(int newValue)値にアクセスするときにスレッドセーフの問題が発生する可能性がなくなりますが、2 つの値にアクセスする必要があるように見えます。これはまだスレッドセーフではないため、次の行に沿って、データ クラスにいくつかのメソッドを追加することができます。

public synchronized void makeReaderActive() {
    //perform checks etc
    waitingReaders.decrementAndGet();
    activeReaders.incrementAndGet();
}

そうしないと、リーダーがデクリメントしてから、インクリメントされる前にwaitingReaders何かが読み取られる可能性activeReadersがあります。

それはあなたの最初のクエリを扱っていると思います。今あなたの2番目に。先生は、インスタンスを作成するのではなく、いくつかのプロセス クラス ファイルを作成したと言っています。これは、同じファイルをコピーして別のもの ( 、 など) を呼び出して貼り付けたためですP1P2これは Java の動作ではありません。Semaphoresを作成するコードを検討してください。

Semaphore e = new Semaphore(1);//control entry
Semaphore r = new Semaphore(0);//used to delay readers

2 つのインスタンスを作成した 1 つのクラス ファイル (Semaphore.class) があります。SemaphoreJDK のクラスを別のファイルにコピーして作成する必要はありませんでした。これを行う必要はありませんでした:

Semaphore1 e = new Semaphore1(1);//control entry
Semaphore2 r = new Semaphore2(0);//used to delay readers

そう; あなたの例では、いくつかのReaderプロセスといくつかのプロセスがあると仮定しますWriter。次に、2 つのクラスが必要です。

public class MyReader implements Callable<Void> {

    private final String name;
    private final SharedData sharedData;

    public MyReader(final String name, final SharedData sharedData) {
        this.name = name;
        this.sharedData = sharedData;
    }

    @Override
    public Void call() {
        //do stuff
        return null;
    }
}

public class MyWriter implements Callable<Void> {

    private final String name;
    private final SharedData sharedData;

    public MyWriter(final String name, final SharedData sharedData) {
        this.name = name;
        this.sharedData = sharedData;
    }

    @Override
    public Void call() {
        //do stuff
        return null;
    }
}

1 つのクラスはすべてのライターが行うことを表し、もう 1 つのクラスはすべてのリーダーが行うことを表します。Callableスレッドではなく sにしました。これにより、次のポイントに進みます。

オブジェクトを使用しないでくださいThread。これらは非常に低レベルであり、適切に管理するのが困難です。新しいExecutorServiceを使用する必要があります。したがって、mainメソッドは次のようになります。

public static void main(String[] args) {
    final SharedData<Double> sharedData = new SharedData<Double>(5.7);
    final List<Callable<Void>> myCallables = new LinkedList<Callable<Void>>();

    for (int i = 0; i < 4; ++i) {
        myCallables.add(new MyReader("reader" + i, sharedData));
    }
    for (int i = 0; i < 2; ++i) {
        myCallables.add(new MyWriter("writer" + i, sharedData));
    }

    final ExecutorService executorService = Executors.newFixedThreadPool(myCallables.size());
    final List<Future<Void>> futures;
    try {
        futures = executorService.invokeAll(myCallables);
    } catch (InterruptedException ex) {
        throw new RuntimeException(ex);
    }
    for (final Future<Void> future : futures) {
        try {
            future.get();
        } catch (InterruptedException ex) {
            throw new RuntimeException(ex);
        } catch (ExecutionException ex) {
            throw new RuntimeException(ex);
        }
    }
}

ここで何が起こっているのかを理解できるように、このコードを順を追って説明します。最初の行で、新しいSharedDataクラスの 1 つを作成します。この場合はSharedData<Double>; です。これは、data含まれる のタイプが であることを意味しDoubleます。これは潜在的に何でもかまいません。

次に、Listofを作成しますCallable。ここに作業クラスが配置されます。次に、作業クラスをListin a ループに入れます。MyReaderそれぞれに同じクラスのインスタンスを作成することに注意してMyWriterください。それぞれにクラス ファイルは必要ありません。

次に、作成したExecutorService作業クラスの数と同じサイズのスレッド プールを使用して新しいものを作成します。スレッド数が少なくなることに注意してください。その場合、各スレッドに作業クラスが割り当てられ、作業が完了すると、すべてが完了するまで新しい作業クラスが割り当てられます。私たちの場合、十分な数のスレッドがあるため、各スレッドに単純に作業クラスの 1 つが割り当てられます。

ここで、ワーク クラスinvokeAllの を渡します。これは、すべてのにを要求する場所です。このメソッドは、すべてが完了するまでブロックします。待機しているすべてのメソッドで as をスローする可能性があります。この場合、例外をスローして終了します。ListExecutorServicecallCallableInterrupedException

最後に、これがExecutorService光る場所です。返されたFutureクラスのリストをループして呼び出します。これは、その future に関連する作業の実行に問題があった場合にgetをスローします。ExecutionExceptionこの場合、例外をスローします。

Callables が type であるVoid(つまり、 として宣言されている)ことに注意してください。これは、 type であるため、 sCallable<Void>にフィルターをかけます。各プロセスからデータを返したい場合は、タイプを に変更すると、 のメソッドがこのデータを返します。FutureFuture<Void>Callable<MyData>getFuture

于 2013-03-07T10:42:03.723 に答える
0

変数を保持する構造を宣言し、個々の変数の代わりに構造を渡すことを検討してください。このようにして、すべてのスレッドが同じデータを操作できます。つまり、お互いの変更を確認します。

例:

static class Data {
    int nr = 0;
    int nw = 0;
    int dr = 0;
    int dw = 0;
}

Data次に、プリミティブの代わりに参照を渡しますnr,nw,dr,dw

例:

Data d = new Data();
P1 r1 = new P1(e, r, w, x, d);
P2 r2 = new P1(e, r, w, x, d);

のコンストラクタ宣言は次のP1ようになります。

public P1(Semaphore e, Semaphore r, Semaphore w, AnyData pbox, Data d)

Data共有相手で編集を同期するようにしてくださいsynchronized (d) {...}

また、クラスをコピーする必要がない場合もありP1,P2,P3,P4ます。ロギングの目的でそれらを区別するためにスレッドの名前を使用することを検討してください。

お役に立てれば。

于 2013-03-06T19:13:53.417 に答える