여러 시나리오에서 스레드 안전성을 빠르게 달성하는 방법에 대해 이야기

이 기사는 HUAWEI CLOUD Community " 5가지 트릭으로 다중 스레드 시나리오에서 스레드 안전성을 달성하는 방법? ", 저자: 자바 팅커벨.

1. 소개

현재 컴퓨터 하드웨어의 급속한 발전으로 개인용 컴퓨터의 CPU도 멀티 코어이며 이제는 일반적인 CPU 코어 수는 4 또는 8 코어입니다. 따라서 프로그램을 작성할 때 효율성을 높이고 하드웨어 기능을 최대한 활용하려면 병렬 프로그램을 작성해야 합니다. Java 언어는 인터넷 응용 프로그램의 주요 언어로 엔터프라이즈 응용 프로그램 개발에 널리 사용되며 멀티 스레딩 ( Multithreading )도 지원하지만 멀티 스레딩이 좋지만 프로그램 작성에 대한 요구 사항이 더 높습니다.

단일 스레드로 올바르게 실행될 수 있는 프로그램이 다중 스레드 시나리오에서 올바르게 실행될 수 있다는 것을 의미하지는 않습니다. 여기서 정확성은 종종 찾기가 쉽지 않으며 동시성이 일정 수에 도달해야 나타납니다. 테스트 세션에서 재현하기가 쉽지 않은 이유이기도 합니다. 따라서 다중 스레드(동시성) 시나리오에서 스레드 로부터 안전한 ( Thread-Safety ) 프로그램을 작성하는 방법은 프로그램의 정확하고 안정적인 작동을 위해 매우 중요합니다. 다음은 예제를 결합하여 Java 언어로 스레드로부터 안전한 프로그램을 구현하는 방법에 대해 설명합니다.

지각적 이해를 돕기 위해 다음과 같이 스레드 불안전성의 예가 제공됩니다.

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        counter = counter + 1;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

이 클래스에는 계산을 위한 정적 속성 카운터가 있습니다. 이 중 정적 메서드인 add()를 사용하여 카운터에 1을 더하거나 getCount() 메서드를 통해 현재 카운터 값을 얻을 수 있습니다. 단일 스레드의 경우 이 프로그램은 10번 반복하는 것과 같이 문제가 없습니다. 그러면 얻은 최종 카운트 카운터 값은 10입니다. 그러나 멀티 스레딩의 경우 이 결과가 제대로 얻어지지 않을 수도 있고, 10과 같을 수도 있고, 9와 같이 10보다 작을 수도 있습니다. 다중 스레드 테스트의 예는 다음과 같습니다.

package com.example.learn;
public class MyThread extends Thread{
    private String name ;
    public MyThread(String name){
        this.name = name ;
    }
    public void run(){
        Counter.add();
        System.out.println("Thead["+this.name+"] Count is "+  Counter.getCount());
    }
}
///
package com.example.learn;
public class Test01 {
    public static void main(String[] args) {
        for(int i=0;i<5000;i++){
            MyThread mt1 = new MyThread("TCount"+i);
            mt1.start();
        }
    }
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.18.19.20.21.

  
 

여기서 카운트 문제를 재현하기 위해 스레드 수를 상대적으로 크게 조정합니다. 여기서는 5000입니다. 이 예제를 실행하면 출력은 다음과 같을 수 있습니다.

Thead[TCount5] Count is 4
Thead[TCount2] Count is 9
Thead[TCount4] Count is 4
Thead[TCount14] Count is 10
..................................
Thead[TCount4911] Count is 4997
Thead[TCount4835] Count is 4998
Thead[TCount4962] Count is 49991.2.3.4.5.6.7.8.

  
 

참고: 다중 스레드 시나리오에서 스레드 안전하지 않은 프로그램의 출력 결과는 불확실합니다.

2, 동기화된 방법

위의 예를 바탕으로 스레드로부터 안전한 프로그램으로 만드는 가장 직접적인 방법  은 해당 메서드에 synchronized 키워드를 추가하여 동기화된 메서드로 만드는 것 입니다. 클래스, 메서드 및 코드 블록을 꾸밀 수 있습니다. 위의 카운팅 프로그램을 수정하면 코드는 다음과 같습니다.

package com.example.learn;
public class Counter {
    private static int counter = 0;
    public static int getCount(){
        return counter;
    }
    public static synchronized void add(){
        counter = counter + 1;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

프로그램을 다시 실행하면 출력은 다음과 같습니다.

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

3. 잠금 장치

또 다른 일반적인 동기화 방법은 잠금입니다.예를 들어 Java에는 재진입 잠금  ReentrantLock 이 있는데 이는 재귀 및 비 차단 동기화 메커니즘입니다.  동기 와 비교  하여 더 강력하고 유연한 잠금 메커니즘을 제공할 수 있습니다. 교착 상태 발생. 샘플 코드는 다음과 같습니다.

package com.example.learn;
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
    private  static int counter = 0;
    private static final ReentrantLock lock = new ReentrantLock(true);
    public static int getCount(){
        return counter;
    }
    public static  void add(){
        lock.lock();
        try {
            counter = counter + 1;
        } finally {
            lock.unlock();
        }
    }
}1.2.3.4.5.6.7.8.9.10.11.12.13.14.15.16.17.

  
 

프로그램을 다시 실행하면 출력은 다음과 같습니다.

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

참고: Java는 또한 읽기-쓰기 분리를 가능하게 하고 더 효율적인 읽기-쓰기 잠금 ReentrantReadWriteLock을 제공합니다.

4. 원자 개체 사용

잠금 메커니즘은 특정 성능에 영향을 미치므로 일부 시나리오에서는 잠금이 없는 방식으로 구현할 수 있습니다. Java에는 다양한 시나리오에 따라 선택할 수 있는 AtomicInteger, AtomicLong, AtomicBoolean 및 AtomicReference와 같은 Atomic 관련 원자 연산 클래스가 내장되어 있습니다. 샘플 코드는 다음과 같습니다.

package com.example.learn;
import java.util.concurrent.atomic.AtomicInteger;
public class Counter {
    private static final AtomicInteger counter = new AtomicInteger();
    public static int getCount(){
        return counter.get();
    }
    public static void add(){
        counter.incrementAndGet();
    }
}1.2.3.4.5.6.7.8.9.10.11.

  
 

프로그램을 다시 실행하면 출력은 다음과 같습니다.

......
Thead[TCount1953] Count is 4998
Thead[TCount3087] Count is 4999
Thead[TCount2425] Count is 50001.2.3.4.

  
 

5. 무상태 객체

앞서 언급한 바와 같이 쓰레드가 불안정해지는 이유 중 하나는 여러 쓰레드가 동시에 객체에 있는 데이터에 접근하여 데이터를 공유하기 때문 이다 . 안전한. 소위 stateless 방법은 동일한 입력을 제공하고 동일한 결과를 반환하는 것입니다. 샘플 코드는 다음과 같습니다.

package com.example.learn;
public class Counter {
    public static int sum (int n) {
        int ret = 0;
        for (int i = 1; i <= n; i++) {
            ret += i;
        }
        return ret;
    }
}1.2.3.4.5.6.7.8.9.10.

  
 

6. 불변 객체

앞서 언급했듯이 데이터를 여러 스레드 간에 공유해야 하고 이 데이터의 지정된 값을 변경할 수 없는 경우 읽기 전용 속성과 동일한 스레드로부터 안전합니다. Java에서 속성은 final  키워드 를 통해 수정할 수 있습니다  . 샘플 코드는 다음과 같습니다.

package com.example.learn;
public class Counter {
    public final int count ;
    public Counter (int n) {
        count = n;
    }
}1.2.3.4.5.6.7.

  
 

7. 요약

여러 스레드로부터 안전한 방법이 위에서 언급되었지만 일반적인 아이디어는 잠금 메커니즘을 통해 동기화를 달성하거나 데이터 공유를 방지하고 데이터가 여러 스레드에서 읽고 쓰는 것을 방지하는 것입니다. 또한 일부 기사에서는  volatile  수정을 변수 앞에 사용하여 동기화 메커니즘을 달성할 수 있다고 말하지만 테스트 후 반드시 테스트해야 하는 것은 아닙니다.일부 시나리오에서는 volatile  이 여전히 스레드 안전성을 보장할 수 없습니다. 위의 내용은 스레드 안전성 경험을 요약한 내용이지만 여전히 엄격한 테스트를 통해 검증해야 하며, 실제 테스트를 위한 유일한 기준은 실천입니다.

HUAWEI CLOUD의 신기술에 대해 처음으로 알아보려면 팔로우를 클릭하세요~​

추천

출처blog.csdn.net/devcloud/article/details/123889582