JavaRush /Java Blog /Random-JA /スレッドで Java を台無しにすることはできません: パート VI - 障壁へ!
Viacheslav
レベル 3

スレッドで Java を台無しにすることはできません: パート VI - 障壁へ!

Random-JA グループに公開済み

導入

ストリームは興味深いものです。以前のレビューでは、マルチスレッドの実装に利用可能なツールのいくつかを検討しました。他にどんな面白いことができるか見てみましょう。現時点では、私たちは多くのことを知っています。たとえば、「スレッドで Java を台無しにすることはできません: パート I - スレッド」から、スレッドがスレッドであることがわかります。スレッドが何らかのタスクを実行していることがわかります。タスクを実行できるようにしたい場合 ( run)、スレッドを特定の に指定する必要がありますRunnable覚えておくために、 Tutorialspoint Java Online Compilerスレッドで Java を台無しにすることはできません: パート VI - 障壁へ!  - 1を使用できます。

public static void main(String []args){
	Runnable task = () -> {
 		Thread thread = Thread.currentThread();
		System.out.println("Hello from " + thread.getName());
	};
	Thread thread = new Thread(task);
	thread.start();
}
また、ロックなどの概念があることも知っています。これについては、「スレッドで Java を台無しにすることはできません: パート II - 同期」で説明しています。スレッドがロックを占有すると、そのロックを占有しようとする別のスレッドは、ロックが解放されるまで強制的に待機することになります。

import java.util.concurrent.locks.*;

public class HelloWorld{
	public static void main(String []args){
		Lock lock = new ReentrantLock();
		Runnable task = () -> {
			lock.lock();
			Thread thread = Thread.currentThread();
			System.out.println("Hello from " + thread.getName());
			lock.unlock();
		};
		Thread thread = new Thread(task);
		thread.start();
	}
}
他に何が面白いかについて話し合う時期が来たと思います。

セマフォ

同時に動作できるスレッドの数を制御する最も簡単な手段は、セマフォです。鉄道の中みたいに。緑色のライトが点灯しています - できます。赤いライトが点灯しています - 待っています。セマフォに何を期待するのでしょうか? 権限。許可は英語で許可します。許可を得るには取得する必要があり、英語ではacquireとなります。そして、許可が必要なくなったら、それを手放さなければなりません、つまり、解放するか処分するか、英語で言うとリリースになります。どのように機能するかを見てみましょう。クラスをインポートする必要がありますjava.util.concurrent.Semaphore。例:

public static void main(String[] args) throws InterruptedException {
	Semaphore semaphore = new Semaphore(0);
	Runnable task = () -> {
		try {
			semaphore.acquire();
			System.out.println("Finished");
			semaphore.release();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	Thread.sleep(5000);
	semaphore.release(1);
}
ご覧のとおり、英語の単語を暗記すると、セマフォがどのように機能するかを理解できます。興味深いことに、主な条件は、セマフォ「アカウント」に正の数の許可が必要であることです。したがって、マイナスで開始できます。また、複数個リクエスト(取得)することも可能です。

カウントダウンラッチ

次の仕組みは ですCountDownLatch。英語でCountDownはカウントダウン、Latchはボルトやラッチのこと。つまり、翻訳すると、これはカウントダウン付きのラッチです。ここでは、クラスの適切なインポートが必要ですjava.util.concurrent.CountDownLatch。スタートラインに全員が集まり、全員の準備ができたら許可が出て全員が同時にスタートする、レースやレースのようなものです。例:

public static void main(String[] args) {
	CountDownLatch countDownLatch = new CountDownLatch(3);
	Runnable task = () -> {
		try {
			countDownLatch.countDown();
			System.out.println("Countdown: " + countDownLatch.getCount());
			countDownLatch.await();
			System.out.println("Finished");
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
 	}
}
英語で待つ - 期待する。つまり、私たちが最初に話しますcountDown。Google 翻訳者が言うように、カウント ダウンは「数字を逆順にゼロまで数える行為」、つまりカウントダウン アクションを実行することであり、その目的はゼロまでカウントすることです。そしてawait、カウンタ値がゼロになるまで待ちます。このようなカウンターが使い捨てであることは興味深いです。JavaDoc に記載されているように、「スレッドがこの方法で繰り返しカウントダウンする必要がある場合は、代わりに CyclicBarrier を使用してください」、つまり、再利用可能なカウントが必要な場合は、 と呼ばれる別のオプションを使用する必要がありますCyclicBarrier

サイクリックバリア

名前が示すように、CyclicBarrierこれは景気循環の障壁です。クラスをインポートする必要がありますjava.util.concurrent.CyclicBarrier。例を見てみましょう:

public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("На старт!");
	CyclicBarrier berrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			berrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + berrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
ご覧のとおり、スレッドは実行中await、つまり待機中です。この場合、バリアの値は減少します。カウントダウンがゼロになると、バリアは壊れたと見なされます ( berrier.isBroken())。バリアをリセットするには、 を呼び出す必要がありますberrier.reset()が、これは にはありませんでしたCountDownLatch

エクスチェンジャー

次の対処法は ですExchanger。英語のExchangeは交換または交換と訳されます。AExchangerは交換者、つまり交換するものです。簡単な例を見てみましょう。

public static void main(String[] args) {
	Exchanger<String> exchanger = new Exchanger<>();
	Runnable task = () -> {
		try {
			Thread thread = Thread.currentThread();
			String withThreadName = exchanger.exchange(thread.getName());
			System.out.println(thread.getName() + " обменялся с " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
ここで 2 つのスレッドを起動します。それぞれが交換メソッドを実行し、別のスレッドも交換メソッドを実行するのを待ちます。したがって、スレッドは渡された引数をスレッド間で交換します。興味深いことです。彼女はあなたに何か思い出させませんか?そして彼はSynchronousQueue、「a」の中心にあることを思い出させますcachedThreadPool。わかりやすくするために、例を示します。

public static void main(String[] args) throws InterruptedException {
	SynchronousQueue<String> queue = new SynchronousQueue<>();
	Runnable task = () -> {
		try {
			System.out.println(queue.take());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	queue.put("Message");
}
この例は、新しいスレッドを起動すると、このスレッドが待機モードになることを示しています。キューは空になります。そして、mainスレッドはテキスト「メッセージ」をキューに入れます。同時に、キューからこのテキスト要素を受け取るまで、必要な時間停止します。このトピックについては、「SynchronousQueue と Exchanger」も参照してください。

フェイザー

そして最後に、最も嬉しいこと - Phaser。クラスをインポートする必要がありますjava.util.concurrent.Phaser。簡単な例を見てみましょう。

public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // Вызывая метод register, мы регистрируем текущий поток (main) How участника
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // Через 3 секунды прибываем к барьеру и снимаемся регистрацию. Кол-во прибывших = кол-во регистраций = пуск
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // Говорим, что будет +1 участник на Phaser
        phaser.register();
        // Запускаем новый поток
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); //threads register arrival to the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
Phaserこの例は、 「a」を使用した場合、登録数とバリアへの到着数が一致したときに バリアが破られることを示しています。Phaser詳細については、ハブの記事「New Phaser synchronizer 」を参照してください。

結果

例からわかるように、スレッドを同期するにはさまざまな方法があります。先ほど、マルチスレッドについて思い出そうとしましたが、前の部分がお役に立てば幸いです。マルチスレッドへの道は、『Java Concurrency in Practice』という本から始まると言われています。この本は 2006 年に出版されましたが、人々はこの本が非常に基本的でありながら、依然としてパンチを持っていると反応しています。たとえば、「Is Java Concurrency In Practice Still valid?」で議論を読むことができます。ディスカッションのリンクを読むことも役立ちます。たとえば、書籍「The Well-Grounded Java Developer 」へのリンクがあり、その中で「第 4 章. 最新の同時実行性」に注目する価値があります。同じトピックに関する別の全体的なレビューがあります。「Java の同時実行は Java 8 の時代でもまだ有効ですか」。また、このトピックを本当に理解するために他に何を読むべきかについてのヒントも記載されています。その後、「 OCA OCP JavaSE 8 Programmer Practice Tests 」などの素晴らしい本を詳しく見ることができます。私たちは 2 番目の部分、つまり OCP に興味があります。そして「∫」にはテストがあります。この本には、問題と解答の両方が解説付きで掲載されています。例: スレッドで Java を台無しにすることはできません: パート VI - 障壁へ!  - 3これは単なるメソッドの暗記にすぎないと多くの人が言い始めるかもしれません。一方では、そうです。一方、この質問は、ExecutorServiceこれが一種の「アップグレード」であることを思い出すことで答えることができますExecutor。またExecutor、これは単にスレッドを作成する方法を隠すことを目的としていますが、スレッドを実行する主な方法、つまり新しいスレッドで実行する方法を隠すことは目的ではありませんRunnable。したがって、execute(Callable)いいえ、なぜなら 単にを返すExecutorServiceメソッドExecutorを追加しただけです。ご覧のとおり、メソッドのリストを記憶することはできますが、クラス自体の性質を知っていれば、推測するのがはるかに簡単になります。さて、このトピックに関するいくつかの追加資料: submitFuture #ヴィアチェスラフ
コメント
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION