자바 병렬프로그래밍(2)

7장 중단 및 종료

작업이나 스레드를 안전하고 빠르고 안정적으로 멈추게 하는것이 쉬운일이 아니다.
자바에는 스레드가 작업하고있을때 강제로 멈추도록 하는방법이 존재하지않는대신,
인터럽트 라는 방법을 사용할수있다. 인터럽트는 특정 스레드에게 작업을 멈춰달라고 요청하는 형태이다.

자바에서는 특정 스레드를 명확하게 종료시킬수있는 방법은 없다. 즉, 특정작업을 임의로 종료 시킬 방법이 없다.
작업을 실행하는 스레드와 작업취소를 요청하는 스레드가 함께 협력하여 작업을 멈추게끔 해야한다.

@ThreadSafe
public class PrimeGenerator implements Runnable {
    private static ExecutorService exec = Executors.newCachedThreadPool();

    @GuardedBy("this") private final List<BigInteger> primes
            = new ArrayList<BigInteger>();
    private volatile boolean cancelled; // 반드시 volatile 로선언 

    public void run() {
        BigInteger p = BigInteger.ONE;
        while (!cancelled) { // cancelled 값을 반복문에서 매번 확인 
            p = p.nextProbablePrime();
            synchronized (this) {
                primes.add(p);
            }
        }
    }

    public void cancel() {
        cancelled = true; // cancelled 플래그에 값 설정 
    }

    public synchronized List<BigInteger> get() {
        return new ArrayList<BigInteger>(primes);
    }

    static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
        PrimeGenerator generator = new PrimeGenerator();
        exec.execute(generator);
        try {
            SECONDS.sleep(1);
        } finally {
            generator.cancel();
        }
        return generator.get();
    }
}

취소요청이 들어올떄까지 소수를 찾아내는작업을 진행하고, 취소요청 플래그를 사용해 작업을 멈추는 방법을 사용하는 코드
cancel 메소드 호출시 cancelled 플래그에 true값 설정되고 반복문이 반복될때마다 cancelled 값을 확인한다. (volatile 형식으로 cancelled 변수를 선언해야함 )


    static List<BigInteger> aSecondOfPrimes() throws InterruptedException {
        PrimeGenerator generator = new PrimeGenerator();
        exec.execute(generator);
        try {
            SECONDS.sleep(1);
        } finally {
            generator.cancel();
        }
        return generator.get();
    }

소수계산 작업 스레드를 실행시킨 다음 1초후에 소수계산을 멈추도록 하는 예제
cancel메소드가 만약 호출되지않으면 소수계산작업은 멈추지않고 계속해서 동작할것이고, CPU자원을 계속잡아먹고 JVM도 종료되지않을것이다.

우리는 어떻게 언제 어떤일을 해야하는지, 어떤방법으로 취소요청을 보낼수있는지, 작업내부에서 취소요청이 들어왔는지를 언제확인하는지, 취소요청이 들어온다면 어떤형태로 동작하는지 등에대한 정보를 제공해야한다.

인터럽트

https://docs.oracle.com/javase/1.5.0/docs/api/java/lang/Thread.html#method_summary

Thread 클래스에는 해당 인터럽트를 거는 메소드를 가지고있으며 void interrupt() , 인터럽트가 걸린상태인지 확인할수있는 메소드도 있다 boolean isInterrupted()
인터럽트는 실행중인 스레드에 제한을 가해 멈추도록 하는것이 아닌 단지 해당하는 스레드가 상황을 봐서 스스로 멈춰주기를 요청하는것이다.


public class PrimeProducer extends Thread {
    private final BlockingQueue<BigInteger> queue;

    PrimeProducer(BlockingQueue<BigInteger> queue) {
        this.queue = queue;
    }

    public void run() {
        try {
            BigInteger p = BigInteger.ONE;
            while (!Thread.currentThread().isInterrupted()) // 현재 스레드가 인터럽트 인지 확인 
                queue.put(p = p.nextProbablePrime());
        } catch (InterruptedException consumed) {
            /* Allow thread to exit */
        }
    }

    public void cancel() {
        interrupt();
    }
}
public class TimedRun1 {
    private static final ScheduledExecutorService cancelExec = Executors.newScheduledThreadPool(1);

    public static void timedRun(Runnable r,
                                long timeout, TimeUnit unit) {
        final Thread taskThread = Thread.currentThread();
        cancelExec.schedule(new Runnable() {
            public void run() {
                taskThread.interrupt();
            }
        r.run();
    }
}

다음코드는 좋지않다. 스레드에 인터럽트를걸때 대상 스레드의 인터럽트 정책을 알지 못하기때문이다.

지정한 시간이 다되기전에 작업이끝난다면 timedRun메소드를 호출했던 스레드는 timedRun 메소드 실행을 모두 끝마치고 그다음 작업을 실행하고있을텐데, 작업중단 스레드는 다음 작업을 실행하는 도중에 인터럽트를 걸게된다. 인터럽트 시점에 어떤코드가 실행되고있을지는 알수없지만, 결과가 정상적이지 않을것이라는것은 예측할수있다.

public class TimedRun2 {
    private static final ScheduledExecutorService cancelExec = newScheduledThreadPool(1);

    public static void timedRun(final Runnable r,
                                long timeout, TimeUnit unit)
            throws InterruptedException {
        class RethrowableTask implements Runnable {
            private volatile Throwable t; // 예외를 작업스레드에서 호출 스레드로 안전하게 공개할수있또록 volatile 선언 
            public void run() {
                try {
                    r.run();
                } catch (Throwable t) {
                    this.t = t;
                }
            }

            void rethrow() {
                if (t != null)
                    throw launderThrowable(t);
            }
        }

        RethrowableTask task = new RethrowableTask();
        final Thread taskThread = new Thread(task);
        taskThread.start();
        cancelExec.schedule(new Runnable() {
            public void run() {
                taskThread.interrupt();
            }
        }, timeout, unit);
        taskThread.join(unit.toMillis(timeout)); // 시간제한이 걸린 join메소드의 사용으로인해 timedrun메소드가 리턴됐을때 정상적으로 스레드가 종료된건지 join메소드에서 타임아웃이 걸린것인지 알수없는 단점이있음 
        task.rethrow();
    }
}

작업 실행 전용 스레드에 인터럽트 거는 방법

7장은 비정상적인 스레드 종료상황처리, 독약방법, 스레드 기반 서비스 중단 등의 내용을 다루고있지만
지금시점에선 제대로 이해하기힘들었다.

7장 요약

작업,스레드,서비스,애플리케이션등이 할일을 모두 마치고 종료되는 시점을 적절하게 관리하려면 프로그램이 훨씬 복잡해질수있다. 하지만 자바에서는 선점적으로 작업을 중단하거나 스레드를 종료시킬 방법을 제공하지않는다.
대신 인터럽트 라는 방법을 사용해 스레드간 협력 과정을 거쳐 작업 중단 기능을 구현하도록 하고있으며, 작업 중단 기능을구현하고 전체 프로그램에 적용하는것은 개발자의 몫이다. FutureTask, Executor 등의 프레임웍을 사용하면 작업,서비스 실행도중 중단기능을 쉽게 구현할수있다.

8장

Executor 프레임웍이 작업의 정의부분과 실행부분을 서로 분리시켜 준다는 얘기를 했었는데, 실행할수 없는 특정형태의 실행 작업들도 존재한다.

  • 의존성이 있는 작업
    => 독립적인 작업은 대부분 문제 없이 스레드풀에서 크기나 설정을 변경할수있지만 , 다른작업에 의존성을 갖는 작업을 스레드풀에 올리는 경우엔 실행정책에 대해 알지 못하는 상태로 조건을 거는것이 된다.

  • 스레드 한정 기법을 사용하는 작업
    => 단일스레드를 사용하는 풀대신 여러개의 스레드를 사용하는 풀로 변경하면 thread-safe 를 쉽게 잃을수있다.

  • 응답시간이 민감한 작업
    => GUI 같은 작업

  • ThreadLocal 사용하는 작업

스레드 부족 데드락

스레드풀에서 다른작업에 의존성을 갖고있는 작업 실행시 데드락 걸릴 가능성이 높다.
이전작업이 추가한 두번쨰작업은 큐에쌓인상태로 이전작업이 끝나길 기다리고
이전작업은 추가된 작업이 실행되어 그 결과를 알려주길 기다릴 상태가 되기 때문이다.
이런현상을 스레드 부족 데드락 이라고 한다.

public class ThreadDeadlock {
    ExecutorService exec = Executors.newSingleThreadExecutor();

    public class LoadFileTask implements Callable<String> {
        private final String fileName;

        public LoadFileTask(String fileName) {
            this.fileName = fileName;
        }

        public String call() throws Exception {
            // Here's where we would actually read the file
            return "";
        }
    }

    public class RenderPageTask implements Callable<String> {
        public String call() throws Exception {
            Future<String> header, footer;
            header = exec.submit(new LoadFileTask("header.html"));
            footer = exec.submit(new LoadFileTask("footer.html"));
            String page = renderBody();
            // Will deadlock -- task waiting for result of subtask
            return header.get() + page + footer.get();
        }

        private String renderBody() {
            // Here's where we would actually render the page
            return "";
        }
    }
}

RenderPageTask 클래스가 페이지의 머리글,꼬리글을 가져오는작업을 Executor에 등록
-> 페이지 본문을 화면에 그려냄 -> 머리글 , 꼬리글 가져오길 기다렸다가 가져오면 하나로 모아 최종페이지를 만드는 코드이다.

Executor 에서 스레드를 하나만 구현 한다면 위의 클래스는 항상 데드락에 걸린다.

스레드풀 크기 조절

하드코딩하여 고정시키는것은 좋은방법이 아니고, 메소드 결과값에 따라 동적으로 지정되도록 하는것이 좋다 .
스레드풀의 크기가 너무 크게 설정되어있으면 스레드는 CPU나 메모리등의 자원을 확보하기위해 경쟁하게 되어 cpu부하, 메모리부족을 일으킬수있다.
스레드풀의 크기가 너무 작다면 작업량은 계속 쌓이는데 CPU나 메모리는 남아돌아 작업처리속도가 떨어질수있다.

스레드풀의 적절한 크기산정을 위해서 컴퓨터환경, 확보하고있는자원의양, 해야할작업이 어떻게 동작하는지에 대해서 정확히 알아야 한다.

  • CPU를 많이 사용하는 작업의 경우
    => 스레드의개수를 CPU개수+1 개로 맞춘다. (N+1개)

  • I/O 작업이 많거나, 기타 다른 블로킹 작업을 해야하는경우
    => 어느순간 모든 스레드가 대기상태에 들어가 전체적인 진행이 멈출수있기때문에 스레드 풀의 크기를 훨씬 크게 잡아야할 필요가있다.

스레드풀의 설정을 변경, 집중대응정책(큐 관련) , 스레드 팩토리, ThreadPoolExecutor 상속 , 재귀함수 병렬화, 등

8장에서는 Executor 프레임웍의 작업을 병렬로 동작시킬수있는 강력함,유연성에대해 소개해주고있다.
또한 스레드를 생성/제거 하는 정책이나 큐에 쌓인 작업을 처리하는방법, 작업이 밀렸을때 튜닝하는옵션, 등 의 내용을 다뤄주고있다. 아직은 깊게 소화할수가 없는것을 느꼈다.

9장에서는 GUI , 스윙과 관련된 스레드 처리에대해서 다룬다

10장

데이터 베이스 시스템은 데드락을 검출한다음 데드락 상황에서 복구하는 기능을 갖추고있다.
트랜잭션을 활용하다보면 여러개의 락이 필요할수있고 락은 커밋이 될떄까지 풀리지않는다.
두개이상의 트랜잭션이 데드락에 걸릴수는 있지만 데드락이 발생한채로 시스템이 멈추도록 방치하지않는다.
(DB 서버가 트랜잭션간에 데드락이 발생했다는 사실을 확인하고 나면 데드락이 트랜잭션 가운데 하나를 선택해 강제종료하고 나머지 하나는 락을확보후 계속 진행한다.)

하지만 JVM 에서는 데드락상태를 추적하는 기능은 갖고있지 않다.
따라서 자바프로그램에서 데드락이 발생하면 끝이다. 해당스레드는 프로그램 자체를 강제종료 하기전엔 영원히 멈춘다.
(물론 뭘하는 스레드이냐에 따라서 멈추는 범위가 달라질순있다.)
데드락은 모습을 거의드러내지않고 상용서비스를 시작하고나서 시스템에 부하가 걸리는경우와같이 최악의 상황에서 모습을 드러내곤 한다.

데드락을 유발하는 종류들 예시

락의 순서에 의한 데드락, 동적인 락 순서에 의한 데드락, 객체간의 데드락, 오픈호출(락을 확보하지않은상태에서 메소드 호출) , 리소스데드락 (필요한 자원을 사용하기위한 대기과정)

데드락 방지,원인추적

스레드 덤프를 활용한 데드락 분석, 락의 시간제한(Lock클래스의 tryLock 메소드) , 스레드덤프를 활용한 데드락분석,

활동성 문제점들

소모상태
스레드 우선순위를 적절치 못하게 올리거나 내리는경우 혹은 락을확보한채로 종료되지않는코드를 실행할때 다른스레드에서 해당락을 가져갈수없기때문에 소모 상황이 발생한다.

자바에서의 스레드 관련 API에서 제공하는 우선순위개념은 스레드의 우선순위를 바꾸고 싶다고 해도 사용하기에는 부작용이 많기때문에 참는것이좋다.

형편없는 응답성

소모상황보다는 나은상황이다. 응답성이 떨어지는 백그라운드 스레드를 사용하는 GUI애플리케이션에서 일상적이다.
백그라운드 에서 실행되고 있는 기능이 CPU를 많이 사용해 응답성을 저해하는 부분이 해당된다.
락을 제대로 관리하지 못하는것이 원인일수있다.

라이브락

특정 작업의 결과를 받아와야 다음단계로 넘어갈수있는 작업이 실패할수밖에 없는 기능을 계속해서 재시도하는경우에 찾아볼수있다.

활동성문제들을 해결하기위해선 애플리케이션을 종료하는것 외에 별다른 방법이 없다는점이 심각한점이다.
가장 흔한형태의 활동성 문제는 락 순서에 의한 데드락이다.

애플리케이션 내부의 스레드에서 두개이상의 락을 한꺼번에 사용해야하는 부분이있다면, 항상 일정한 순서를 두고 여러개의 락을 확보해야한다.
이런문제에 대한 가장 효과적인 방법은 항상 오픈호출방법을 사용해 메소드를 호출하는것이다.
이를 사용하면 한번에 여러개의 락을 사용하는경우를 엄청 줄일수있고, 여러개의 락을 사용하는 부분이 어디인지 쉽게 찾아낼수있다.

11장

스레드를 사용하면서 더 나은 능을 목표로 해서 병렬로 동작하도록 만들때는 두가지부분을 생각해야한다.
프로그램이 확보할수있는 모든자원을 최대한 활용해야하고 , 남는 자원이 생길떄마다 그자원역시 최대한 활용해야한다.

서버애플리케이션을 만들때는 얼마나 빠르게 라는측면보다 얼마나 많이라는 측면 (확장성과 처리량) 을 훨씬 중요하게 생각하는 경우가 많다. 처리용량을 단시간에 급격하게 증가시키는일이 어렵기 때문이다.

컨텍스트 스위칭

하나의스레드가 실행되다가 다른 스레드가 실행되는순간 발생함.
실행중인 스레드의 실행상태를 보관해두고 다음번에 실행되기로 스케줄된 다른 스레드의 실행상태를 다시 읽어들인다.
스레드가 실행하다가 락을 확보하기위해 대기하기시작하면 JVM은 해당스레드를 일시적으로 정지시키고 다른 스레드가 실행되돍한다. 대기상태에 들어가는연산을 많이 사용하는 프로그램은 CPU를 주로 활용하는 프로그램보다 컨텍스트 스위칭 횟수가 많아지고, 전체적인 처리량이 줄어든다.

메모리동기화

동기화 작업이 경쟁적인지 아닌지 에 따라서 성능에 미치는 영향이 다르다.
경쟁조건이 발생하는 동기화블록을 어떻게 최적화할지에 대해서만 고민하면된다.
특정스레드에서 진행되는 동기화작업으로 인해 다른스레드의 성능이 영향받을수있다.
특정 스레드가 동기화 작업을 진행하느라 공유 메모리를 붙잡고있다면, 다른스레드는 성능이 떨어질수밖에 없다.

블로킹

경쟁하지않는 상태에서의 동기화작업은 JVM내부에서 처리할수있찌만 경쟁조건이 발생할때에는
운영체제가 관여해야할수있다 .
JVM은 스핀대기(락을 확보할떄까지 계속해서 재시도)방법 , 운영체제가 제공하는 기능을 사용해 스레드를 실제 대기상태로 두는 방법 두가지를 사용할수있다.
자원의양, 락확보 소모시간, 대기시간, 등을 따져서 효율적인 방법을 찾는다.

락 경쟁 줄이기

작업을 순차적으로 처리하면 확장성을 놓치고, 병렬로처리하면 컨텍스트 스위칭에서 성능에 악영향을 준다.
락을 놓고 경쟁하는 상황이 발생하면 순차적으로 처리 + 컨텍스트 스위칭이 동시에 일어나서 치명적일수있다.
이를 줄이면 성능과 확장성을 높일수있다.
락은 공유된 데이터의 보호를 위한 명분이있지만 그에 따른 대가도 분명하다. 락을 지속적으로 보호하고자 하는 상황에서 확장성 문제가 생긴다.

병렬 애플리케이션에서는 확장성에 가장 큰 위협이 되는 존재는 특정자원을 독점적으로 사용하도록 제한하는 락이다.

락 경쟁조건을 줄일수있는 방법들이있다.

  • 락을 확보한채로 유지되는 시간을 최대한 줄일것
  • 락을 확보하고자 요청하는 횟수를 최대한 줄일것
  • 독점적인 락 대신 병렬성을 높여주는 여러 방법을 사용할것

락 구역좁히기

락이 꼭 필요하지 않은 코드를 synchronized 블록 밖으로 빼내어 락이영향을 미치는 구역을 좁히는 방법이다.
(특히 I/O 작업같은 대기시간이 발생할수있는 코드)

@ThreadSafe
public class AttributeStore {
    @GuardedBy("this") private final Map<String, String>
            attributes = new HashMap<String, String>();

    public synchronized boolean userLocationMatches(String name,
                                                    String regexp) { // 메소드 전체에 synchronized 
        String key = "users." + name + ".location";
        String location = attributes.get(key); // 사실 이부분만 동기화 해주면됨 
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }
}

이 코드에서 락구역을 좁혀보겠다.

@ThreadSafe
public class BetterAttributeStore {
    @GuardedBy("this") private final Map<String, String>
            attributes = new HashMap<String, String>(); // CouncurrentHashMap, Hashtable, synchroinzedMap 등을 사용하면 스레드 안전성도 확보가 가능하다. 

    public boolean userLocationMatches(String name, String regexp) {
        String key = "users." + name + ".location";
        String location;
        synchronized (this) { // 이부분만 락으로 
            location = attributes.get(key);
        }
        if (location == null)
            return false;
        else
            return Pattern.matches(regexp, location);
    }
}

암달의법칙에선 순차적으로 처리돼야하는 코드의 양이 줄어드는 효과가 있기때문에 애플케이션의 확장성을 저해하는 요소를 줄이는 결과도 기대할수있다.
synchronized 블록에서 동기화를 맞추는것도 자원이 필요하기때문에 너무 남발해서는 안된다.

락 정밀도 높이기

락을 점유하고있는 시간을 최대한 줄이고, 스레드에서 해당락을 덜사용하도록 변경하는 방법이다.

@ThreadSafe
public class ServerStatusBeforeSplit {
    @GuardedBy("this") public final Set<String> users;
    @GuardedBy("this") public final Set<String> queries;

    public ServerStatusBeforeSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }

    public synchronized void addUser(String u) {
        users.add(u);
    }

    public synchronized void addQuery(String q) {
        queries.add(q);
    }

    public synchronized void removeUser(String u) {
        users.remove(u);
    }

    public synchronized void removeQuery(String q) {
        queries.remove(q);
    }
}

before

@ThreadSafe
public class ServerStatusAfterSplit {
    @GuardedBy("users") public final Set<String> users;
    @GuardedBy("queries") public final Set<String> queries;

    public ServerStatusAfterSplit() {
        users = new HashSet<String>();
        queries = new HashSet<String>();
    }

    public void addUser(String u) {
        synchronized (users) {
            users.add(u);
        }
    }

    public void addQuery(String q) {
        synchronized (queries) {
            queries.add(q);
        }
    }

    public void removeUser(String u) {
        synchronized (users) {
            users.remove(u);
        }
    }

    public void removeQuery(String q) {
        synchronized (users) {
            queries.remove(q);
        }
    }
}

락 스트라이핑

독립적인 객체를 여러가지 크기의 단위로 묶고 , 묶인 블록을 단위로 락을 나누는 방법
여러개의 락을 사용하도록 쪼개놓은 컬렉션 전체를 한꺼번에 독점적으로 사용해야할때에는 단일락보다 동기화시키기 어렵고, 자원도 많이 소모한다는 단점이있다.

12장 병렬 테스트에대한내용

13장

Lock 과 ReentrantLock

락을 확보하고자 대기하고있는상태의 스레드는 인터럽트 거는일이 불가능하고, 대기상태에 들어가지않으면서 락을 확보하는 방법이 꼭 필요한 상황이있다.

암묵적인락

단일연산 특성을 보장하기위해 synchronized 라는 구문으로 락을 제공한다.
synchronized 블록을 실행중이라면 다른 스레드가 그 블록에 들어올수없다.
synchronized 블록이 끝나는 시점에 반드시 해제된다. (블록의 구조를 갖추지않으면 락걸어야하는경우에 적용이안된다.)
실수방지에 좋고 오류도줄일수있지만 유연하지못하다

명시적인락

Reentrant Lock 은 finally 블록에서 반드시 락을 해제해야한다. 그렇지않으면 락이 해제되지않을수가있다.
시간제한이있는 락을 적용할때 유용하다.
락을확보할때 타임아웃을 지정하거나 대기상태에서 인터럽트에잘 반응하고 블록의 구조를 갖추지않은경우에도 락을 적용할수있다.
synchronized 블록에서 제공하지않는 특별한 기능이 필요할때만 사용하는편이 안전하다. (혼동가능성, 오류가능성)

14장

15장

CAS

최근 병렬 알고리즘과 관련한 연구결과에서는
넌블로킹 알고리즘, 여러스레드가 동작하는환경에서 락을 사용하는 대신
CAS = compare and swap 비교후 교환 명령을 사용하는 알고리즘을 다루고있다.
프로세스나 스레드를 스케줄링, 가비지컬렉션작업, 락이나 기타병렬 자료구조를 구현하는 부분에서 많이사용한다.

넌블로킹 알고리즘은 락을 기반으로 하는방법보다 설계,구현이 모두 훨씬 복잡하지만 확장성과 활동성을 엄청나게 높여준다. 여러스레드가 동일한 자료를 놓고 경쟁하는 과정에서 대기상태에 들어가지않고 , 데드락이나 기타 활동성 문제가 발생할 위험도 없다.

낙관적인 기법, V의 메모리위치에 A라는 예상된 값이 있을거다. 실제로 A가들어가있으면 B(새로 설정할 값)로 바꾸고 A가아니라면 아무작업도 하지말고 V의값이 뭔지 알려달라 라는 메커니즘
값을 확인한후 변경한 사실이있는지 확인이나 하자는 의미
CAS연산에 실패한 스레드는 락과 다르게 대기상태에 들어가는것이 아니라 연산을 재시도할지, 다른방법을 ㅜ치할지, 아예 아무조치도 취하지않을지 정할수있다. 유연성을 가진다는 뜻이고 락을 사용하면서 발생할수밖에 없었던 활동성 문제를 미연에 방지할수있다.

@ThreadSafe
public class SimulatedCAS {
    @GuardedBy("this") private int value;

    public synchronized int get() {
        return value;
    }

    public synchronized int compareAndSwap(int expectedValue,
                                           int newValue) {
        int oldValue = value;
        if (oldValue == expectedValue)
            value = newValue;
        return oldValue;
    }

    public synchronized boolean compareAndSet(int expectedValue,
                                              int newValue) {
        return (expectedValue
                == compareAndSwap(expectedValue, newValue));
    }
}

CAS연산 구현코드

코드도 길어질수있고 코드의 흐름도 복잡해져서 락 기반의 카운터 클래스보다 성능이 떨어질것처럼 보이지만 오히려 그 반대다

CAS의 단점은 호출하는 프로그램에서 직접 스레드 경쟁조건에 대한 처리를 해야하는반면 (재시도,처리,무시 등의 방법)
락을 사용하면 대기상태에 들어가도록 하면서 알아서 스레드 경쟁문제를 처리해준다는 단점이있다.

atomic variable

멀티프로세서 시스템에서 고성능의 병렬 프로그램을 작성하고자 할때 핵심적인 역할을 한다.
스레드가 경쟁하는 범위를 하나의 변수로 좁혀주는 효과가있다
락대신 단일연산 변수기반의 알고리즘으로 구현된 프로그램은 내부의 스레드가 지연되는 현상이 거의없으며 스레드경쟁이 발생해도 훨씬 쉽게 헤쳐나갈수있다.

volatile 변수에서 읽고 변경하고 쓰는것과 같은 조건부 단일연산을 지원하도록 일반화한 구조

대기상태에 들어가지않는 넌블로킹 알고리즘은 락대신 CAS(비교후 치환) 과같은 저수준 명령을 통해 스레드 안전성을 유지하는 알고리즘이다. 이런 저수준의 기능은 단일연산 클래스를 통해 사용할수있고 더나은 volatile 변수로써 정수형 변수나 객체대한 참조등을 대상으로 단일연산 기능을 제공하기도 한다.

좋은 웹페이지 즐겨찾기