멀티 스레드 환경에서, 여러 스레드가 동시에 접근해도 동시성 문제나, 특이 사항이 발생하지 않는. 즉, 안전한 경우를 Thread Safe 하다 라고 한다.
public class ArrayListMain {
public static void main(String[] args) {
List<Integer> sharedList = new ArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
// ArrayList에 값 추가
sharedList.add(i);
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(task);
}
for (Thread thread : threads) {
thread.start();
}
// 모든 스레드가 종료될 때까지 대기
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected size: " + (10 * 1000));
System.out.println("Actual size: " + sharedList.size());
}
}
Expected size: 10000
Actual size: 7394
syncronized
키워드를 통해 lock을 획득 후, 접근 하도록 변경 해 보자.package collection;
import java.util.ArrayList;
import java.util.List;
public class ArrayListProxy {
private final List<Integer> lists;
public ArrayListProxy(List<Integer> lists) {
this.lists = lists;
}
public synchronized boolean add(Integer data){
return lists.add(data);
}
}
package collection;
import java.util.ArrayList;
import java.util.List;
public class ArrayListMainV2 {
public static void main(String[] args) {
List<Integer> sharedList = new ArrayList<>();
ArrayListProxy arrayListProxy = new ArrayListProxy(sharedList);
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
// ArrayList에 값 추가
arrayListProxy.add(i);
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(task);
}
for (Thread thread : threads) {
thread.start();
}
// 모든 스레드가 종료될 때까지 대기
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected size: " + (10 * 1000));
System.out.println("Actual size: " + sharedList.size());
}
}
Expected size: 10000
Actual size: 10000
10000
이 나온 모습을 확인 할 수 있다.syncronized
동기화를 사용한 메서드를 만들어야 한다.syncronzied
동기화를 사용하면, 모든 ArrayList에 접근하는 코드는 직렬적으로 접근하게 되어 성능이 나빠진다.동시성 컬렉션
을 제공한다.syncornized
프록시 객체를 생성해주는 키워드
가 또 존재한다.Collections.synchronizedList(new ArrayList<>());
처럼 사용하면 Thread Safe 한 동기화 프록시 객체를 생성해 준다.package collection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class ArrayListMainV2 {
public static void main(String[] args) {
// List<Integer> sharedList = new ArrayList<>();
// ArrayListProxy arrayListProxy = new ArrayListProxy(sharedList);
List<Integer> arrayListProxy = Collections.synchronizedList(new ArrayList<>());
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
// ArrayList에 값 추가
arrayListProxy.add(i);
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(task);
}
for (Thread thread : threads) {
thread.start();
}
// 모든 스레드가 종료될 때까지 대기
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected size: " + (10 * 1000));
System.out.println("Actual size: " + arrayListProxy.size());
}
}
ArrayList
의 동시성 컬렉션인 CopyOnWriteArrayList
를 사용해 보자.package collection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ArrayListMainV3 {
public static void main(String[] args) {
List<Integer> sharedList = new CopyOnWriteArrayList<>();
Runnable task = () -> {
for (int i = 0; i < 1000; i++) {
// ArrayList에 값 추가
sharedList.add(i);
}
};
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(task);
}
for (Thread thread : threads) {
thread.start();
}
// 모든 스레드가 종료될 때까지 대기
for (Thread thread : threads) {
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Expected size: " + (10 * 1000));
System.out.println("Actual size: " + sharedList.size());
}
}
Expected size: 10000
Actual size: 10000
10000
결과가 나왔다.List
계열CopyOnWriteArrayList
: ArrayList
의 대안Set
계열CopyOnWriteArraySet
: HashSet
의 대안ConcurrentSkipListSet
TreeSet
의 대안(정렬된 순서 유지, Comparator
사용 가능)Map
계열ConcurrentHashMap
: HashMap
의 대안ConcurrentSkipListMap
: TreeMap
의 대안(정렬된 순서 유지, Comparator
사용 가능)Queue
계열ConcurrentLinkedQueue
: 동시성 큐, 비 차단(non-blocking) 큐이다.Deque
계열ConcurrentLinkedDeque
: 동시성 데크, 비 차단(non-blocking) 큐이다.또한, 스레드는 차단하는 블로킹 큐 또한 존재 한다.
BlockingQueue
ArrayBlockingQueue
LinkedBlockingQueue
PriorityBlockingQueue
SynchronousQueue
DelayQueue