• 카테고리

    질문 & 답변
  • 세부 분야

    취업 · 이직

  • 해결 여부

    미해결

volatile 관련 자바 classname.this 질문

24.03.26 13:12 작성 조회수 76

0

DEEP DIVE : 싱글톤 패턴을 구현하는 7가지 방법 #2 ★★☆

강의 volatile 설명에서 알려주신 자바 코드와 동일하게 Kotlin 코드를 작성했습니다.

class Test2 {
    private var flag = true

    fun test() {
        Thread {
            var cnt = 0
            while (flag) {
                cnt++
                Thread.sleep(100)
                println(cnt)
            }
            println("Thread1 finished")
        }.start()
        Thread {
            Thread.sleep(100)
            println("flag to false")
            flag = false
        }.start()
    }
}


이 코드를 실행하면, flag가 바뀌는 즉시 첫 번째 쓰레드가 멈춥니다. 이유가 궁금해서 자바로 디컴파일된 코드를 확인해봤고, Thread1에서 flag를 확인할 때 Test2.this.flag와 같이 접근하는 것을 발견했습니다.



ClassName.this 가 무엇인지 궁금합니다. 자바의 정석 책에 해당 내용은 없고, 구글에 검색해봤는데, 유의미한 답변으로 아래 글만 발견했습니다.

Within an instance method or a constructor, this is a reference to the current object — the object whose method or constructor is being called. You can refer to any member of the current object from within an instance method or a constructor by using this.

https://stackoverflow.com/questions/4080868/using-this-with-class-name/46091971#46091971

답변 1

답변을 작성해보세요.

0

안녕하세요 안농님ㅎㅎ

해당 코드외 다른 언어로된 코드 질문은 죄송하지만 받지 않습니다.

 

감사합니다.

ClassName.this는 Java 코드 입니다 ㅠ

Kotlin이 더컴파일된 코드 또한 Java 코드 입니다...

음.. 실행했다고 말씀하신게

코틀린 파일 실행 해서 해당 부분 디버깅 - 자바을 질문하신게 아닌가요..?

 

제가 코틀린을 못해서요 ㅠㅠ

 

혹시 그러면 단순히 자바에서의 this를 얘기하시는 건가요?

그거는 아닐것 같긴한데...

 

그부분은 당연히 설명 가능합니다.

 

public class Car {
    private String color;

    public void setColor(String color) {
        this.color = color; // 여기서 'this.color'는 인스턴스 변수를, 'color'는 파라미터를 가리킵니다.
    }
}

this는 보통.. 이럴 때 쓰긴합니다. #1 (인스턴스 반환 등 여러가지가 더 있긴합니다.)

클래스 내부에서, 메서드나 생성자의 파라미터 이름이 클래스의 인스턴스 변수와 동일할 때, 이 두 변수를 구분하기 위해 this 키워드를 사용할 수 있습니다.

이 방법을 사용하면 로컬 변수(메서드나 생성자의 파라미터)와 인스턴스 변수를 명확히 구분할 수 있습니다.

 

이런거를 물어보신건가요?

자바 관련해서 물어보시는 거면 예시코드 들어주시면서 (스샷말구 코드) 말씀해주시면 상세히 답변드리겠습니다.

 

감사합니다.

Kotlin이 디컴파일된 자바 코드가 자바 코드와 동일하다고 생각되어 질문했는데,
디컴파일된 자바 코드와 동일하게 작성해도 다르게 동작하네요(Thread1이 멈추지 않음)...

 

public class Test {
    boolean flag = true;

    public void test() {
        new Thread(() -> {
            int cnt = 0;
            while (Test.this.flag) {
                cnt++;
            }
            System.out.println("Thread1 finished\n");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("flag to false");
            Test.this.flag = false;
        }).start();
    }
}


Q1. 이 부분은 너무 딥한 내용인가요? 굳이 면접에서 물어보지 않을 내용을 제가 파고 있는 걸까요?

Q2. Kotlin 디컴파일된 코드 != 자바 코드 인걸까요?

질문을 정정하겠습니다.
Kotlin 코드에서는 첫 번째 thread에서 while문을 돌 때 Thread.sleep을 걸어놨었습니다.

Java에서도 동일하게 첫 번째 thread가 while문에서 cnt 하는 중간에 thread.sleep을 거니까, volatile 없이도 while문을 벗어나는데, 이유가 무엇인가요?

public class Test {
    boolean flag = true;

    public void test() {
        new Thread(() -> {
            int cnt = 0;
            while (flag) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                cnt++;
            }
            System.out.println("Thread1 finished\n");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("flag to false");
            flag = false;
        }).start();
    }
}

안녕하세요 안농님 ㅎㅎ

Thread.sleep() 호출은 현재 스레드를 일시 정지시키고 다른 스레드에 실행 기회를 줍니다. 이 과정에서 메인 메모리와 CPU 캐시 간의 데이터 동기화가 발생할 수 있습니다. 이 과정에서 volatile 없이도 메모리 가시성을 어느정도 보장받을 수도 있습니다.

그러나 이부분은 JVM 버전, 운영체제 등에 따라 달라질 수 있습니다. UB라고 보시면 됩니다.

  • UB : unexpected behavior / 이는 프로그램이 예측 불가능한 방식으로 동작할 수 있음을 의미하며, 종종 오류, 보안 취약점, 시스템 충돌 등을 야기할 수 있습니다.

따라서 volatile을 쓰시는게 좋습니다. 이를 통해 멀티스레딩 환경에서 변수의 메모리 가시성을 명시적으로 보장하는 것이죠.

 



또 질문 있으시면 언제든지 질문 부탁드립니다.

좋은 수강평과 별점 5점은 제게 큰 힘이 됩니다. :)

감사합니다.

강사 큰돌 올림.


 

public class Test {
    boolean flag = true;

    public void test() {
        new Thread(() -> {
            int cnt = 0;
            while (flag) {
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName());
                cnt++;
            }
            System.out.println("Thread1 finished\n");
        }).start();
        new Thread(() -> {
            try {
                Thread.sleep(100);
            } catch (InterruptedException ignored) {
            }
            System.out.println("flag to false");
            flag = false;
        }).start();
    }
}


sleep 하는 과정에서 Thread가 변경되는지 확인해보니, Thread-1으로 동일합니다.

sleep 하는 과정에서 Thread가 변경되는지 확인해보니, Thread-1으로 동일합니다.

>>

일단 다시 설명드리면...

Thread.sleep() 호출은 현재 실행 중인 스레드를 일시 정지시키며, 이는 운영 체제 스케줄러에게 CPU 시간을 다른 스레드에게 할당할 기회를 제공합니다. 이러한 컨텍스트 전환(context switch) 과정에서 CPU 캐시에 저장된 데이터가 메인 메모리와 동기화될 가능성이 있습니다.

또한

Thread.sleep()을 호출할 때 발생할 수 있는 메모리 동기화는 우연히 발생할 수 있는 부수적인 효과일 뿐입니다.

 

이 코드로 보시면 더 정확합니다.

public class MyClass {
     
    boolean flag = true;

    public void test() {
        new Thread(() -> {
            int cnt = 0; 
            while (flag) {
                System.out.println(Thread.currentThread().getName());  
                cnt++;
            }
            System.out.println("Thread0 finished\n");
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName());   
            System.out.println("flag to false");  
            flag = false;
            System.out.println("Thread1 finished\n");
        }).start();
    }

    public static void main(String[] args) {
        // Create an instance of MyClass and call the 'test' method on it.
        new MyClass().test();
    }
}

 

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-0

Thread-1

flag to false

Thread-0

Thread0 finished

Thread1 finished

 

 

sleep 추가 코드

public class MyClass {
     
    boolean flag = true;

    public void test() {
        new Thread(() -> {
            int cnt = 0;
            while (flag) {
                System.out.println(Thread.currentThread().getName() + " before sleep"); 
                
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + " after sleep");
                cnt++;
            }
            System.out.println("Thread0 finished\n");
        }).start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " before sleep"); 
            
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } 
            System.out.println(Thread.currentThread().getName() + " after sleep");
            System.out.println("flag to false"); 
            flag = false;
            System.out.println("Thread1 finished\n");
        }).start();
    }

    public static void main(String[] args) {
        // Create an instance of MyClass and call the 'test' method on it.
        new MyClass().test();
    }
}

주석

Thread-1 before sleep

Thread-0 before sleep

Thread-1 after sleep

flag to false

Thread1 finished

Thread-0 after sleep

Thread0 finished

이렇게 보시면 더 명확합니다.




또 질문 있으시면 언제든지 질문 부탁드립니다.

좋은 수강평과 별점 5점은 제게 큰 힘이 됩니다. :)

감사합니다.

강사 큰돌 올림.