쓰레드 풀 모델
쓰레드의 생성과 소멸은 리소스 소모가 많은 작업이다.
그런데 쓰레드 풀은 쓰레드의 재활용을 위한 모델이다.
class ExecutorsDemo {
public static void main(String[] args) {
Runnable task = () -> { // 쓰레드에게 시킬 작업 - Runnable 구현한 인스턴스 생성
int n1 = 10;
int n2 = 20;
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (n1 + n2));
};
ExecutorService exr = Executors.newSingleThreadExecutor();
exr.submit(task); // 쓰레드 풀에 작업을 전달
System.out.println("End " + Thread.currentThread().getName());
exr.shutdown(); // 쓰레드 풀과 그 안에 있는 쓰레드의 소멸
// 예전 모델에서는 쓰레드가 종료되면 자동으로 소멸됬는데, 쓰레드 풀은 계속 대기중임.
// 쓰레드 소멸했다고 해서 바로 소멸되는게 아니라 할당된 쓰레드가 모두 종료되면 그 때 소멸됨!
}
• newSingleThreadExecutor
풀 안에 하나의 쓰레드만 생성하고 유지한다.
• newFixedThreadPool(3) - 예
풀 안에 인자로 전달된 수의 쓰레드를 생성하고 유지한다.
• newCachedThreadPool - 실험적이여서 확실히 좋다고는 말못함...
public static void main(String[] args) {
Runnable task1 = () -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (5 + 7));
};
Runnable task2 = () -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (7 - 5));
};
ExecutorService exr = Executors.newFixedThreadPool(2);
exr.submit(task1);
exr.submit(task2);
exr.submit(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + ": " + (5 * 7));
});
exr.shutdown();
public interface Runnable {
void run();
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<Integer> task = () -> {
int sum = 0;
for(int i = 0; i < 10; i++)
sum += i;
return sum;
};
ExecutorService exr = Executors.newSingleThreadExecutor();
Future<Integer> fur = exr.submit(task);
Integer r = fur.get(); // 쓰레드의 반환 값 획득 - 쓰레드가 완료된후 반환된 값이 저장됨!
System.out.println("result: " + r);
exr.shutdown();
}1~20까지 더하고 싶으면 1~10까지 더하는 쓰레드와 11~20까지 더하는 쓰레드의 반환값을 get해서 더해주면 동시에 실행가능!
synchronized를 대신하는 ReentrantLock
class MyClass {
ReentrantLock criticObj = new ReentrantLock(); // 자물쇠
void myMethod(int arg) {
criticObj.lock() ; // 문을 잠근다.
.... // 한 쓰레드에 의해서만 실행되는 영역
criticObj.unlock(); // 문을 연다.
}
}
class MyClass {
ReentrantLock criticObj = new ReentrantLock();
void myMethod(int arg) {
criticObj.lock() ; // 문을 잠근다.
try {
.... // 한 쓰레드에 의해서만 실행되는 영역
} finally {
criticObj.unlock(); // 문을 연다.
} unlock의 호출이 생략되는 것을 막기 위해 try-finally 권고됨!
}
}
컬렉션 인스턴스 동기화
public static <T> Set<T> synchronizedSet(Set<T> s)
public static <T> List<T> synchronizedList(List<T> list)
public static <K, V> Map<K, V> synchronizedMap(Map<K, V> m)
public static <T> Collection<T> synchronizedCollection(Collection<T> c)
List<String> lst = Collections.synchronizedList(new ArrayList<String>());
컬렉션 인스턴스 동기화의 예
Runnable task = () -> {
synchronized(lst) {
ListIterator<Integer> itr = lst.listIterator();
while(itr.hasNext())
itr.set(itr.next() + 1);
}
};
class SyncArrayList {
public static List<Integer> lst =
Collections.synchronizedList(new ArrayList<Integer>());
public static void main(String[] args) throws InterruptedException {
for(int i = 0; i < 16; i++)
lst.add(i);
System.out.println(lst);
Runnable task = () -> {
ListIterator<Integer> itr = lst.listIterator();
while(itr.hasNext())
itr.set(itr.next() + 1);
};
ExecutorService exr = Executors.newFixedThreadPool(3);
exr.submit(task);
exr.submit(task);
exr.submit(task);
exr.shutdown();
exr.awaitTermination(100, TimeUnit.SECONDS); // 쓰레드가 종료될때까지 최대 100초 기다리겠다는 뜻임.
System.out.println(lst);
}
}
ArrayList의 원소들을 하나씩 꺼내서 1씩 증가키는 쓰레드를 3개를 실행했는데 제대로 동작하지 않았다.
이유는, ArrayList 인스턴스에는 동기화를 해줘서 ArrayList를 통해 접근하면 동기화가 되겠지만
쓰레드는 반복자를 통해 접근하고 있다.
반복자도 하나의 인스턴스로 반복자를 통한 인스턴스는 동기화 영향을 받지 않는다.
따라서 코드를 위의 빨간색 네모칸처럼 고쳐줘야 한다!!
'# 02 > Java' 카테고리의 다른 글
[Java] 자바 메모리 구조 (0) | 2020.10.25 |
---|---|
[Java] RxJava 세미나 (0) | 2019.12.20 |
[윤성우 열혈자바] 34-2. 쓰레드의 동기화 (0) | 2019.10.28 |
[윤성우 열혈자바] 34-1. 쓰레드의 이해와 쓰레드의 생성 (0) | 2019.10.28 |
[윤성우 열혈자바] 33-3. NIO 기반의 입출력 (0) | 2019.10.28 |