잘못된 정보가 있다면, 꼭 댓글로 알려주세요(비로그인 익명도 가능).

여러분의 피드백이 저와 방문자 모두를 올바른 정보로 인도할 수 있습니다.

감사합니다. -현록

후원해주실 분은 여기로→

현록의 기록저장소

Java - volatile 키워드, synchronized 키워드 본문

Study/Java

Java - volatile 키워드, synchronized 키워드

현록 2022. 8. 7. 18:53

https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/reflect/Modifier.html

 

 


더보기
package testvolatile;

public abstract class ObjectTest {
    public abstract int getInteger();
    public abstract void setInteger(int integer);
}

더보기
package testvolatile;

public class ObjectTestVolatile extends ObjectTest {
    private volatile int integer;

    public ObjectTestVolatile() {
        super();
        this.integer = 0;
    }

    @Override
    public int getInteger() {
        return integer;
    }

    @Override
    public void setInteger(int integer) {
        this.integer = integer;
    }
}

더보기
package testvolatile;

public class ObjectTestSync extends ObjectTest  {
    private int integer;

    public ObjectTestSync() {
        super();
        this.integer = 0;
    }

    @Override
    public synchronized int getInteger() {
        return integer;
    }

    @Override
    public synchronized void setInteger(int integer) {
        this.integer = integer;
    }
}

더보기
package testvolatile;

import java.util.concurrent.atomic.AtomicInteger;

public class ObjectTestAtomic extends ObjectTest  {
    private AtomicInteger integer;

    public ObjectTestAtomic() {
        super();
        this.integer = new AtomicInteger(0);
    }

    @Override
    public int getInteger() {
        return integer.get();
    }

    @Override
    public void setInteger(int integer) {
        this.integer.set(integer);
    }
}

더보기
package testvolatile;

import java.util.concurrent.atomic.AtomicBoolean;

public class TestMain {
    boolean bool = true;
    volatile boolean volatileBool = true;
    AtomicBoolean atomicBoolean = new AtomicBoolean(true);

    public void test() {
        new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
            bool = false;
            System.out.println(String.format("non-volatile: %s", bool));
        }).start();
        new Thread(() -> {
            while (bool) {
                // 아무 명령도 없는 경우에, bool을 갱신하지 않고 캐시 그대로 사용해서 종료되지 않음
            }
            System.out.println("non-volatile 종료됨");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
            volatileBool = false;
            System.out.println(String.format("volatile: %s", volatileBool));
        }).start();
        new Thread(() -> {
            while (volatileBool) {
                // 아무 명령도 없는 경우에도.
            }
            System.out.println("volatile 종료됨");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
            }
            atomicBoolean.set(false);
            System.out.println(String.format("atomicBoolean: %s", atomicBoolean.get()));
        }).start();
        new Thread(() -> {
            while (atomicBoolean.get()) {
                // 아무 명령도 없는 경우에도.
            }
            System.out.println("atomicBoolean 종료됨");
        }).start();
    }

    public static void main(String[] args) {
        TestMain testMain = new TestMain();
        testMain.test();

        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
        }
        System.out.println("--------------------------------------------------");






        ObjectTestVolatile objectTestVolatile = new ObjectTestVolatile();
        for (int i = 0; i < 200; ++i) {
            new Thread(() -> {
                objectTestVolatile.setInteger(objectTestVolatile.getInteger() + 1);
                System.out.println(String.format("%10s: %03d", "volatile", objectTestVolatile.getInteger()));
            }).start();
        }
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
        System.out.println("--------------------------------------------------");



        ObjectTestSync objectTestSync01 = new ObjectTestSync();
        for (int i = 0; i < 200; ++i) {
            new Thread(() -> {
                objectTestSync01.setInteger(objectTestSync01.getInteger() + 1);
                System.out.println(String.format("%10s: %03d", "sync", objectTestSync01.getInteger()));
            }).start();
        }
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
        System.out.println("--------------------------------------------------");



        ObjectTestSync objectTestSync02 = new ObjectTestSync();
        for (int i = 0; i < 200; ++i) {
            new Thread(() -> {
                synchronized (objectTestSync02) {
                    objectTestSync02.setInteger(objectTestSync02.getInteger() + 1);
                    System.out.println(String.format("%10s: %03d", "sync-block", objectTestSync02.getInteger()));
                }
            }).start();
        }
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
        System.out.println("--------------------------------------------------");



        ObjectTestAtomic objectTestAtomic = new ObjectTestAtomic();
        for (int i = 0; i < 200; ++i) {
            new Thread(() -> {
                objectTestAtomic.setInteger(objectTestAtomic.getInteger() + 1);
                System.out.println(String.format("%10s: %03d", "atomic", objectTestAtomic.getInteger()));
            }).start();
        }
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
        }
        System.out.println("--------------------------------------------------");
    }
}

더보기

atomicBoolean 종료됨

volatile 종료됨    (non-volatile 종료됨은 출력되지 않았고, 쓰레드도 계속 도는 상황!)

non-volatile: false

atomicBoolean: false

volatile: false

--------------------------------------------------

  volatile: 002

  volatile: 003

  volatile: 001

  volatile: 004

  volatile: 005

  volatile: 006

  volatile: 008

  volatile: 009

..........

  volatile: 197

  volatile: 198

  volatile: 199

  volatile: 200

--------------------------------------------------

      sync: 003

      sync: 005

      sync: 002

      sync: 005

      sync: 001

      sync: 006

      sync: 007

      sync: 009

..........

      sync: 118

      sync: 178

      sync: 176

      sync: 174

--------------------------------------------------

sync-block: 001

sync-block: 002

sync-block: 003

sync-block: 004

sync-block: 005

sync-block: 006

sync-block: 007

sync-block: 008

..........

sync-block: 197

sync-block: 198

sync-block: 199

sync-block: 200

--------------------------------------------------

    atomic: 001

    atomic: 002

    atomic: 003

    atomic: 005

    atomic: 004

    atomic: 006

    atomic: 007

    atomic: 008

..........

    atomic: 193

    atomic: 194

    atomic: 196

    atomic: 196

    atomic: 197

    atomic: 198

    atomic: 199

    atomic: 200

--------------------------------------------------


ㆍ코드로 보면 "non-volatile 종료됨"이 출력되어야할 것 같지만,

 while (bool)에서 bool캐시에 둔 값만 사용해서 계속 true로 사용하여 종료되지 않는다.

ㆍ반면 while (volatileBool)volatileBool을 메인 메모리에 두기 때문에, 정상적으로 종료된다. (volatile 종료됨)

while (bool)도 내부에 코드가 있으면 캐시를 새로 고치려 하기 때문에,

 일부러 이런 상황을 만들기도 어렵지만, 실제로 발생할 수 있는 상황이다.

 

volatile 키워드는 변수를 메인 메모리에 저장할 뿐, 그렇다고 thread-safe한 결과를 내진 않는다.

 값을 읽을 때는 캐시와의 차이는 없더라도, 쓰레드간 순서는 보장하지 않는다.

 volatile이 multi-thread 상황에서 각자의 캐시간 값 차이를 없앨 수 있는 수단 중 하나일 뿐,

 그 순서를 보장해주진 않는다. 순서는 lock의 개념으로 가야한다.

 multi-thread 상황 때문에 둘을 한 범주에 두고 생각해버릴 수 있다.

 

volatile 멤버 변수를 둔 개체라도, multi-thread에서 순서는 보장 되지 않는 것을 결과로 확인할 수 있다.

getter()setter()가 synchronized 메서드라도,

 System.out.println()synchronized 메서드가 아니기 때문에,

 출력에서 순서가 보장되지 않는다.

 (사용하는 모든 메서드가 동일한 개체에 synchronized여야 순서를 보장받을 수 있다.)

synchronized () {} 블록으로 묶은 후에야 순서가 보장되었다.

 

ㆍAtomicWrapper 클래스를 역시 내부에 volatile 키워드를 사용하여 thread별 캐시간 값 차이를 없앨을 뿐,

 순서는 보장해주지 않는다.

 

 


ㆍ여러 Thread에서 동시에 접근하는 변수라면, volatile 키워드를 사용한다.

ㆍvolatile 변수라도 자체적으로 lock을 해주는 것은 아니다.

 multi-thread에서 순서를 보장하려면 synchronized 키워드나 Semaphore 클래스가 필요하다.

ㆍ한 mutex에 묶인 synchronized가 아니라면, 그 사이에는 synchronized 되지 않은 채 진행되므로,

 원하는 결과를 얻지 않을 수 있다.

 사용하는 메서드와 흐름을 잘 파악하고 묶어야한다.

 

 

'Study > Java' 카테고리의 다른 글

Java의 hashCode  (0) 2020.11.04
Comments

잘못된 정보가 있다면, 꼭 댓글로 알려주세요(비로그인 익명도 가능).

여러분의 피드백이 저와 방문자 모두를 올바른 정보로 인도할 수 있습니다.

감사합니다. -현록

후원해주실 분은 여기로→