개요
static
와 synchronized
의 연관관계에 대해 한번 정리를 해보려고 한다.
일단 JAVA
에서 static
은 클래스가 로딩될 때 메모리의 정적 영역(Static Area)에 할당된다.
이 영역은 특징은 다음과 같다.
- JVM이 시작될 때 생성되며, 프로그램의 실행이 끝날 때까지 유지된다.
- 클래스의 모든 인스턴스가 공유하는 공통된 값은 가진다.
즉 멀티스레딩 환경에서 주의하여 사용해야한다.
테스트
아래 예시는 주의하지 않고 사용한 코드이다.
public class A {
public static int a;
public static void plus() {
a++;
}
}
public class ATest {
@Test
public void testPlusMethod() {
Runnable task = () -> {
for (int i = 0; i < 100; i++) {
A.plus();
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(task);
threads[i].start();
}
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
} ``
// 검증
assertEquals(1000, A.a);
}
}
위 코드의 기대 결과는 1000
이지만 (100(반복횟수) * 10(스레드 개수)) 실제 결과값은 1000
이 안나온다,
원인은 공유자원(static
)에 여러 스레드가 접근하여, plus
메소드를 호출하기 때문에 발생한다.
해결법은 간단하다. synchronized
을 이용하면 된다.
public class A {
public static int a;
public synchronized static void plus() {
a++;
}
}
테스트도 무난히 통과한다.
인스턴스 변수도 static과 마찬가지로 동시성 이슈가 발생한다. 특히 인스턴스를 1개만 사용하는 싱글톤 패턴에서 많이 발생한다.
지역변수만이 동시성 이슈로부터 자유로운데, 그 이유는 지역변수는 쓰레드 마다 갖고있는 메모리 공간(Stack 영역)에 저장되기 때문이다.
그렇다면, 객체의 필드를 사용하면서, 동시성 이슈를 해결할 수 있는 방법은 없을까?
쓰레드 로컬이 있다.
쓰레드 로컬
해당 쓰레드마다 접근할 수 있는 특별한 저장소이다. 즉 쓰레드마다 저장공간이 할당되어 있는 것이다.
아래 예제는 쓰레드 로컬을 사용한 코드이다.
public class A {
public ThreadLocal<Integer> a = new ThreadLocal<>();
public void set() {
a.set(0);
}
public int get() {
return a.get().intValue();
}
public void plus() {
int nA = a.get()+1;
a.set(nA);
}
}
쓰레드 로컬을 사용하기 위한 메소드들을 생성하였다. 시작할 때, 0으로 세팅 후, +1 씩 100번 돌린다.
각 쓰레드마다 메모리가 할당되었으니. 예상 값은 100이다.
A a = new A();
@Test
public void testPlusMethod() {
Runnable task = () -> {
a.set();
for (int i = 0; i < 100; i++) {
a.plus();
}
assertEquals(100,a.get());
};
task 시작전 set
후 plus
하고, 예상값 100인지 확인한다.
위코드는 정상작동한다!
값 저장 : ThreadLocal.set()
값 조회 : ThreadLocal.get()
값 제거 : ThreadLocal.remove()
주의해야할 점이 있다.
- 쓰레드 로컬을 사용하고 나면 반드시 해당 쓰레드가 가지고 있는 메모리를
ThreadLocal.remove()
로 제거해줘야한다. 제거하지 않으면, 메모리 누수 가능성이 있다.
'개발자로서 살아남기' 카테고리의 다른 글
스카우터를 활용한 서비스 성능 최적화 (0) | 2023.09.19 |
---|---|
Git 원격 저장소 성능 유지하기 (feat - Git 크라켄 성능 최적화) (1) | 2023.08.29 |
서버 개발자로 살아남기 - HAProxy vs NGINX (0) | 2023.01.10 |
서버 개발자로 살아남기 - Nginx (0) | 2023.01.05 |
서버 개발자로 살아남기 - 서블릿 컨테이너에 대해 (0) | 2022.12.07 |