case 1 : 그냥 공유 변수를 사용할 때
class Ex1{
static class Memory {
private int var;
public int getVar() { return var; }
public void setVar(int var) { this.var = var;}
}
void ThreadConflict() throws InterruptedException {
//공유 변수 클래스
Memory memory = new Memory();
//Thread 동작
Runnable logic = () -> {
for(int i = 0; i< 10000; i++) {
int var = memory.getVar();
memory.setVar(++var);
}
};
//Thread 생성
Thread A = new Thread(logic);
Thread B = new Thread(logic);
//Thread 동작 시작
A.start();
B.start();
//Thread 동작 기다림
A.join();
B.join();
System.out.println("memory var = " + memory.getVar());
}
}
# main 메서드는 생략했다.
위의 예제에서는 메모리의 변수를 10000번 증가시키는 쓰레드를 2개 생성한 이후 변수의 값을 출력하는 예제이다. 하나의 쓰레드가 10000번 증가시키기 때문에 논리적으로는 변수의 값은 0에서 20000이 되어야하지만 실제 실행하면 20000이 아닌 더 적은 수가 출력된다.
case 2 : Synchronized를 사용했을 때
class Ex2{
static class Memory {
private int var;
public int getVar() { return var; }
public void setVar(int var) { this.var = var;}
}
void ThreadConflict() throws InterruptedException {
//공유 변수 클래스
Memory memory = new Memory();
//Thread 동작
Runnable logic = new Runnable() {
@Override
public void run() {
for(int i = 0; i< 10000; i++) {
synchronized (this){//동기화
int var = memory.getVar();
memory.setVar(++var);
}
}
}
};
//Thread 생성
Thread A = new Thread(logic);
Thread B = new Thread(logic);
//Thread 동작 시작
A.start();
B.start();
//Thread 동작 기다림
A.join();
B.join();
System.out.println("memory var = " + memory.getVar());
}
}
synchronized를 사용하여 변수의 원자성을 보장함으로써 동기화를 이루워준다. 2번 예제의 경우에는 정상적으로 출력이 20000의 값으로 나온다.
위와 같은 경우에는 해당 int 변수를 AtomicInteger를 사용하여 원자성을 보장하는 방법도 있다.
case 3 : 지역 변수 동기화 x
class EXLocal1{
static class Market {
private Integer 빵 = 0;
public void set빵(Integer 빵) {
this.빵 = 빵;
}
public synchronized int 빵가져감(){
if(빵 == 0) return 0;//빵이 없으면 빵을 못준다고 0을 반환
this.빵--;
return 1; //빵이 있으면 빵개수를 차감하고 빵 한개를 반환
}
}
void ThreadConflict() throws InterruptedException {
Market market = new Market();
market.set빵(20000); //나누어줄 빵의 개수 정함
Runnable 불우이웃Logic = () -> {
int 나누어준빵 = 0;
for(int i = 0; i< 20000; i++) {
나누어준빵+=market.빵가져감();
}
System.out.println("[불우이웃] 나누어준 빵 = " + 나누어준빵);
};
Runnable 도둑Logic = () -> {
int 훔친빵 = 0;
for(int i = 0; i< 20000; i++) {
훔친빵+=market.빵가져감();
}
System.out.println("[도둑] 훔친 빵 = " + 훔친빵);
};
Thread 불우이웃 = new Thread(불우이웃Logic);
Thread 도둑 = new Thread(도둑Logic);
불우이웃.start();
도둑.start();
불우이웃.join();
도둑.join();
}
}
이번의 경우는 불우이웃에게 빵을 나누어주려하는데 도둑이 빵을 훔치려는 상황을 코드로써 재현한 예제이다. 도둑에게 빵을 뺏기면 안되지만 위의 경우에는 도둑이 마켓에서 빵에 접근하여 불우이웃에게 돌아갈 빵을 빼앗기게 된다. 이러한 상황을 막기위해서 사용하는 것이 ThreadLocal 이다.
case 4 : ThreadLocal 사용
class EXLocal2{
static class Market {
private final ThreadLocal<Integer> 빵 = new ThreadLocal<>();
public void set빵(Integer 빵) {
this.빵.set(빵);
}
public synchronized int 빵가져감(){
if(빵.get() == 0) return 0;//빵이 없으면 빵을 못준다고 0을 반환
this.빵.set(this.빵.get() - 1);
return 1; //빵이 있으면 빵개수를 차감하고 빵 한개를 반환
}
}
void ThreadConflict() throws InterruptedException {
Market market = new Market();
Runnable 불우이웃Logic = () -> {
market.set빵(20000);
int 나누어준빵 = 0;
for(int i = 0; i< 20000; i++) {
나누어준빵+=market.빵가져감();
}
System.out.println("[불우이웃] 나누어준 빵 = " + 나누어준빵);
};
Runnable 도둑Logic = () -> {
market.set빵(0);
int 훔친빵 = 0;
for(int i = 0; i< 20000; i++) {
훔친빵+=market.빵가져감();
}
System.out.println("[도둑] 훔친 빵 = " + 훔친빵);
};
Thread 불우이웃 = new Thread(불우이웃Logic);
Thread 도둑 = new Thread(도둑Logic);
불우이웃.start();
도둑.start();
불우이웃.join();
도둑.join();
}
}
ThreadLocal를 사용하면 각 쓰레드 마다 해당 변수를 사용하는 공간이 격리되어 생성된다. 따라서 쓰레드에서 처음 해당 변수 사용할때 초기화를 해야한다. 위의 코드에서 market.set(int); 는 초기화를 위한 명령이다.
격리된 변수 공간을 각 쓰레드마다 가지고 있기 때문에 도둑이 불우이웃에게 전달할 빵을 가져가지 못하도록 동기화가 되었다.
+WAS에서 ThreadLocal 주의 사항
WAS에서는 쓰레드 생성시 자원의 소모가 심하기 때문에 미리 쓰레드를 생성하고 관리하는 쓰레드 풀을 가지고 있다.
해서 WAS가 종료되기 전까지는 쓰레드가 종료되지 않기 때문에 쓰레드는 사실상 영구적이다.
그러므로 ThreadLocal을 사용할 때 사용한 이후 ThreadLocal.remove를 통해서 ThreadLocal 변수를 제가하지 않는다면 불필요한 메모리 공간을 계속해서 쓰레드가 점유하고 있기 때문에 주의를 해야한다.
'programming' 카테고리의 다른 글
[파일 시스템] 파일 삭제 코드 (0) | 2023.02.08 |
---|---|
SOLID (0) | 2022.01.17 |
HashTable (0) | 2021.06.24 |
HashSet (0) | 2021.06.24 |