Effective Java 버전 3 프로젝트 78 "공유된 가변 데이터에 대한 액세스 동기화"읽기

13361 단어 Javatech

개시하다

  • 최근 Effective Java 3판 프로젝트 78'공유된 가변 데이터에 대한 접근 동기화'를 읽고 내용을 정리했다.
  • 요약

  • 다중 라인에서 가변 데이터를 공유하는 것을 최대한 피한다.필요할 때 동기화를 잘 하세요.
  • 컨텐트

  • 다음과 같은 절차가 있다고 가정한다.
  • class StopThread {
        private static boolean stopRequested;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(() -> {
                int i = 0;
                while (!stopRequested)
                    i++;
            });
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(1);
            stopRequested = true;
        }
    }
    
  • 언뜻 보면 이 프로그램은 1초만 지나면 끝날 것 같지만 실제로는 끝나지 않는다.이는 backgroundThread 1차 캐시stopRequested의 값이 계속 false되기 때문에 순환에서 영원히 벗어날 수 없기 때문이다.해결 방안은 syncronized 수식자를 사용하여 라인 사이를 동기화stopRequested하는 값이다.
  • class StopThread {
        private static boolean stopRequested;
    
        private static synchronized void requestStop() {
            stopRequested = true;
        }
    
        private static synchronized boolean stopRequested() {
            return stopRequested;
        }
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(() -> {
                int i = 0;
                while (!stopRequested())
                    i++;
            });
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(1);
            requestStop();
        }
    }
    
  • 여기의 쓰기와 읽기 처리는 모두 동기화되지만 실제로는 두 가지 작업이 모두 동기화되어야 한다(쓸 때 데이터를 읽는 것을 방지하기 위해서, 반대로도 마찬가지이다).
  • 단, 이 프로그램에만 한정된다면volatile도 수식자를 사용하여 더욱 간단하게 실현할 수 있다.
  • class StopThread {
        private static volatile boolean stopRequested;
    
        public static void main(String[] args) throws InterruptedException {
            Thread backgroundThread = new Thread(() -> {
                int i = 0;
                while (!stopRequested)
                    i++;
            });
            backgroundThread.start();
    
            TimeUnit.SECONDS.sleep(1);
            stopRequested = true;
        }
    }
    
  • volatile 마운트 필드stopRequested의 라인이 이 필드에 마지막으로 쓴 값을 가져올 것을 보장합니다.따라서 stopRequested가 사실이라는 것을 감지할 수 있기 때문에 이 프로그램은 무한순환에 빠지지 않고 처리가 끝난다.
  • 단, volatile는 마지막 쓰기 필드의 값을 확보하는 것일 뿐 배타 제어를 하는 것이 아니기 때문에 목적에 따라 사용하지 않을 수도 있다.다음 코드가 예다.
  • private static volatile int NEXT_SERIAL_NUMBER = 0;
    
    private static int generateSerialNumber() {
        return NEXT_SERIAL_NUMBER++;
    }
    
  • 이런 상황에서 언뜻 보기generateSerialNumber() 방법은 매번 호출될 때마다 독특한 값을 되돌려주지만 실제로는 그렇지 않다.증가 연산자를 사용했기 때문이다.증분 연산자가 읽기NEXT_SERIAL_NUMBER한 다음 읽은 값을 1로 추가한 후 다시 쓰기NEXT_SERIAL_NUMBER.위에서 말한 바와 같이 volatile 마지막 쓰기 필드의 값만 가져와서 배타 제어를 하지 않기 때문에 어떤 라인이 NEXT_SERIAL_NUMBER을 읽고 쓰기 전에 다른 라인이 호출generateSerialNumber()하면 모든 라인이 같은 값을 얻는다NEXT_SERIAL_NUMBER설치 의도와 다른 동작이 됩니다.
  • 이 문제는 synchronized 수식자를 사용하거나 java.util.concurrent.atomic 포장에 포함된 AtomicLong류를 사용하여 해결할 수 있다.java.util.concurrent.atomic 패키지는 잠금, 스레드 보안 및 원자가 없는 조작을 제공합니다.따라서 synchronized 수식자 동기화 수치를 사용하는 것보다 성능이 좋다.설치 예는 다음과 같습니다.
  • private static final AtomicLong NEXT_SERIAL_NUMBER = new AtomicLong();
    public static long generateSerialNumber() {
        return NEXT_SERIAL_NUMBER.getAndIncrement();
    }
    

    감상

  • 이 항목을 읽고 나서야 비로소 volatile 수식자의 존재를 알았지만 내용만 봐도 사용 상황이 상당히 적다고 느꼈다.다만, 비동기 처리에서 데이터를 공유하고 동시 처리하는 실시 방침이 참고가 되기 때문에 자신이 실시하거나 팀 내에서 논평을 할 때 이 프로젝트의 내용을 떠올려 시스템을 고장내지 마세요.
  • 좋은 웹페이지 즐겨찾기