CodeGym /Java Blog /무작위의 /더 나은 조합: Java와 Thread 클래스. 6부 — 발사!
John Squirrels
레벨 41
San Francisco

더 나은 조합: Java와 Thread 클래스. 6부 — 발사!

무작위의 그룹에 게시되었습니다

소개

스레드는 흥미로운 것입니다. 이전 리뷰에서 멀티스레딩을 구현하는 데 사용할 수 있는 몇 가지 도구를 살펴보았습니다. 우리가 할 수 있는 다른 흥미로운 일이 무엇인지 봅시다. 이 시점에서 우리는 많은 것을 알고 있습니다. 예를 들어 " Better together: Java and the Thread class. Part I — Threads of execution "에서 우리는 Thread 클래스가 실행 스레드를 나타낸다는 것을 알고 있습니다. 스레드가 어떤 작업을 수행한다는 것을 알고 있습니다. 작업에서 를 수행하려면 run스레드를 로 표시해야 합니다 Runnable. 기억하기 위해 Tutorialspoint Online Java Compiler를더 나은 조합: Java와 Thread 클래스.  6부 — 발사!  - 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();
}
또한 자물쇠라는 것이 있다는 것도 알고 있습니다. " Better together: Java and the Thread class. Part II — Synchronization 에서 이에 대해 배웠습니다 . 한 스레드가 잠금을 획득하면 잠금을 획득하려는 다른 스레드는 잠금이 해제될 때까지 기다려야 합니다.

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();
	}
}
우리가 할 수 있는 다른 흥미로운 일들에 대해 이야기할 시간이라고 생각합니다.

세마포어

동시에 실행할 수 있는 스레드 수를 제어하는 ​​가장 간단한 방법은 세마포어입니다. 철도 신호와 같습니다. 녹색은 진행을 의미합니다. 빨간색은 대기를 의미합니다. 세마포어에서 무엇을 기다립니까? 입장. 액세스 권한을 얻으려면 획득해야 합니다. 액세스 권한이 더 이상 필요하지 않으면 이를 제공하거나 해제해야 합니다. 이것이 어떻게 작동하는지 봅시다. 클래스 를 가져와야 합니다 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

다음 메커니즘은 CountDownLatch. 당연히 이것은 카운트다운이 있는 걸쇠입니다. 여기서 클래스에 대한 적절한 import 문이 필요합니다 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은 카운트다운을 "0까지 역순으로 숫자를 세는 행위"라고 정의합니다. 그런 다음 래치에 지시합니다 await(). 즉, 카운터가 0이 될 때까지 기다리십시오. 흥미롭게도 이것은 일회성 카운터입니다. Java 설명서에는 "스레드가 이러한 방식으로 반복적으로 카운트 다운해야 하는 경우 대신 CyclicBarrier를 사용하십시오"라고 나와 있습니다. 즉, 재사용 가능한 카운터가 필요한 경우 다른 옵션이 필요합니다 CyclicBarrier.

CyclicBarrier

이름에서 알 수 있듯이 CyclicBarrier"재사용 가능한" 장벽입니다. 클래스 를 가져와야 합니다 java.util.concurrent.CyclicBarrier. 예를 살펴보겠습니다.

public static void main(String[] args) throws InterruptedException {
	Runnable action = () -> System.out.println("On your mark!");
	CyclicBarrier barrier = new CyclicBarrier(3, action);
	Runnable task = () -> {
		try {
			barrier.await();
			System.out.println("Finished");
		} catch (BrokenBarrierException | InterruptedException e) {
			e.printStackTrace();
		}
	};
	System.out.println("Limit: " + barrier.getParties());
	for (int i = 0; i < 3; i++) {
		new Thread(task).start();
	}
}
보시다시피 스레드는 await메서드를 실행합니다. 즉, 대기합니다. 이 경우 장벽 값이 감소합니다. barrier.isBroken()카운트다운이 0에 도달하면 장벽이 깨진( ) 것으로 간주됩니다 . 장벽을 재설정하려면 없는 reset()메서드를 호출해야 합니다 .CountDownLatch

교환기

다음 메커니즘은 Exchanger입니다. 이러한 맥락에서 Exchange는 변경 사항이 교환되거나 교환되는 동기화 지점입니다. 예상대로 an은 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() + " exchanged with " + withThreadName);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	};
	new Thread(task).start();
	new Thread(task).start();
}
여기서 우리는 두 개의 스레드를 시작합니다. 그들 각각은 교환 방법을 실행하고 다른 스레드도 교환 방법을 실행하기를 기다립니다. 이렇게 하면 스레드가 전달된 인수를 교환합니다. 흥미로운. 뭔가 생각나지 않나요? SynchronousQueue의 중심에 있는 를 연상시킵니다 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");
}
이 예는 새 스레드가 시작될 때 대기열이 비어 있기 때문에 대기한다는 것을 보여줍니다. 그런 다음 메인 스레드는 "Message" 문자열을 대기열에 넣습니다. 또한 이 문자열이 대기열에서 수신될 때까지 중지됩니다. 이 항목에 대한 자세한 내용은 " SynchronousQueue vs Exchanger "를 참조하십시오 .

페이저

우리는 마지막을 위해 최선을 다했습니다 — Phaser. 클래스 를 가져와야 합니다 java.util.concurrent.Phaser. 간단한 예를 살펴보겠습니다.

public static void main(String[] args) throws InterruptedException {
        Phaser phaser = new Phaser();
        // By calling the register method, we register the current (main) thread as a party
        phaser.register();
        System.out.println("Phasecount is " + phaser.getPhase());
        testPhaser(phaser);
        testPhaser(phaser);
        testPhaser(phaser);
        // After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
        Thread.sleep(3000);
        phaser.arriveAndDeregister();
        System.out.println("Phasecount is " + phaser.getPhase());
    }

    private static void testPhaser(final Phaser phaser) {
        // We indicate that there will be a +1 party on the Phaser
        phaser.register();
        // Start a new thread
        new Thread(() -> {
            String name = Thread.currentThread().getName();
            System.out.println(name + " arrived");
            phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
            System.out.println(name + " after passing barrier");
        }).start();
    }
이 예는 를 사용할 때 Phaser등록 수가 장벽에 도착한 수와 일치할 때 장벽이 깨지는 것을 보여줍니다. 이 GeeksforGeeks 기사를Phaser 읽으면 더 친숙해질 수 있습니다 .

요약

이 예제에서 알 수 있듯이 스레드를 동기화하는 방법에는 여러 가지가 있습니다. 이전에는 멀티스레딩의 측면을 기억해 보았습니다. 이 시리즈의 이전 기사가 도움이 되었기를 바랍니다. 어떤 사람들은 멀티스레딩으로 가는 길은 "Java Concurrency in Practice"라는 책에서 시작된다고 말합니다. 2006년에 출간되었지만 사람들은 이 책이 상당히 기초적이며 오늘날에도 여전히 관련이 있다고 말합니다. 예를 들어 여기에서 토론을 읽을 수 있습니다. "Java Concurrency In Practice"는 여전히 유효합니까? . 토론의 링크를 읽는 것도 유용합니다. 예를 들어 The Well-Grounded Java Developer 라는 책에 대한 링크가 있으며 특히 4장. 최신 동시성 을 언급할 것입니다 . 이 주제에 대한 전체 리뷰도 있습니다."Java Concurrency in Practice"는 Java 8 시대에도 여전히 유효합니까? 이 기사는 또한 이 주제를 진정으로 이해하기 위해 읽어야 할 다른 내용에 대한 제안도 제공합니다. 그런 다음 OCA/OCP Java SE 8 Programmer Practice Tests 와 같은 훌륭한 책을 볼 수 있습니다 . 두 번째 약어인 OCP(Oracle Certified Professional)에 관심이 있습니다. "20장: Java 동시성"에서 테스트를 찾을 수 있습니다. 이 책에는 질문과 답변이 함께 설명되어 있습니다. 예: 더 나은 조합: Java와 Thread 클래스.  6부 — 발사!  - 삼많은 사람들이 이 질문이 방법 암기의 또 다른 예라고 말하기 시작할 수 있습니다. 한편으로는 그렇습니다. 반면 에 ExecutorService. Executor그리고ExecutorRunnable스레드가 생성되는 방식을 단순히 숨기기 위한 것이지만 스레드를 실행하는 주요 방법, 즉 새 스레드에서 개체를 시작하는 방법은 아닙니다 . 이것이 없는 이유입니다 . execute(Callable)에서 객체 를 반환할 수 있는 메서드를 단순히 추가하기 때문 입니다. 물론 메서드 목록을 외울 수도 있지만 클래스 자체의 특성에 대한 지식을 바탕으로 답을 만드는 것이 훨씬 쉽습니다. 다음은 주제에 대한 몇 가지 추가 자료입니다. ExecutorServiceExecutorsubmit()Future 더 나은 조합: Java와 Thread 클래스. 1부 — 실행 스레드 Java와 Thread 클래스를 함께 사용하면 더 좋습니다. 2부 — 동기화 함께 하면 더 좋습니다: Java와 Thread 클래스. 3부 — 상호 작용 더 나은 조합: Java와 Thread 클래스. 4부 — 호출 가능, 미래 및 친구 더 나은 조합: Java 및 Thread 클래스. 파트 V — 집행자, ThreadPool, 포크/조인
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION