JavaRush /Java Blog /Random-KO /스레드로 Java를 망칠 수는 없습니다: 파트 VI - 장벽을 향해!
Viacheslav
레벨 3

스레드로 Java를 망칠 수는 없습니다: 파트 VI - 장벽을 향해!

Random-KO 그룹에 게시되었습니다

소개

스트림은 흥미로운 것입니다. 이전 리뷰에서는 멀티스레딩 구현에 사용할 수 있는 몇 가지 도구를 살펴보았습니다. 우리가 할 수 있는 또 다른 흥미로운 일이 무엇인지 살펴보겠습니다. 이 시점에서 우리는 많은 것을 알고 있습니다. 예를 들어, " 스레드를 사용하면 Java를 망칠 수 없습니다: 1부 - 스레드 "에서 우리는 스레드가 스레드라는 것을 알고 있습니다. 우리는 스레드가 어떤 작업을 수행하고 있다는 것을 알고 있습니다. 작업이 ( )를 실행할 수 있도록 하려면 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를 망칠 수는 없습니다: 2부 - 동기화 " 에서 읽었습니다 . 스레드는 잠금을 점유할 수 있으며 잠금을 점유하려는 다른 스레드는 잠금이 해제될 때까지 기다려야 합니다.
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();
	}
}
이제 우리가 할 수 있는 흥미로운 일에 대해 이야기할 때가 된 것 같습니다.

세마포어

동시에 작동할 수 있는 스레드 수를 제어하는 ​​가장 간단한 방법은 세마포어입니다. 철도처럼. 녹색 표시등이 켜져 있습니다. 가능합니다. 빨간불이 켜져 있습니다. 기다리고 있습니다. 우리는 세마포어에서 무엇을 기대하는가? 권한. 영어로 허가 - 허가. 허가를 얻으려면 허가를 받아야 하며, 이는 영어로 취득됩니다. 그리고 허가가 더 이상 필요하지 않을 때 우리는 그것을 포기해야 합니다. 즉, 해제하거나 제거해야 합니다. 영어로 release가 됩니다. 그것이 어떻게 작동하는지 봅시다. 클래스를 가져와야 합니다 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);
}
보시다시피 영어 단어를 외우면 세마포어가 어떻게 작동하는지 이해합니다. 흥미롭게도 주요 조건은 세마포어 "계정"에 양수 개수의 허가가 있어야 한다는 것입니다. 따라서 마이너스로 시작할 수 있습니다. 그리고 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. 구글 번역기에서 말하는 것처럼 카운트다운은 "숫자를 역순으로 0까지 세는 행위", 즉 0까지 세는 것을 목적으로 하는 카운트다운 동작을 수행하는 것입니다. 그런 다음 await카운터 값이 0이 될 때까지 기다리십시오. 이러한 카운터가 일회용이라는 점이 흥미롭습니다. 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()카운트다운이 0에 도달하면 장벽이 깨진 것으로 간주됩니다( ). berrier.reset()장벽을 재설정하려면 에서 누락된 를 호출해야 합니다 CountDownLatch.

교환기

다음 치료법은 입니다 Exchanger. 영어에서의 교환은 교환 또는 교환으로 번역됩니다. A Exchanger는 교환자, 즉 그들이 교환하는 어떤 것입니다. 간단한 예를 살펴보겠습니다.
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();
}
여기서는 두 개의 스레드를 시작합니다. 그들 각각은 교환 방법을 실행하고 다른 스레드도 교환 방법을 실행할 때까지 기다립니다. 따라서 스레드는 전달된 인수를 서로 교환합니다. 흥미로운 것. 그녀는 당신에게 아무것도 생각나지 않나요? 그리고 그는 ' SynchronousQueuea 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 Vs 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 동기화 장치 "의 기사에서 확인할 수 있습니다 .

결과

예제에서 볼 수 있듯이 스레드를 동기화하는 방법에는 여러 가지가 있습니다. 이전에 멀티스레딩에 관해 기억해보려고 했는데 이전 부분이 도움이 되었기를 바랍니다. 멀티스레딩의 길은 『Java Concurrency in Practice』라는 책에서 시작된다고 합니다. 2006년에 출간됐음에도 불구하고 사람들은 이 책이 꽤 기초적이고 여전히 강렬하다는 반응을 보이고 있다. 예를 들어, " Is Java Concurrency In Practice still valid? " 라는 토론을 읽을 수 있습니다 . 토론의 링크를 읽는 것도 도움이 됩니다. 예를 들어, " 4장. 현대 동시성 " 에 주목할 만한 " The Well-Grounded Java Developer " 책에 대한 링크가 있습니다 . 동일한 주제에 대한 또 다른 전체 리뷰가 있습니다: " Java 8 시대에도 실제로 Java 동시성이 여전히 관련이 있습니까 ? ". 또한 주제를 실제로 이해하기 위해 읽어야 할 다른 내용에 대한 팁도 있습니다. 그런 다음 " OCA OCP JavaSE 8 프로그래머 연습 테스트 " 와 같은 훌륭한 책을 자세히 살펴볼 수 있습니다 . 우리는 두 번째 부분, 즉 OCP에 관심이 있습니다. 그리고 "∫"에는 테스트가 있습니다. 이 책에는 질문과 답변이 모두 설명과 함께 담겨 있습니다. 예를 들면 다음과 같습니다. 많은 사람들은 이것이 단지 방법의 또 다른 암기라고 말하기 시작할 것입니다. 한편으로는 그렇습니다. 반면에, 이 질문은 이것이 일종의 "업그레이드"라는 점을 기억함으로써 답할 수 있습니다 . 그리고 이는 단순히 스레드를 생성하는 방법을 숨기기 위한 것이지만 스레드를 실행하는 주요 방법, 즉 새 스레드에서 실행하는 방법은 아닙니다 . 그러므로 아니, 왜냐하면 그들은 단지 를 반환할 수 있는 메서드 를 추가했을 뿐입니다 . 보시다시피 메소드 목록을 외울 수 있지만 클래스 자체의 특성을 알면 추측하기가 훨씬 쉽습니다. 음, 주제에 대한 몇 가지 추가 자료: 스레드로 Java를 망칠 수는 없습니다: 파트 VI - 장벽을 향해!  - 삼ExecutorServiceExecutorExecutorRunnableexecute(Callable)ExecutorServiceExecutorsubmitFuture #비아체슬라프
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION