Java
Thread 관련 - Object, ThreadGroup, ThreadLocal, volatile
jamie.
2020. 1. 16. 20:18
반응형
Object 클래스에 선언된 Thread와 관련된 메서드들
/* 다른 Thread가 Object 객체에 대한 notify() 메서드나 notifyAll() 메서드를
* 호출할 때까지 현재 Thread가 대기하고 있도록 함
* WAITING 상태로 변경됨
*/
void wait()
/* wait() 메서드와 동일한 기능을 제공
* 매개 변수에 지정한 시간만큼만 대기
* 매개 변수 시간을 넘어섰을 때, 현재 Thread는 다시 깨어남
* 시간은 밀리초로 1/1,000초 단위 - 1초일 경우 1000
*/
void wait(long timeout)
/* wait() 메서드와 동일한 기능을 제공
* 보다 자세한 밀리초(1/1,000) + 나노초(1/1,000,000,000)만큼 대기
* 나노초는 0 ~ 999,999까지 지정 가능
*/
void wait(long timeout, int nanos)
/* 모니터
* = lock 객체처럼 Thread Safe하게 수행하도록 도와주는 객체
* = synchronized(monitor) { } 이런식으로 사용
*/
// Object 객체의 모니터에 대기하고 있는 단일 Thread를 깨움 - 먼저 대기하는 Thread부터
void notify()
// Object 객체의 모니터에 대기하고 있는 모든 Thread를 깨움
void notifyAll()
사용 예
package jamie.thread;
public class StateThread extends Thread {
private Object monitor;
// monitor라는 이름의 객체를 매개변수로 받아 인스턴스 변수로 선언
public StateThread(Object monitor) {
this.monitor = monitor;
}
public void run() {
try {
// Thread를 실행중인 상태로 만들기 위해 간단하게 loop를 돌며 String 객체 생성
for(int loop = 0; loop < 10000; loop++) {
String a = "A";
}
// synchronized 블럭 안에서 monitor 객체의 wait() 메서드를 호출
synchronized(monitor) {
monitor.wait();
}
System.out.println(getName) + " is notified");
// wait() 상황이 끝나면 1초간 대기했다가 해당 쓰레드 종료
Thread.sleep(1000);
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
package jamie.thread;
public class RunThreads {
public static void main(String[] args) {
RunTreads threads = new RunThreads();
threads.checkThreadState();
}
public void checkThreadState() {
// StateThread의 매개 변수로 넘겨줄 monitor라는 Object 클래스 작성
Object monitor = new Object();
StateThread thread = new StateThread(monitor);
try {
System.out.println("thread state = " + thread.getState());
// Thread 객체를 생성하고 시작
thread.start();
System.out.println("thread state(after start) = " + thread.getState());
Thread.sleep(100);
System.out.println("thread state(after 0.1 sec) = " + thread.getState());
// monitor 객체를 통해 notify() 메서드 호출
synchronized(monitor) {
monitor.notify();
}
Thread.sleep(100);
System.out.println("thread state(after notify) = " + thread.getState());
// Thread가 종료될 때까지 기다린 후 상태 출력
thread.join();
System.out.println("thread state(after join) = " + thread.getState());
} catch (InterruptedException ie) {
ie.printStackTrace();
}
}
}
/* 출력 결과
* thread state = NEW
* thread state(after start) = RUNNABLE
* thread state(after 0.1 sec) = WAITING
* Thread-0 is notified.
* thread state(after notify) = TIMED_WAITING
* thread state(after join) = TERMINATED
*/
ThreadGroup에서 제공하는 주요 메서드들
ThreadGroup - Thread의 관리를 용이하게 하기 위한 Class
기본적으로 tree 구조를 가짐 - 즉 다른 그룹에 속할 수도 있고, 다른 그룹을 포함할 수도 있음
// 실행중인 Thread의 개수를 리턴
int activeCount()
// 실행중인 ThreadGroup의 개수를 리턴
int activeGroupCount()
/* enumerate의 리턴값 = 배열에 저장된 값의 개수
* 위의 개수를 파악하는 메서드로 갯수 파악 후 배열을 맞춰 생성하면 됨
*/
// 현재 ThreadGroup에 있는 모든 Thread를 매개 변수로 넘어온 Thread 배열에 담음
int enumerate(Thread[] list)
/* 현재 ThreadGroup에 있는 모든 Thread를 매개 변수로 넘어온 Thread 배열에 담음
* 두 번째 매개 변수가 true이면 하위에 있는 ThreadGroup의 Thread 목록도 포함됨
*/
int enumerate(Thread[] list, boolean recurse)
// 현재 ThreadGroup에 있는 모든 ThreadGroup을 매개 변수로 넘어온 ThreadGroup 배열에 담음
int enumerate(ThreadGroup[] list)
/* 현재 ThreadGroup에 있는 모든 ThreadGroup을 매개 변수로 넘어온 ThreadGroup 배열에 담음
* 두 번째 매개 변수가 true라면 하위의 ThreadGroup 목록도 포함됨
*/
int enumerate(ThreadGroup[] list, boolean recurse)
// ThreadGroup의 이름을 리턴
String getName()
// 부모 ThreadGroup을 리턴
ThreadGroup getParent()
// ThreadGroup 상세정보 출력
void list()
// 지금 ThradGroup에 속한 Thread들을 Daemon으로 지정
void setDaemon(boolean daemon)
ThreadLocal - 각 Thread에서 혼자 쓸 수 있는 값
synchronized - 여러 Thread에서 데이터를 공유할 때 발생하는 문제를 해결
ThreadLocal - Thread 별로 서로 다른 값을 처리해야 할 필요가 있을 때 사용
ThreadLocal
-
ThreadLocal에 저장된 값은 해당 Thread에서 고유하게 사용할 수 있음
-
ThreadLocal 클래스의 변수는 private static final로 선언
-
ThreadLocal 클래스에 선언되어 있는 메서드 : set(), get(), remove(), initialValue()
-
사용이 끝난 후에는 remove() 메서드를 호출해주는 습관을 들일 것
remove() 메서드를 호출하는 이유
web 기반의 application에서는 Thread를 재사용하기 위해 ThreadPool이라는 것을 사용
ThreadPool을 사용하면, Thread가 시작된 후 그냥 끝나는 것이 아니기 때문에 remove()를 사용해서 값을 지워줘야만 해당 Thread를 다음에 사용할 때 쓰레기 값이 들어있지 않게 됨
예제
ThreadLocal을 사용하지 않고 OtherLogic Class에서 임의로 만든 값을 사용하려면?
- printMyNumber() 메서드 호출시 매개 변수로 넘기기, 단) OtherLogic에서 호출하는 다른 메서드에서 해당 값을 사용하려면 또 매개 변수로 넘겨줘야 함
- LocalUserThread에서 인스턴스 변수를 선언하여 사용, 단) 구현이 매우 복잡해질 수 있음
package jamie.thread;
import java.util.Random;
public class ThreadLocalSample {
/* local이라는 ThreadLocal 객체 생성
* ThreadLocal은 제네릭한 클래스
* - 객체를 생성하기 위해서는 각 Thread에서 고유하게 사용할 데이터 타입을 지정해줘야 함
* 예제는 Integer
*/
private final static ThreadLocal<Integer> local
= new ThreadLocal<Integer>();
private static Random random;
static {
random = new Random();
}
public static Integer generateNumber() {
int value = random.nextInt(45);
// ThreadLocal 클래스의 set() 메서드를 이용해 저장하고자 하는 값 할당
local.set(value);
return value;
}
public static Integer get() {
// ThreadLocal 클래스의 get() 메서드를 이용하여 값을 꺼냄
return local.get();
}
public static void remove() {
// local 객체에 저장되어 있는 값을 제거하기 위해 remove() 메서드 사용
local.remove();
}
}
// ThreadLocal 클래스 확장 사용시, initalValue() 메서드를 Override하여 초기 값 지정도 가능함
// 현재 Thread의 이름과 ThreadLocalSample의 get() 메서드를 사용하여 값을 읽음
package jamie.thread;
public class OtherLogic {
public void printMyNumber() {
System.out.println(Thread.currentThread().getName()
+ " OtherLogic value = " + ThreadLocalSample.get());
}
}
package jamie.thread;
public class LocalUserThread extends Thread {
public void run() {
/* 간단하게 ThreadLocalSample의 generateNumber() 메서드 호출
* 임의의 변수를 ThreadLocal에 저장하고 리턴
*/
int value = ThreadLocalSample.generateNumber();
System.out.println(this.getName() + " LocalUserThread value = " + value);
OtherLogic otherLogic = new OtherLogic();
// OtherLogic이라는 Class의 printMyNumber란 메서드 호출
otherLogic.printMyNumber();
// ThreadLocal에서 값을 지움
ThreadLocalSample.remove();
}
}
package jamie.thread;
public class RunThreads {
public static void main(String[] args) {
RunTreads threads = new RunThreads();
threads.runLocalUserThread();
}
public void runLocalUserThread() {
LocalUserThread threads[] = new LocalUserThread[3];
for (LocalUserThread thread : threads) {
thread = new LocalUserThread();
thread.start();
}
}
}
/* 실행 결과
* Thread-1 LocalUserThread value=20
* Thread-1 OhterLogic value=20
* Thread-2 LocalUserThread value=01
* Thread-2 OtherLogic value=01
* Thread-3 LocalUserThread value=15
* Thread-3 OtherLogic value=15
*
* LocalUserThread에서 출력한 값과 OtherLogic에서 출력한 값이 동일함
* Thread별로 값을 절대 공유하지 않음
*/
Java의 volatile
변수 선언시에만 사용되는 예약어
예제
package jamie.thread;
public class VolatileSample extends Thread {
private double instanceVariable = 0;
// 1. volatile 사용
// private volatile double instanceVariable = 0;
void setDouble(double value) {
this.instanceVariable = value;
}
public void run() {
// instanceVariable이 0이면 계속 while문을 돌림
while (instanceVariable == 0);
System.out.println(instanceVariable);
}
/* 2. 대기시간을 1밀리초 줌
public void run() {
try {
while (instanceVariable == 0) {
Thread.sleep(1)
}
} catch (Exception e) {}
System.out.println(instanceVariable);
}
*/
}
package jamie.thread;
public class RunThreads {
public static void main(String[] args) {
RunTreads threads = new RunThreads();
threads.runLocalUserThread();
}
public void runValatileSample() {
VolatileSample sample = new VolatileSample();
sample.start();
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Sleep ended !!!");
sample.setDouble(-1);
System.out.println("Set value is completed !!!");
}
}
/* 결과
* Sleep ended !!!
* Set value is completed !!!
*
* 그런데 sample Thread가 종료되지 않음
* 원인 : CPU Cache 때문
* Thread에서 수행되는 변수의 값을 반복적으로 참조할 때는 "메인 메모리"에 저장되지 않음
* "CPU 캐시"에 저장되고 참조됨. 값을 바꾸게 되면, CPU 캐시의 다른 곳에 또 저장되는데,
* 진행중인 쓰레드는 기존 값(0)을 참조함. 즉 효과 없음.
*
* 1. volatile으로 선언된 Thread의 인스턴스 변수의 경우, 해당 변수 값이 바뀌면 추적을 해줌
* 같은 객체에 있는 변수는 모든 쓰레드가 같은 값을 바라보게 됨
* 단) 남발시 성능 저하 발생
*
* 모든 Thread의 인스턴스 변수에 volatile이라고 적어주지 않았다고 전부 데이터가 꼬이는건 아닌데,
* 왜 예제는 volatile을 적지 않았을 때 문제가 생겼을까?
* - JIT 컴파일러가 Optimization(최적화 작업)을 캐시간에 두고 최적화되었기 때문
* 2. 즉 최적화가 되지 않아 캐시간 데이터가 다른 값을 보지 않으면 volatile을 사용할 필요가 없음
*
* volatile은 데이터에 문제가 있을 경우에만 사용
*/
그 외, ThreadPool을 비롯한 Thread를 처리하는 Class는 java.util.concurrent 패키지의 Class를 참고
반응형