규칙 72 - 스레드 스케줄러에 의존하지마라.

JAVA/Effective Java|2018. 6. 15. 23:26

실행해야 할 스레드가 많을 경우 어떠한 스레드를 얼마나 오랫동안 실행할지 결정은 스레드 스케줄러가 진행한다.

운영체제마다 스레드 스케줄러는 다르기 때문에 아무리 운영체제에서 효율적으로 진행한다고 하더라고 이에 의존하여 프로그램을 제작해서는 안된다.

정확하고 좋은 스레드 프로그램은 의존하는것이 아니라 실행가능한 스레드의 개수가 프로세수 개수보다 넘지 않도록 제작하는 것이다.

그렇게되면 스레드 스케줄러가 순차적으로 스레드를 실행시켜줄뿐 정책에 신경쓰지않는다.

그렇다면 실행중인 스레드의 개수를 최대한 줄일 수 있는 방법은 무엇일까?

바로 사용하지 않는 스레드는 실행하지 않고 정지하거나 종료해야한다. 그래서 바로 직전에 공부했던 스레드 풀을 사용하여 적절하게 스레드를 관리하면 좋은 프로그램을 만들 수 있다.

그렇다면 오래 반환하지 않고 잘못된 스레드 방식으로 개발하는 코드를 알아보자.

public class FailLatch {
	private int count;
	public FailLatch (int count) {
		if (count < 0) {
			throw new IllegalArgumentException(count + " < 0");
		}
		this.count = count;
	}
	
	public void await() {
		while (true) {
			synchronized (this) {
				if (count == 0) {
					System.out.println("the end");
					return;
				}
					
			}
		}
	}
	
	public synchronized void countDown() {
		if (count != 0) {
			count--;
		}
	}

}

 위의 코드를 보면 count가 0일 때까지 스레드가 놀고있어야 하는 아주 좋지 않은 프로그램이 된다. 만약 그럼 저상태에서 대기상태에서 Thread.yield를 사용한다면 조금 나아지려나?

그렇지 않다. 왜냐하면 이는 일부 JVM에서는 성능이 향상되는 것처럼 보일 수 있으나, 무조건 좋아지지 않는다. 그렇기 때문에 병렬적으로 실행 가능한 스레드 수를 애초에 줄이는것이 중요하다.

결론을 이야기하자면 프로그램의 정확성을 스레드 스케줄러에 의존하지말고 쓰레드 수를 제한하고 일부 쓰레드가 무조건 낭비되구 있는것을 방지하자. 단 Thread.yield 등과 같이 임시방편으로 무엇을 해결하려 하지 말고 근본적 문제를 해결하라.

 

출처 : 조슈아  블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1)

 

댓글()

규칙 68 - 스레드보다는 실행자와 태스크를 이용하라.

JAVA/Effective Java|2018. 6. 13. 09:35

여러 쓰레드를 실행해야할 때,  큐에 넣고 작업을 진행하거나 할 수 있으면 더욱 효율적으로 관리 할 수있다.


그래서 자바 1.5부터 자바 플랫폼에는 java.util.concurrent가 추가되었다. 이 패키지에는 Executor Framework가 들어 있는데 이는 인터페이스 기반 task 실행 프레임워크이다.


해당 Executor를 실행하기 위해서는 다음과 같이 입력하면 된다.

1
2
3
4
5
6
7
ExecutorService executor = Executors.newSingleThreadExecutor();
    executor.    
    @Override
    public void run() {
        System.out.println("test");
    }
});
cs


그리고 만약 executor안에 요소가 모두 실행이 끝난뒤에는 실행자가 꺼지지는 않는다.

이를 명시적으로 꺼주어야 하는데 그때 shutdown() 메소드를 이용하면 된다.


이른 기능을 제외하고도 임의에 task의 작업을 기다릴 수도 있고 task가 끝날때마다 정보를 가져오도록 할수도 있다. 


여러 작업을 관리해야하는 경우에는 ThreadPool을 만들어서 작업할 수 있다. 이런 작업을 제공하는 ThreadPool은 newCachedThreadPool과 newFixedThreadPool이 있다.


먼저 newCachedThreadPool은 부하가 크지않고 작은 프로그램에서 사용하기에 적합하다. 설정도 필요없고 보통 많은일을 알아서 처리한다. 하지만 작업량이 많은곳에서는 적합하지 않다. 

왜냐하면 해당 쓰레드풀은 작업이 큐에 들어가는 것이 아니라 실행을 담당하는 스레드에 바로 넘겨진다. 그렇기 때문에 task가 많이 지정될 경우에는 상당히 많은 양의 쓰레드가 생성이 되어 CPU의 사용량이 증가된다.


그렇기 때문에 이를 보안하는 ThreadPool이 있는데 newFixedThreadPool이다.


newFixedThreadPool은 스레드 개수가 고정된 풀을 만들어서 제어가 손쉽다.


이런 실행자들을 이용하여 쓰레드를 관리하면 별도의 개별쓰레드를 만들어서 관리하는 것보다 훨씬 편하고 안정적이다.


  출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙68



댓글()

규칙 67 - 과도한 동기화는 피하라

JAVA/Effective Java|2018. 6. 10. 21:17

동기화 시에 너무 많은 동기화 블록을 사용할 경우에 데드락이 걸리거나 성능저하 등등 문제를 일으킬 수 있는 소지들이 몇 가지 있다.


특히 동기화 영역안에서 수행되는 작업의 양을 가능한 줄여야 한다.


자바에서는 동기화에 대한 비용처리가 그나마 잘되어있지만 잘 사용해야 하는 이유는 잘못된 동기화 사용은 각 쓰레드들의 메인 메모리 접근에 대한 지연시간을 늘릴 수 있기 때문에 비용이 증가할 수 있다.


또한 클래스 내에서 동기화를 수행하는 것이 외부에서 객체 호출 시 사용하는것 보다 높은 병행성을 달성 할 수 있을 때문 진행해야한다.


다시말하자면 필요할 때 해당 메서드등을 호출하여 동기화를 실행해야지 해당 메서드 자체를 동기화 하는것은 좋지 않다. 예를 들면 기존에는 StringBuffer를 사용하여 내부적으로 Thread-safe하게 동작하여 많이 사용하였지만 굳이 병렬성을 보장하지 않아도 되는경우에도 자주 사용하여 문제가 자주 발생했다.


그래서 StringBuilder이 생겼고, 필요시에 동기화 블록을 만들어서 Thread safe하게 처리를 하였다.


그리고 


static 필드를 변경하는 메서드가 있을 때는 해당 필드에 대한 접근을 반드시 동기화 해야한다. 왜냐하면 해당 필드에 대해서 다른 쓰레드가 접근하고 있는지에 대해 알 수 있는 방법이 없기 때문이다. 


요약하자면 주의해서 동기화 블록을 사용하고 해당 내용에 대해서 문서에 정확히 적어주어야 한다.

문서에 해당내용을 잘 적지 않는 개발자들이 많다. 잘 적지 않고 알아서 코드를 보고 분석해서 그때그때 사용하기를 원하는 사람들을 일을 하면서 많이 보았다.


물론 같은 개발자로서 코드로 이야기 한다는 명목안에서 그럴 수 있지만, 일에 대한 처리가 순조롭지 않다. 그렇다면 본인들은 개발을 진행할 때 필요한 라이브러리가 있을 때 Document를 보지 않고, 코드를 모두 분석 후 사용하는지 묻고 싶다.. 


협력하는 시대에 협력하지 않고 구태적 마인드를 가지고 있다면 그는 효울적인 자바코드를 사용하는 근본이 잘못되었다고 생각한다.


 출처 : 조슈아 블로크, 『 Effective Java 2/E』, 이병준 옮김, 인사이트(2014.9.1), 규칙67

댓글()

Java Thread 대표 메서드 소개 및 특징 정리

JAVA/Thread|2018. 5. 27. 19:57

처음 입사 후 담당했던 프로젝트의 경우 단일 스레드로 동작하며,

동작 필요에 따라 Thread를 만들어주고 UI에 Lock 걸리는 것을 방지하기 위해 UI Thread 처리도 별도로 해주어야 했다.

그래서 Thread에 대해 많이 익숙 했었다.

하지만 Web 프로젝트를 진행하면서 Container에서 기본적으로 Multi-Thread를 지원해주기 때문에 동기화 처리를 제외하고는 그렇게 크게 Multi-Thread에 대해 처리를 해줄 필요가 없게 되었다.

핑계일 수 있지만 이러한 이유로 많이 잊어먹은 Thread에 대해 다시한번 정리해보았다.



Thread 실행
Thread에서 start를 해야한다.
start()메소드는 새로운 쓰레드가 작업을 실행하는데 필요한 호출스택(공간)을 생성한 다음 run()을 호출해서 그 안(스택)에 run()이 저장되는 것이다.

Thread 우선순위
Thread는 우선순위를 1 ~ 10 까지 부여가 가능하다. (1이 가장 낮고 10이 가장 높다.)
부여하지 않을 경우 5의 우선순위를 할당받게 된다.
최소 스레드가 5개 이상이어야 그때부터 순서가 적용된다. 그 이하는 순서가 아무런 소용이 없다.

동기화블록
만약 동기화 블록에 this를 넣을 경우 해당 객체를 잠그는 것으로써 동기화 블록들이 모두 실행될 때까지 다른 스레드들은 this(ex. calculator)의 모든 동기화 메소드, 블록을 사용할 수 없다.


※ 주요 메서드

Yield
현재 쓰레드를 정지 하고 다른 쓰레드에게 실행을 양보하는 Yield


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class ThreadTest extends Thread{
    
    private boolean isYield;
    
    public ThreadTest(boolean isYield) {
        this.isYield = isYield;
    }
 
    @Override
    public void run() {
        if (isYield) {
            System.out.println(this.getName() + " is Yielded");
            Thread.yield();
            System.out.println(this.getName() + " is Started");
        } else {
            System.out.println(this.getName() + " is Started");
            Thread.yield();
            System.out.println(this.getName() + " is Notify()");
        }
    }
}
 
public class TestClass {
    public static void main(String args[]) {
        ThreadTest thread1 = new ThreadTest(true);
        ThreadTest thread2 = new ThreadTest(false);
        
        thread1.start();
        thread2.start();
    }
}
 
출력결과
Thread-1 is Started
Thread-0 is Yielded
Thread-1 is Notify()
Thread-0 is Started
cs





join()
특정 스레드가 끝날때 까지 대기하고 그 쓰레드가 종료되면 현재 스레드 실행


1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TestClass {
    public static void main(String args[]) {
        ThreadTest thread1 = new ThreadTest(true);
        
        
        try {
            // thread1이 종료되면 메인스레드 계속 진행
            thread1.join();
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}
cs




wait(), notify(), notifyAll()
wait()를 통해 스레드를 정지상태로 만들고 notify()는 wait() 상태에 스레드 하나를 대기상태로 만든다.
notifyAll() 메서드는 wait()에 의해 일시 정지된 모든 스레드들을 실행 대기 상태로 만든다.
※ 단 이 메소드들은 동기화 메서드 또는 동기화 블록에서만 실행될 수 있다.



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
package enum32;
 
import java.util.ArrayList;
import java.util.List;
 
public class ShareObject {
    
    private List<String> datas;
    private List<Boolean> checkGetDatas;
    
    public ShareObject(int size) {
        datas = new ArrayList<>(size);
        checkGetDatas = new ArrayList<>(size);
        for (int i = 0; i < size; i++) {
            datas.add(null);
            checkGetDatas.add(null);
        }
    }
    
    public synchronized String getData(int index) {
        if (null == datas.get(index)) {
            try {
                // 데이터가 없는경우 대기
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        System.out.println("Produced Data : " + datas.get(index));
        checkGetDatas.add(index, true);
        notify();
        
        return datas.get(index);
    }
    
    public synchronized void setData(int index, String data) {
        if ( null != datas.get(index) && null == checkGetDatas.get(index) ) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        
        datas.add(index, data);
        System.out.println("Setted Data : " + data);
        notify();
    }
 
}
 
package enum32;
 
public enum Mode {
    SET, GET;
}
 
package enum32;
 
public class ThreadObject implements Runnable {
    
    private Mode mode;
    private ShareObject shareObject;
    private int size;
    
    public ThreadObject(Mode mode, ShareObject shareObject, int size) {
        this.mode = mode;
        this.shareObject = shareObject;
        this.size = size;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < size; i++) {
            if (mode.equals(Mode.GET)) {
                shareObject.getData(i);
            } else if (mode.equals(Mode.SET)) {
                shareObject.setData(i, "data" + i);
            }
        }
    }
 
}
 
package enum32;
 
public class ThreadObject implements Runnable {
    
    private Mode mode;
    private ShareObject shareObject;
    private int size;
    
    public ThreadObject(Mode mode, ShareObject shareObject, int size) {
        this.mode = mode;
        this.shareObject = shareObject;
        this.size = size;
    }
 
    @Override
    public void run() {
        for (int i = 0; i < size; i++) {
            if (mode.equals(Mode.GET)) {
                shareObject.getData(i);
            } else if (mode.equals(Mode.SET)) {
                shareObject.setData(i, "data" + i);
            }
        }
    }
 
}
 
// 출력결과
Setted Data : data0
Produced Data : data0
Setted Data : data1
Produced Data : data1
Setted Data : data2
Setted Data : data3
Produced Data : data2
Produced Data : data3
Setted Data : data4
Produced Data : data4
cs




Runnable과 Callable의 차이
executorService에 실행 시킬 스레드로 Runnable과 Callable<T>를 넣을 수 있다.
Runnable은 반환값이 없어 작업 처리 결과를 받지 못하고. 예외 발생시 쓰레드가 정지해버린다.
하지만
Callable<T>는 반환값이 있으며, 예외가 발생해도 정지 하지 않는다. 



1
2
3
4
5
6
7
8
9
10
11
12
13
14
Runnable a = new Runnable() {
 @Override
 public void run() {}
};
 
Callable<T> task = new Callable<T>() {} {
 @Override
 public T call() throws Exception {
   return T;
 }
}
 
Future<T> future = executorService.submit(task);
T result = future.get();
cs



future의 작업 완료에 따른 결과 전달 방법
- poll() : 완료된 작업의 Future를 가져옴. 완료된 작업이 없다면 null 처리
- poll(long timeout, TimeUnit unit) 완료된 작업이 있으면 Future를 바로 가져오고 없으면 기재된 시 
  간만큼 블로킹 한다.
- take() : 완료된 작업이 있으면 가져오고 없으면 가져올 때 까지 블로킹
- submit(callable<V> task) 스레드 풀에 Callblae 작업 요청
- submit(Runnable task, V result) : 스레드 풀에 Runnable 작업 처리 요청


쓰레드풀 (ThreadPool)

 쓰레드 풀은 여러생성된 쓰레드들의 작업 요청을 큐에 넣어놓고 미리 정의해 놓은 스레드 만큼만 작업이 돌고 나머지는 큐에 대기하고 있는다. 이는 빈번하게 발생하는 쓰레드의 작업요청에 따른 성능저하등에 문제 해결을 위해 사용된다.

이 중 대표적으로 사용되는 두개의 Executer에 대해 설명해보자.

쓰레드 풀은 newCachedThreadPool과 new FixedThreadPool이 존재하는데 첫 번째 것은 초기 스레드 개수와 코어 스레드는 0개 이고 스레드 개수보다 작업 개수가 많으면 새로운 개수를 생성하여 처리한다.

ExecuterService executerSErvice = Executors.newCachedThreadPool();

두번째 newFixedThreadPool 메소드로 생성된 쓰레드 풀의 초기 스레드 개수는 0개 코어 스레드 수는 nThreads이다. 이 스레드는 작업 개수가 많아지면 새로운 스레드 풀을 생성하는것은 위와 동일 하지만 스레드가 작업하지 않아도 줄어들지 않으며 코어수 만큼 생성 할 수 있다.


정리하면

newFixedThreadPool(int nThreads)  
- nThreads 개수만큼의 스레드를 항상 유지.일반적인 스레드풀 항상 일정한 스레드 개수를 유지한다. 
- 스레드가 유휴상태이더라도 제거하지 않고 유지한다..

newCachedThreadPool() 
- 스레드 개수에 제한 없음. 짧게 처리되는 태스크에 적합.
- 사용가능한 스레드가 없을때 추가로 스레드 생성. 60초동안 사용안하면 스레드 제거  
- 스레드 개수에 제한이 없이 필요한 경우 계속 스레드 수가 증가한다. 
- 다만 일정 시간(60초)동안 사용하지 않는(idle) 스레드는 종료된다. 
- 필요없는 스레드를 제거하므로 서버 리소스(memory)는 적게 사용하지만, 스레드 생성과 삭제를 반복하므로 작업 부하가 불규칙적인 경우 비효율적이다. 



ExecutorService executorService = Executors.newFixedThreadPool(
 Runtime.getRuntime().availableProcessors();
);



'JAVA > Thread' 카테고리의 다른 글

Java Thread 대표 메서드 소개 및 특징 정리  (0) 2018.05.27
synchronous vs asynchronous  (0) 2016.12.24
멀티 스레드  (0) 2016.12.21
쓰레드 개념정리  (0) 2016.12.21
JAVA 데몬 스레드 소개  (0) 2016.12.21
synchronized 쓰레드 예제 프로그래밍  (0) 2016.12.21

댓글()

쓰레드 개념정리

JAVA/Thread|2016. 12. 21. 23:02

쓰레드의 스케줄링과 관련된 메서드

void interrupt() :  sleep()이나 join()에 의해 일시정지상태인 쓰레드를 실행대기 상태로 만든다.

void join(), join(long millis) : 지정된 시간동안 쓰레드가 실행되도록 한다. / 해당 쓰레드가 종료될때 까지 main 쓰레드는 대기한다.

void resume() : suspend()에 의해 일시정지상태에 있는 쓰레드를 실행대기상태로 만든다.

static void sleep(long miillis) : 지정된 시간 동안 쓰레드를 일시정지 시킨다. 시간이 지난후 다시 실행대기상태가 된다.

void stop() : .쓰레드를 즉시 종료신킨다.

void suspend() : 쓰레드를 일시정지 시킨다.

static void yield() : 실행중에 다른 쓰레드에게 양보하고 실행대기 상태가 된다.



쓰레드의 상태

NEW : 쓰레드가 생성되고 아직 START() 호출되지 않은 상태

RUNNABLE :  실행 중 또는 실행 가능한 상태

BLOCKED : 동기화블럭에 의해서 일시정지된 상태(LOCK이 풀릴 때까지 기다리느상태)

WATING, TIMED_WAITING  : 쓰레드으 ㅣ작업이 종료되지는 않았지만 실행가능하지 않은 일시정지 상태

TERMINATED :  작업이 종료된 상태



응용

1. Thread t1과 t2가 동시에 실행될때 순차적으로 진행하고 싶을 경우

t1.start()

t2.start()


t1.join() // t1이 끝날때까지 기다린다.



2. 현재 실행 중인쓰레드를 일시 정지(sleep)시키거나 양보하고 싶을 경우(yield)의 경우에는

t1.sleep(1000), t1.yield()가 아니라 Thread.sleep(1000), Thread.yield를 사용해야 한다.


왜냐면 이 둘은 항상 현재 실행중인 쓰레드에 대해 작동하기 때문에 t1.sleep(1000)이라 했어도 실제 영향을 받는것은 main쓰레드 이기 때문이다.


'JAVA > Thread' 카테고리의 다른 글

synchronous vs asynchronous  (0) 2016.12.24
멀티 스레드  (0) 2016.12.21
쓰레드 개념정리  (0) 2016.12.21
JAVA 데몬 스레드 소개  (0) 2016.12.21
synchronized 쓰레드 예제 프로그래밍  (0) 2016.12.21
Thread wait(), notify() 소개  (0) 2016.12.21

댓글()

JAVA 데몬 스레드 소개

JAVA/Thread|2016. 12. 21. 23:01


데몬 쓰레드는 다른 일반 쓰레드의 작업을 돕는 보조적인 역할을 수행하는 쓰레드이다.


boolean isDeaemon() 쓰레드가 daemon 쓰레드 인지 확인

void setDaemon(boolean on) 쓰레드를 데몬 쓰레드로 또는 사용자 쓰레드로 변경한다. 



자동저장 쓰레드 프로그래밍



package javas;


import javax.swing.JOptionPane;


public class Thread1 {

static boolean autoSave = false;


public static void main(String args[]) {

Runnable r = new Thread_1();


Thread t1 = new Thread(r);

t1.setDaemon(true);

t1.start();


for (int i = 0; i <= 20; i++) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

System.out.println(i);


if (i == 5)

autoSave = true;

}

}

}


class Thread_1 implements Runnable {

public void run() {

while (true) {

try {

Thread.sleep(5 * 1000);

} catch (InterruptedException e) {

}


if (Thread1.autoSave) {

autosave();

}

}

}


public void autosave() {

System.out.println("작업파일이 자동 저장 되었습니다.");

}

}



※주의

데몬 프로세스는 프로세스가 start되기전에 setDaemon이 되어야 한다

그렇지 않으면 IllegalThreadStateException이 발생한다.


'JAVA > Thread' 카테고리의 다른 글

멀티 스레드  (0) 2016.12.21
쓰레드 개념정리  (0) 2016.12.21
JAVA 데몬 스레드 소개  (0) 2016.12.21
synchronized 쓰레드 예제 프로그래밍  (0) 2016.12.21
Thread wait(), notify() 소개  (0) 2016.12.21
Thread 크리티컬 세션  (0) 2016.12.21

댓글()

synchronized 쓰레드 예제 프로그래밍

JAVA/Thread|2016. 12. 21. 22:59

문제의 프로그램

package javas;


import javax.swing.JOptionPane;


public class Thread1 {

public static void main(String args[]) {

Runnable r = new RunnableEX();

Thread t1 = new Thread(r);

Thread t2 = new Thread(r);

t1.start();

t2.start();

}

}


class Account{

int balance = 1000;

public void withdraw(int money){

if(balance >= money){

try{ Thread.sleep(1000);}

catch (Exception e){

}

balance -= money;

}

}

}


class RunnableEX implements Runnable{

Account acc = new Account();

public void run(){

while(acc.balance>0){

int money = (int) (Math.random()*3+1)*100;

acc.withdraw(money);

System.out.println("balance : " +acc.balance);

}

}

}


balance를 서로 같이사용하기 때문에 그 부분을 동기화 시켜주지 않았기 때문이다.


해결방법

1. withdraw 메서드 부분을 synchronized 해준다.

public synchronized void withdraw(int money){

if(balance >= money){

try{ Thread.sleep(1000);}

catch (Exception e){

}

balance -= money;

}

}


2. withdraw()가 수행되는 동안 객체에 lock을 걸어준다. 

public void withdraw(int money){

synchronized(this){if(balance >= money){

try{ Thread.sleep(1000);}

catch (Exception e){

}

balance -= money;

}

}

}


'JAVA > Thread' 카테고리의 다른 글

쓰레드 개념정리  (0) 2016.12.21
JAVA 데몬 스레드 소개  (0) 2016.12.21
synchronized 쓰레드 예제 프로그래밍  (0) 2016.12.21
Thread wait(), notify() 소개  (0) 2016.12.21
Thread 크리티컬 세션  (0) 2016.12.21
java thread pool 소개  (0) 2016.12.21

댓글()

Thread wait(), notify() 소개

JAVA/Thread|2016. 12. 21. 22:58

문제점

하나의 쓰레드가 객체에  lock 을 걸고 어떤 조건이 만족될 때까지 기다려야 하는 경우, 이 쓰레드를 그대로 놔두면 이 객체를 사용하려는 다른 쓰레드들은  lock이 풀릴 때 까지 같이 기다려야 하는 상황이 발생

 

해결책

 이런 비효율을 개선하기 위해서 wait()와  notify()를 사용한다. 한 쓰레드가 객체에 lock을 걸고 오래 기다리는 대신 wait()을 호출해서 다른 쓰레드에게 제어권을 넘겨주고 대기상태로 기다리다가 다른 쓰레드에 의해서 notify() 가 호출 되면 다시 실행 상태가 되도록 하는 것이다.



wait(), notify(), notifyAll()

- object 클래스에 정의된 메소드이므로 모든 객체에서 호출이 가능하다.

- 동기화 블록(synchronized블록) 내에서만 사용이 가능 하다.

- 보다 효율적인 동기화를 가능하게 한다.

- 쓰레드가 wait()을 호출하면 그 때 까지 자신이 객체에 걸어 놓았던 모든 lock을 풀고, wait()이 호출된 객체의 대기실에서 기다린다. 그러다가 다른 쓰래드에 의해서  그 객체에 대한 notify()를 호출하면 객체의  대기실에서 벗어나서 다시 실행대기상태가 된다. 




예제

class Account {


int balance = 10000;


public synchronized void withdraw(int money){

while(balance<money){

try{

wait();

}catch(InterruptedExcepion e){}

}


//출금을 위해 withdraw()가 호출 되었을 때 잔고가 부족하면 wait()을 호춣해서 쓰레드가 객체의 lock을 풀고 그 객체의 waiting pool(대기실)에 들어가면서 제어권을 다른 쓰레드레게 양보하게 된다.


public syschronized void deposit(int money){

balance += money;

notify();

}

}



// 다른 쓰레드에 의해서 deposit()메소드가 호출되어 잔고가 증가하면서 notify()를 호출하면 객체의 waiting pool에서 기다리고 있던 쓰레드를 깨우게 된다.


}

'JAVA > Thread' 카테고리의 다른 글

JAVA 데몬 스레드 소개  (0) 2016.12.21
synchronized 쓰레드 예제 프로그래밍  (0) 2016.12.21
Thread wait(), notify() 소개  (0) 2016.12.21
Thread 크리티컬 세션  (0) 2016.12.21
java thread pool 소개  (0) 2016.12.21
JAVA 스레드 스케줄링  (0) 2016.12.21

태그 : java, notify, Thread, Wait

댓글()