1. 소개
이 예제에서는 동시 프로그램 테스트의 몇 가지 기본 사항을 다룰 것입니다. 우리는 주로 스레드 기반 동시성과 테스트에서 제시하는 문제에 중점을 둘 것입니다.
또한 이러한 문제 중 일부를 해결하고 Java에서 다중 스레드 코드를 효과적으로 테스트하는 방법을 이해합니다.
2. 동시 프로그래밍
동시 프로그래밍은 많은 계산을 더 작고 상대적으로 독립적인 계산으로 나누는 프로그래밍을 말합니다 .
이 연습의 목적은 이러한 작은 계산을 동시에, 심지어 병렬로 실행하는 것입니다. 이를 달성하는 방법에는 여러 가지가 있지만 목표는 항상 프로그램을 더 빠르게 실행하는 것입니다.
2.1. 스레드와 동시 프로그래밍
프로세서가 그 어느 때보다 많은 코어를 포장하면서 동시 프로그래밍은 이를 효율적으로 활용하기 위한 최전선에 있습니다. 그러나 동시성 프로그램은 설계, 작성, 테스트 및 유지 관리가 훨씬 더 어렵다는 사실이 남아 있습니다 . 따라서 동시 프로그램에 대한 효과적이고 자동화된 테스트 사례를 작성할 수 있다면 이러한 문제의 상당 부분을 해결할 수 있습니다.
그렇다면 동시 코드에 대한 테스트를 작성하는 것이 그렇게 어려운 이유는 무엇입니까? 이를 이해하려면 프로그램에서 동시성을 달성하는 방법을 이해해야 합니다. 가장 널리 사용되는 동시 프로그래밍 기술 중 하나는 스레드를 사용하는 것입니다.
이제 스레드는 기본 운영 체제에 의해 예약되는 경우 기본이 될 수 있습니다. 또한 런타임에 의해 직접 예약되는 녹색 스레드로 알려진 것을 사용할 수도 있습니다.
2.2. 동시 프로그램 테스트의 어려움
어떤 종류의 쓰레드를 사용하든 쓰레드를 사용하기 어렵게 만드는 것은 쓰레드 통신이다. 스레드는 포함하지만 스레드 통신은 포함하지 않는 프로그램을 실제로 작성할 수 있다면 이보다 더 좋은 것은 없습니다! 보다 현실적으로 스레드는 일반적으로 통신해야 합니다. 이를 달성하는 방법에는 공유 메모리와 메시지 전달의 두 가지가 있습니다.
동시 프로그래밍과 관련된 대부분의 문제는 공유 메모리와 함께 기본 스레드를 사용하는 데서 발생합니다 . 이러한 프로그램을 테스트하는 것도 같은 이유로 어렵습니다. 공유 메모리에 액세스할 수 있는 다중 스레드는 일반적으로 상호 배제가 필요합니다. 우리는 일반적으로 잠금을 사용하는 일부 보호 메커니즘을 통해 이를 달성합니다.
그러나 이것은 몇 가지 예를 들면 경쟁 조건, 라이브 잠금, 교착 상태 및 스레드 기아와 같은 문제의 호스트로 여전히 이어질 수 있습니다 . 게다가, 네이티브 스레드의 경우 스레드 스케줄링이 완전히 비결정적이기 때문에 이러한 문제는 간헐적입니다.
따라서 이러한 문제를 결정적인 방식으로 감지할 수 있는 동시 프로그램에 대한 효과적인 테스트를 작성하는 것은 실제로 어려운 일입니다!
2.3. 스레드 인터리빙의 해부
네이티브 스레드는 운영 체제에서 예기치 않게 예약될 수 있다는 것을 알고 있습니다. 이러한 스레드가 공유 데이터에 액세스하고 수정하는 경우 흥미로운 스레드 인터리빙이 발생 합니다. 이러한 인터리빙 중 일부는 완전히 허용될 수 있지만 다른 일부는 최종 데이터를 바람직하지 않은 상태로 남길 수 있습니다.
예를 들어 보겠습니다. 모든 스레드에 의해 증가되는 전역 카운터가 있다고 가정합니다. 처리가 끝나면 이 카운터의 상태가 실행된 스레드 수와 정확히 같게 하고 싶습니다.
private int counter;
public void increment() {
counter++;
}
이제 Java에서 기본 정수 를 증가시키는 것은 원자적 연산이 아닙니다 . 값을 읽고, 늘리고, 마지막으로 저장하는 것으로 구성됩니다. 여러 스레드가 동일한 작업을 수행하는 동안 많은 인터리빙이 발생할 수 있습니다.
이 특정 인터리빙은 완전히 수용 가능한 결과를 생성하지만 다음은 어떻습니까?
이것은 우리가 예상한 것이 아닙니다. 이제 이보다 훨씬 더 복잡한 코드를 실행하는 수백 개의 스레드를 상상해 보십시오. 이것은 스레드가 인터리브할 상상할 수 없는 방식을 야기할 것입니다.
이 문제를 방지하는 코드를 작성하는 방법에는 여러 가지가 있지만 이 사용방법(예제)의 주제는 아닙니다. 잠금을 사용한 동기화는 일반적인 것 중 하나이지만 경쟁 조건과 관련된 문제가 있습니다.
3. 다중 스레드 코드 테스트
이제 다중 스레드 코드를 테스트할 때의 기본적인 문제를 이해했으므로 이를 극복하는 방법을 살펴보겠습니다. 우리는 간단한 사용 사례를 구축하고 가능한 한 동시성과 관련된 많은 문제를 시뮬레이션하려고 노력할 것입니다.
가능한 모든 것의 개수를 유지하는 간단한 클래스를 정의하는 것으로 시작하겠습니다.
public class MyCounter {
private int count;
public void increment() {
int temp = count;
count = temp + 1;
}
// Getter for count
}
이것은 겉보기에 무해한 코드 조각이지만 스레드로부터 안전하지 않다는 것을 이해하는 것은 어렵지 않습니다 . 이 클래스를 사용하여 동시 프로그램을 작성하면 결함이 있을 수 있습니다. 여기서 테스트의 목적은 그러한 결함을 식별하는 것입니다.
3.1. 비동시 부품 테스트
경험에 따르면 항상 코드를 동시 동작에서 격리하여 테스트하는 것이 좋습니다 . 이는 동시성과 관련이 없는 코드의 다른 결함이 없음을 합리적으로 확인하기 위한 것입니다. 어떻게 할 수 있는지 봅시다.
@Test
public void testCounter() {
MyCounter counter = new MyCounter();
for (int i = 0; i < 500; i++) {
counter.increment();
}
assertEquals(500, counter.getCount());
}
여기에는 별 내용이 없지만 이 테스트는 최소한 동시성이 없을 때 작동한다는 확신을 줍니다.
3.2. 동시성 테스트 첫 시도
이번에는 동시 설정에서 동일한 코드를 다시 테스트해 보겠습니다. 여러 스레드를 사용하여 이 클래스의 동일한 인스턴스에 액세스하고 어떻게 작동하는지 살펴보겠습니다.
@Test
public void testCounterWithConcurrency() throws InterruptedException {
int numberOfThreads = 10;
ExecutorService service = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
MyCounter counter = new MyCounter();
for (int i = 0; i < numberOfThreads; i++) {
service.execute(() -> {
counter.increment();
latch.countDown();
});
}
latch.await();
assertEquals(numberOfThreads, counter.getCount());
}
이 테스트는 여러 스레드가 있는 공유 데이터에 대해 작업하려고 하므로 합리적입니다. 스레드 수를 10과 같이 낮게 유지하면 거의 항상 통과하는 것을 알 수 있습니다. 흥미롭게도 스레드 수를 늘리기 시작하면(예: 100) 테스트가 대부분 실패하기 시작하는 것을 볼 수 있습니다 .
3.3. 동시성을 사용한 더 나은 테스트 시도
이전 테스트에서 우리 코드가 스레드로부터 안전하지 않다는 것이 밝혀졌지만 이 테스트에는 문제가 있습니다. 이 테스트는 기본 스레드가 비결정적 방식으로 인터리브하기 때문에 결정적이지 않습니다. 우리는 우리 프로그램에서 이 테스트에 의존할 수 없습니다.
우리에게 필요한 것은 스레드의 인터리빙을 제어하여 훨씬 적은 수의 스레드로 결정론적인 방식으로 동시성 문제 를 드러낼 수 있는 방법입니다. 테스트할 코드를 약간 조정하여 시작하겠습니다.
public synchronized void increment() throws InterruptedException {
int temp = count;
wait(100);
count = temp + 1;
}
여기에서 메서드를 동기화 하고 메서드 내에서 두 단계 사이에 대기를 도입했습니다. 동기화 하나의 스레드 만이 수정할 수있는 키워드를 보장하지만 카운트 한 번에 변수를하고, 대기 소개하고 각 스레드 실행 사이에 지연.
테스트하려는 코드를 반드시 수정할 필요는 없습니다. 그러나 스레드 스케줄링에 영향을 줄 수 있는 방법이 많지 않기 때문에 이 방법을 사용합니다.
나중 섹션에서 코드를 변경하지 않고 이 작업을 수행하는 방법을 살펴보겠습니다.
이제 이전에 했던 것처럼 이 코드를 유사하게 테스트해 보겠습니다.
@Test
public void testSummationWithConcurrency() throws InterruptedException {
int numberOfThreads = 2;
ExecutorService service = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(numberOfThreads);
MyCounter counter = new MyCounter();
for (int i = 0; i < numberOfThreads; i++) {
service.submit(() -> {
try {
counter.increment();
} catch (InterruptedException e) {
// Handle exception
}
latch.countDown();
});
}
latch.await();
assertEquals(numberOfThreads, counter.getCount());
}
여기서 우리는 단지 두 개의 스레드로 이것을 실행하고 있으며 우리가 놓치고 있는 결함을 얻을 수 있을 가능성이 있습니다. 여기서 우리가 한 것은 우리에게 영향을 줄 수 있는 특정 스레드 인터리빙을 달성하는 것입니다. 데모에는 좋지만 실용적인 목적에는 유용하지 않을 수 있습니다 .
4. 사용 가능한 테스트 도구
스레드 수가 증가함에 따라 이들이 인터리브할 수 있는 가능한 방법의 수도 기하급수적으로 늘어납니다. 그건 그들을 위해 이러한 모든 interleavings 및 테스트를 알아 내기 위해 단지 수는 없습니다 . 우리는 우리를 위해 동일하거나 유사한 노력을 수행하기 위해 도구에 의존해야 합니다. 다행히도 우리의 삶을 더 쉽게 만들어주는 몇 가지가 있습니다.
동시 코드 테스트에 사용할 수 있는 도구에는 두 가지 광범위한 범주가 있습니다. 첫 번째는 많은 스레드가 있는 동시 코드에서 상당히 높은 스트레스를 생성할 수 있게 해줍니다. 스트레스는 드문 인터리빙의 가능성을 증가시켜 결함을 찾을 가능성을 높입니다.
두 번째를 사용하면 특정 스레드 인터리빙을 시뮬레이션할 수 있으므로 보다 확실하게 결함을 찾을 수 있습니다.
4.1. 텐푸스 푸깃
템퍼 - fugit 자바 라이브러리는 쉽게 쓰기 및 테스트 동시 코드에 도움이 . 여기서는 이 라이브러리의 테스트 부분에 중점을 둘 것입니다. 여러 스레드가 있는 코드에 스트레스를 가하면 동시성과 관련된 결함을 찾을 가능성이 높아진다는 것을 앞서 보았습니다.
우리가 스스로 스트레스를 생성하는 유틸리티를 작성할 수 있지만, tempus-fugit은 동일한 스트레스를 달성하는 편리한 방법을 제공합니다.
이전에 스트레스를 생성하려고 했던 동일한 코드를 다시 살펴보고 tempus-fugit을 사용하여 동일한 결과를 얻을 수 있는 방법을 이해해 보겠습니다.
public class MyCounterTests {
@Rule
public ConcurrentRule concurrently = new ConcurrentRule();
@Rule
public RepeatingRule rule = new RepeatingRule();
private static MyCounter counter = new MyCounter();
@Test
@Concurrent(count = 10)
@Repeating(repetition = 10)
public void runsMultipleTimes() {
counter.increment();
}
@AfterClass
public static void annotatedTestRunsMultipleTimes() throws InterruptedException {
assertEquals(counter.getCount(), 100);
}
}
여기에서는 tempus-fugit에서 사용할 수 있는 두 가지 규칙을 사용합니다. 이러한 규칙은 테스트를 가로채서 반복 및 동시성과 같은 원하는 동작을 적용하는 데 도움이 됩니다. 따라서 효과적으로 테스트 중인 작업을 10개의 다른 스레드에서 각각 10번 반복합니다.
반복성과 동시성을 높일수록 동시성과 관련된 결함을 감지할 가능성이 높아집니다.
4.2. 실 위버
Thread Weaver 는 기본적 으로 다중 스레드 코드를 테스트하기 위한 Java 프레임워크입니다 . 우리는 스레드 인터리빙이 매우 예측할 수 없다는 것을 이전에 보았고 따라서 정기적인 테스트를 통해 특정 결함을 찾지 못할 수도 있습니다. 우리가 효과적으로 필요로 하는 것은 인터리브를 제어하고 가능한 모든 인터리빙을 테스트하는 방법입니다. 이것은 이전 시도에서 상당히 복잡한 작업임이 입증되었습니다.
여기서 Thread Weaver가 우리를 어떻게 도울 수 있는지 봅시다. Thread Weaver를 사용하면 방법에 대해 걱정할 필요 없이 여러 가지 방법으로 두 개의 개별 스레드 실행을 인터리빙할 수 있습니다. 또한 스레드가 인터리브하기를 원하는 방식을 세밀하게 제어할 수 있는 가능성을 제공합니다.
이전의 순진한 시도를 어떻게 개선할 수 있는지 봅시다.
public class MyCounterTests {
private MyCounter counter;
@ThreadedBefore
public void before() {
counter = new MyCounter();
}
@ThreadedMain
public void mainThread() {
counter.increment();
}
@ThreadedSecondary
public void secondThread() {
counter.increment();
}
@ThreadedAfter
public void after() {
assertEquals(2, counter.getCount());
}
@Test
public void testCounter() {
new AnnotatedTestRunner().runTests(this.getClass(), MyCounter.class);
}
}
여기에서 카운터를 증가시키려는 두 개의 스레드를 정의했습니다. Thread Weaver는 가능한 모든 인터리빙 시나리오에서 이러한 스레드로 이 테스트를 실행하려고 합니다. 아마도 인터리브 중 하나에서 결함을 얻을 수 있으며 이는 코드에서 매우 분명합니다.
4.3. 멀티스레드TC
MultithreadedTC 는 동시 응용 프로그램을 테스트하기 위한 또 다른 프레임워크입니다 . 여러 스레드에서 일련의 활동을 미세하게 제어하는 데 사용되는 메트로놈이 특징입니다. 스레드의 특정 인터리빙을 실행하는 테스트 케이스를 지원합니다. 따라서 우리는 결정론적으로 별도의 스레드에서 모든 중요한 인터리빙을 이상적으로 테스트할 수 있어야 합니다.
이제 기능이 풍부한 이 라이브러리에 대한 완전한 소개는 이 사용방법(예제)의 범위를 벗어납니다. 그러나 실행 중인 스레드 간에 가능한 인터리빙을 제공하는 테스트를 신속하게 설정하는 방법을 확실히 알 수 있습니다.
MultithreadedTC를 사용하여 코드를 보다 결정적으로 테스트하는 방법을 살펴보겠습니다.
public class MyTests extends MultithreadedTestCase {
private MyCounter counter;
@Override
public void initialize() {
counter = new MyCounter();
}
public void thread1() throws InterruptedException {
counter.increment();
}
public void thread2() throws InterruptedException {
counter.increment();
}
@Override
public void finish() {
assertEquals(2, counter.getCount());
}
@Test
public void testCounter() throws Throwable {
TestFramework.runManyTimes(new MyTests(), 1000);
}
}
여기에서 공유 카운터에서 작동하고 증가시키기 위해 두 개의 스레드를 설정합니다. 우리는 실패를 감지할 때까지 최대 천 개의 다른 인터리빙에 대해 이러한 스레드로 이 테스트를 실행하도록 MultithreadedTC를 구성했습니다.
4.4. 자바 jcstress
OpenJDK는 OpenJDK 프로젝트 작업을 위한 개발자 도구를 제공하기 위해 Code Tool Project를 유지 관리합니다. 이 프로젝트에는 Java 동시성 스트레스 테스트(jcstress)를 포함하여 몇 가지 유용한 도구가 있습니다 . 이것은 Java에서 동시성 지원의 정확성을 조사하기 위한 실험적 도구 및 테스트 모음으로 개발되고 있습니다.
이것은 실험적인 도구이지만 여전히 이를 활용하여 동시 코드를 분석하고 이와 관련된 결함에 자금을 지원하는 테스트를 작성할 수 있습니다. 이 사용방법(예제)에서 지금까지 사용한 코드를 테스트하는 방법을 살펴보겠습니다. 개념은 사용 관점에서 매우 유사합니다.
@JCStressTest
@Outcome(id = "1", expect = ACCEPTABLE_INTERESTING, desc = "One update lost.")
@Outcome(id = "2", expect = ACCEPTABLE, desc = "Both updates.")
@State
public class MyCounterTests {
private MyCounter counter;
@Actor
public void actor1() {
counter.increment();
}
@Actor
public void actor2() {
counter.increment();
}
@Arbiter
public void arbiter(I_Result r) {
r.r1 = counter.getCount();
}
}
여기에서 클래스를 어노테이션 State 로 표시했는데, 이는 여러 스레드에 의해 변경된 데이터를 보유하고 있음을 나타냅니다. 또한 다른 스레드에서 수행한 작업을 보유하는 메서드를 표시 하는 어노테이션 Actor 를 사용하고 있습니다.
마지막으로 Arbiter 어노테이션으로 표시된 메서드가 있습니다. 이 메서드는 기본적으로 모든 Actor 가 방문했을 때만 상태 를 방문합니다. 우리는 또한 우리의 기대치를 정의하기 위해 어노테이션 결과 를 사용했습니다.
전반적으로 설정은 매우 간단하고 직관적입니다. JCStressTest로 어노테이션이 달린 모든 클래스를 찾고 가능한 모든 인터리빙을 얻기 위해 여러 번 반복하여 실행 하는 프레임워크에서 제공하는 테스트 하네스를 사용하여 이를 실행할 수 있습니다 .
5. 동시성 문제를 감지하는 다른 방법
동시 코드에 대한 테스트를 작성하는 것은 어렵지만 가능합니다. 우리는 도전과제와 그것을 극복하는 대중적인 방법을 보았습니다. 그러나 테스트만으로는 가능한 모든 동시성 문제를 식별할 수 없을 수도 있습니다. 특히 더 많은 테스트를 작성하는 증분 비용이 이점을 능가하기 시작할 때 그렇습니다.
따라서 합리적인 수의 자동화된 테스트와 함께 다른 기술을 사용하여 동시성 문제를 식별할 수 있습니다. 이렇게 하면 자동화된 테스트의 복잡성에 대해 너무 깊이 들어가지 않고도 동시성 문제를 찾을 가능성이 높아집니다. 이 섹션에서 이들 중 일부를 다룰 것입니다.
5.1. 정적 분석
정적 분석 은 프로그램을 실제로 실행하지 않고 분석하는 것을 말합니다 . 자, 그런 분석이 무슨 소용이 있겠습니까? 우리는 그것에 올 것이지만 먼저 동적 분석과 어떻게 대조되는지 이해합시다. 지금까지 작성한 단위 테스트는 테스트하는 프로그램의 실제 실행과 함께 실행되어야 합니다. 이것이 우리가 주로 동적 분석이라고 부르는 것의 일부인 이유입니다.
정적 분석은 동적 분석을 대체할 수 없습니다. 그러나 코드를 실행하기 훨씬 전에 코드 구조를 검사하고 가능한 결함을 식별할 수 있는 귀중한 도구를 제공합니다. 정적 분석 차종은 경험 큐레이터하는 템플릿의 호스트의 사용 과 이해.
코드를 살펴보고 우리가 선별한 모범 사례 및 규칙과 비교하는 것은 꽤 가능하지만 더 큰 프로그램에서는 그럴듯하지 않다는 것을 인정해야 합니다. 그러나 이 분석을 수행하는 데 사용할 수 있는 몇 가지 도구가 있습니다. 대부분의 인기 있는 프로그래밍 언어에 대한 방대한 규칙을 포함하여 상당히 성숙합니다.
Java용으로 널리 사용되는 정적 분석 도구는 FindBugs 입니다. FindBugs는 "버그 패턴"의 인스턴스를 찾습니다. 버그 패턴은 종종 오류가 발생하는 코드 관용구입니다. 이것은 어려운 언어 기능, 잘못 이해된 방법 및 잘못 이해된 불변량과 같은 여러 가지 이유로 인해 발생할 수 있습니다.
FindBugs는 실제로 바이트 코드를 실행하지 않고 버그 패턴의 발생에 대해 Java 바이트 코드를 검사합니다 . 이것은 사용하기 매우 편리하고 빠르게 실행할 수 있습니다. FindBugs는 조건, 디자인 및 중복 코드와 같은 많은 범주에 속하는 버그를 보고합니다.
또한 동시성과 관련된 결함도 포함됩니다. 그러나 FindBugs는 가양성을 보고할 수 있습니다. 실제로는 더 적지만 수동 분석과 상관 관계가 있어야 합니다.
5.2. 모델 확인
모델 검사는 시스템의 유한 상태 모델이 주어진 사양을 충족하는지 여부를 확인하는 방법입니다 . 이제 이 정의가 너무 학문적으로 들릴 수 있지만 잠시 동안은 참으세요!
일반적으로 계산 문제를 유한 상태 기계로 나타낼 수 있습니다. 이것은 그 자체로 광대한 영역이지만 명확하게 정의된 시작 및 끝 상태를 사용하여 유한한 상태 집합과 이들 사이의 전환 규칙이 있는 모델을 제공합니다.
이제 사양은 모델이 올바른 것으로 간주되기 위해 어떻게 작동해야 하는지 정의합니다 . 기본적으로 이 사양은 모델이 나타내는 시스템의 모든 요구 사항을 담고 있습니다. 사양을 캡처하는 방법 중 하나는 Amir Pnueli가 개발한 시간 논리 공식을 사용하는 것입니다.
모델 검사를 수동으로 수행하는 것이 논리적으로 가능하지만 매우 비실용적입니다. 다행히도 여기에 도움이 되는 많은 도구가 있습니다. Java에 사용할 수 있는 이러한 도구 중 하나는 JPF( Java PathFinder )입니다. JPF는 NASA에서 다년간의 경험과 연구를 통해 개발되었습니다.
특히 JPF는 Java 바이트코드용 모델 검사기입니다 . 가능한 모든 방법으로 프로그램을 실행하여 가능한 모든 실행 경로를 따라 교착 상태 및 처리되지 않은 예외와 같은 속성 위반을 확인합니다. 따라서 모든 프로그램에서 동시성과 관련된 결함을 찾는 데 매우 유용할 수 있습니다.
6. 뒷이야기
지금쯤이면 다중 스레드 코드와 관련된 복잡성을 최대한 피하는 것이 가장 좋습니다 . 테스트 및 유지 관리가 더 쉬운 더 간단한 디자인으로 프로그램을 개발하는 것이 우리의 주요 목표가 되어야 합니다. 우리는 동시 프로그래밍이 현대 응용 프로그램에 종종 필요하다는 데 동의해야 합니다.
그러나 우리는 우리의 삶을 더 쉽게 만들 수 있는 동시 프로그램 을 개발하면서 몇 가지 모범 사례와 원칙을 채택할 수 있습니다 . 이 섹션에서는 이러한 모범 사례 중 일부를 살펴볼 것이지만 이 List이 완전하지 않다는 점을 명심해야 합니다!
6.1. 복잡성 감소
복잡성은 동시 요소가 없어도 프로그램을 테스트하기 어렵게 만드는 요소입니다. 이것은 동시성에 직면하여 복합적입니다. 더 간단하고 더 작은 프로그램이 추론하기 쉽고 따라서 효과적으로 테스트 하는 이유를 이해하는 것은 어렵지 않습니다 . SRP(Single Responsibility Pattern) 및 KISS(Keep It Stupid Simple)와 같이 여기에 도움이 될 수 있는 몇 가지 최고의 패턴이 있습니다.
이제 이것들은 동시 코드에 대한 테스트를 직접 작성하는 문제를 해결하지는 않지만 작업을 더 쉽게 시도할 수 있도록 합니다.
6.2. 원자 연산 고려
원자적 작업 은 서로 완전히 독립적으로 실행되는 작업입니다 . 따라서 인터리빙을 예측하고 테스트하는 어려움을 간단히 피할 수 있습니다. 비교 및 교환은 널리 사용되는 원자 명령어 중 하나입니다. 간단히 말해서 메모리 위치의 내용을 주어진 값과 비교하고 동일한 경우에만 해당 메모리 위치의 내용을 수정합니다.
대부분의 최신 마이크로프로세서는 이 명령어의 일부 변형을 제공합니다. Java는 AtomicInteger 및 AtomicBoolean 과 같은 다양한 원자 클래스를 제공하여 아래에 비교 및 스왑 명령의 이점을 제공합니다.
6.3. 불변성 수용
다중 스레드 프로그래밍에서 변경할 수 있는 공유 데이터는 항상 오류의 여지를 남깁니다. 불변성 은 인스턴스화 후 데이터 구조를 수정할 수 없는 상태를 나타냅니다 . 이것은 동시 프로그램에 대한 천국의 일치입니다. 객체가 생성된 후 상태를 변경할 수 없는 경우 경쟁 스레드는 객체에 대한 상호 배제를 신청할 필요가 없습니다. 이것은 동시 프로그램 작성 및 테스트를 크게 단순화합니다.
그러나 불변성을 선택할 수 있는 자유가 항상 있는 것은 아니지만 가능하면 선택해야 합니다.
6.4. 공유 메모리 피하기
다중 스레드 프로그래밍과 관련된 대부분의 문제는 경쟁 스레드 간에 메모리를 공유한다는 사실에 기인할 수 있습니다. 우리가 그들을 제거할 수 있다면 어떨까요! 글쎄, 우리는 여전히 스레드가 통신하기 위한 몇 가지 메커니즘이 필요합니다.
있습니다 우리에게 가능성을 제공 동시 응용 프로그램에 대한 대체 디자인 패턴 . 인기 있는 것 중 하나는 액터를 동시성의 기본 단위로 규정하는 액터 모델입니다. 이 모델에서 액터는 메시지를 전송하여 서로 상호 작용합니다.
Akka는 더 나은 동시성 프리미티브를 제공하기 위해 Actor Model 을 활용하는 Scala로 작성된 프레임워크입니다 .
7. 결론
이 예제에서는 동시 프로그래밍과 관련된 몇 가지 기본 사항을 다루었습니다. 우리는 특히 Java의 다중 스레드 동시성에 대해 자세히 논의했습니다. 우리는 특히 공유 데이터를 사용하여 그러한 코드를 테스트하는 동안 그것이 우리에게 제시하는 문제를 겪었습니다. 또한 동시 코드를 테스트하는 데 사용할 수 있는 몇 가지 도구와 기술을 살펴보았습니다.
또한 자동화된 테스트 외에 도구와 기술을 포함하여 동시성 문제를 피하는 다른 방법에 대해서도 논의했습니다. 마지막으로 동시 프로그래밍과 관련된 몇 가지 프로그래밍 모범 사례를 살펴보았습니다.
이 기사의 소스 코드는 GitHub 에서 찾을 수 있습니다 .