처음 입사 후 담당했던 프로젝트의 경우 단일 스레드로 동작하며,
동작 필요에 따라 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' 카테고리의 다른 글
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 |