자바 보안 인 코딩 가이드 의 가시 성과 원자 성 을 상세히 설명 합 니 다.

불가 변 대상 의 가시 성
가 변 적 이지 않 은 대상 은 초기 화 된 후에 수정 할 수 없 는 대상 입 니 다.그러면 클래스 에 가 변 적 이지 않 은 대상 을 도입 한 것 이 아 닙 니까?가 변 적 이지 않 은 대상 에 대한 모든 수정 은 즉시 모든 스 레 드 를 볼 수 있 습 니까?
실제로 불가 변 대상 은 다 중 스 레 드 환경 에서 만 대상 이 사용 하 는 안전성 을 보장 할 수 있 을 뿐 대상 의 가시 성 을 보장 할 수 없다.
먼저 가 변성 에 대해 토론 합 시다.우 리 는 다음 의 예 를 고려 합 니 다.

public final class ImmutableObject {
    private final int age;
    public ImmutableObject(int age){
        this.age=age;
    }
}
우 리 는 Immutable Object 대상 을 정 의 했 습 니 다.class 는 final 이 고 그 안의 유일한 필드 도 final 입 니 다.그래서 이 Immutable Object 가 초기 화 되면 바 꿀 수 없습니다.
그리고 get 과 set 이라는 Immutable Object 를 정의 합 니 다.

public class ObjectWithNothing {
    private ImmutableObject refObject;
    public ImmutableObject getImmutableObject(){
        return refObject;
    }
    public void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}
위의 예 에서 우 리 는 변 하지 않 는 대상 에 대한 refObject 인용 을 정의 한 다음 get 과 set 방법 을 정의 했다.
비록 Immutable Object 라 는 클래스 자체 가 변 할 수 없 지만,우 리 는 이 대상 에 대한 refObject 인용 은 변 할 수 있 습 니 다.이것 은 우리 가 setImmutable Object 방법 을 여러 번 호출 할 수 있다 는 것 을 의미한다.
가시 성 을 다시 토론 하 다.
위의 예 에서 다 중 스 레 드 환경 에서 setImmutable Object 는 매번 getImmutable Object 를 새로운 값 으로 되 돌려 줍 니까?
답 은 부정 적 이다.
원본 코드 를 컴 파일 한 후에 컴 파일 러 에서 생 성 된 명령 의 순 서 는 원본 코드 의 순서 와 완전히 일치 하지 않 습 니 다.프로 세 서 는 명령 을 어 지 럽 히 거나 병행 하 는 방식 으로 실행 할 수 있 습 니 다.또한 프로세서 와 로 컬 캐 시 는 결 과 를 로 컬 캐 시 에 저장 하면 다른 스 레 드 는 결 과 를 볼 수 없습니다.그 밖 에 캐 시 를 주 메모리 에 제출 하 는 순서 도 달라 질 수 있 습 니 다.
어떻게 해결 하지?
가장 간단 한 해결 방법 은 volatile 키 워드 를 추가 하 는 것 입 니 다.volatile 키 워드 는 자바 메모리 모델 의 happens-before 규칙 을 사용 하여 volatile 의 변 수 를 모든 스 레 드 에 보 여 줍 니 다.

public class ObjectWithVolatile {
    private volatile ImmutableObject refObject;
    public ImmutableObject getImmutableObject(){
        return refObject;
    }
    public void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}
또한 자물쇠 메커니즘 을 사용 하면 같은 효 과 를 얻 을 수 있다.

public class ObjectWithSync {
    private  ImmutableObject refObject;
    public synchronized ImmutableObject getImmutableObject(){
        return refObject;
    }
    public synchronized void setImmutableObject(int age){
        this.refObject=new ImmutableObject(age);
    }
}
마지막 으로 우 리 는 원자 류 를 사용 하여 같은 효 과 를 얻 을 수 있다.

public class ObjectWithAtomic {
    private final AtomicReference<ImmutableObject> refObject= new AtomicReference<>();
    public ImmutableObject getImmutableObject(){
        return refObject.get();
    }
    public void setImmutableObject(int age){
        refObject.set(new ImmutableObject(age));
    }
}
공유 변수의 복합 작업 의 원자 성 을 확보 하 다.
공유 대상 이 라면 다 중 스 레 드 환경 에서 의 원자 성 을 고려 해 야 한다.공유 변수 에 대한 복합 작업 이 라면:+,--*=,/=,%=,+=,-=,<=,>=,>>=,>=,^=등 은 하나의 문장 으로 보이 지만 실제로는 여러 문장의 집합 이다.
우 리 는 다 중 스 레 드 아래 의 안전성 을 고려 해 야 한다.
아래 의 예 를 고려 하 다.

public class CompoundOper1 {
    private int i=0;
    public int increase(){
        i++;
        return i;
    }
}
예 에서 우 리 는 int i 에 대해 누적 작업 을 한다.그러나++는 실제로 세 가지 조작 으로 이 루어 져 있다.
1.메모리 에서 i 값 을 읽 고 CPU 레지스터 에 기록 합 니 다.
2.CPU 레지스터 에서 i 값+1
3.메모리 에 있 는 i 에 값 을 다시 씁 니 다.
단일 스 레 드 환경 에 서 는 문제 가 없 지만 다 중 스 레 드 환경 에 서 는 원자 조작 이 아니 기 때문에 문제 가 발생 할 수 있 습 니 다.
해결 방법 은 여러 가지 가 있 는데,첫 번 째 는 synchronized 키 워드 를 사용 하 는 것 이다.

public synchronized int increaseSync(){
    i++;
    return i;
}
두 번 째 는 lock 을 사용 하 는 것 입 니 다.

private final ReentrantLock reentrantLock=new ReentrantLock();

public int increaseWithLock(){
    try{
        reentrantLock.lock();
        i++;
        return i;
    }finally {
        reentrantLock.unlock();
    }
}
세 번 째 는 Atomic 원자 류 를 사용 하 는 것 이다.

private AtomicInteger atomicInteger=new AtomicInteger(0);

public int increaseWithAtomic(){
    return atomicInteger.incrementAndGet();
}
여러 Atomic 원자 류 작업 의 원자 성 을 확보 합 니 다.
만약 한 방법 이 여러 개의 원자 류 의 조작 을 사용 했다 면,비록 하나의 원자 조작 은 원자 성 이지 만,조합 해 보면 반드시 그렇지 는 않다.
우 리 는 예 를 하나 보 자.

public class CompoundAtomic {
    private AtomicInteger atomicInteger1=new AtomicInteger(0);
    private AtomicInteger atomicInteger2=new AtomicInteger(0);

    public void update(){
        atomicInteger1.set(20);
        atomicInteger2.set(10);
    }

    public int get() {
        return atomicInteger1.get()+atomicInteger2.get();
    }
}
위의 예 에서 우 리 는 두 개의 AtomicInteger 를 정 의 했 고 각각 update 와 get 작업 에서 두 개의 AtomicInteger 를 조작 했다.
AtomicInteger 는 원자 적 이지 만 두 개의 서로 다른 AtomicInteger 를 합치 면 그렇지 않다.다 중 스 레 드 작업 과정 에서 문제 가 생 길 수 있 습 니 다.
마찬가지 로 우 리 는 동기 화 메커니즘 이나 자 물 쇠 를 사용 하여 데이터 의 일치 성 을 확보 할 수 있다.
보증 방법 호출 체인 의 원자 성
만약 우리 가 대상 의 인 스 턴 스 를 만 들 려 고 한다 면,이 대상 의 인 스 턴 스 는 체인 호출 을 통 해 만 든 것 입 니 다.그러면 우 리 는 체인 호출 의 원자 성 을 확보 해 야 한다.
다음 의 예 를 고려 해 보 자.

public class ChainedMethod {
    private int age=0;
    private String name="";
    private String adress="";

    public ChainedMethod setAdress(String adress) {
        this.adress = adress;
        return this;
    }

    public ChainedMethod setAge(int age) {
        this.age = age;
        return this;
    }

    public ChainedMethod setName(String name) {
        this.name = name;
        return this;
    }
}
아주 간단 한 대상 입 니 다.우 리 는 세 개의 속성 을 정 의 했 습 니 다.매번 set 는 this 에 대한 인용 을 되 돌려 줍 니 다.
우 리 는 다 중 스 레 드 환경 에서 어떻게 호출 하 는 지 보 자.

ChainedMethod chainedMethod= new ChainedMethod();
Thread t1 = new Thread(() -> chainedMethod.setAge(1).setAdress("www.flydean.com1").setName("name1"));
t1.start();

Thread t2 = new Thread(() -> chainedMethod.setAge(2).setAdress("www.flydean.com2").setName("name2"));
t2.start();
다 중 스 레 드 환경 에서 위의 set 방법 은 혼 란 스 러 울 수 있 기 때문이다.
어떻게 해결 하지?우 리 는 먼저 로 컬 복사 본 을 만 들 수 있 습 니 다.이 복사 본 은 로 컬 에서 방문 한 것 이기 때문에 스 레 드 가 안전 합 니 다.마지막 으로 복사 본 을 새로 만 든 인 스 턴 스 대상 에 게 복사 할 수 있 습 니 다.
주요 코드 는 다음 모양 입 니 다:

public class ChainedMethodWithBuilder {
    private int age=0;
    private String name="";
    private String adress="";

    public ChainedMethodWithBuilder(Builder builder){
        this.adress=builder.adress;
        this.age=builder.age;
        this.name=builder.name;
    }

    public static class Builder{
        private int age=0;
        private String name="";
        private String adress="";

        public static Builder newInstance(){
            return new Builder();
        }
        private Builder() {}

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setAge(int age) {
            this.age = age;
            return this;
        }

        public Builder setAdress(String adress) {
            this.adress = adress;
            return this;
        }

        public ChainedMethodWithBuilder build(){
            return new ChainedMethodWithBuilder(this);
        }
    }
우 리 는 어떻게 호출 하 는 지 보 자.

final ChainedMethodWithBuilder[] builder = new ChainedMethodWithBuilder[1];
Thread t1 = new Thread(() -> {
    builder[0] =ChainedMethodWithBuilder.Builder.newInstance()
        .setAge(1).setAdress("www.flydean.com1").setName("name1")
        .build();});
t1.start();

Thread t2 = new Thread(() ->{
    builder[0] =ChainedMethodWithBuilder.Builder.newInstance()
        .setAge(1).setAdress("www.flydean.com1").setName("name1")
        .build();});
t2.start();
lambda 표현 식 에서 사용 하 는 변 수 는 final 또는 final 과 같은 효 과 를 가 져 야 하기 때문에 final 배열 을 구축 해 야 합 니 다.
읽 기와 쓰기 64bits 의 값
자바 에 서 는 64bits 의 log 와 double 이 두 개의 32bits 로 취급 된다.
그래서 하나의 64 bits 작업 은 두 개의 32 bits 작업 으로 나 뉘 었 다.원자 문 제 를 일 으 켰 다.
다음 코드 를 고려 하 십시오:

public class LongUsage {
    private long i =0;

    public void setLong(long i){
        this.i=i;
    }
    public void printLong(){
        System.out.println("i="+i);
    }
}
롱 의 읽 기와 쓰 기 는 두 부분 으로 나 뉘 어 진행 되 기 때문에 다 중 스 레 드 환경 에서 setLong 과 printLong 을 여러 번 호출 하 는 방법 이 있 으 면 문제 가 생 길 수 있 습 니 다.
해결 방법 은 간단 합 니 다.log 또는 double 변 수 를 volatile 로 정의 하면 됩 니 다.

private volatile long i = 0;
이상 은 자바 보안 코드 매 뉴 얼 의 가시 성과 원자 성에 대한 상세 한 내용 입 니 다.자바 보안 코드 매 뉴 얼 의 가시 성과 원자 성에 관 한 자 료 는 다른 관련 글 을 주목 하 시기 바 랍 니 다!

좋은 웹페이지 즐겨찾기