• 제네릭 가변인수 배열에 값을 저장하는 것은 안전하지 않다.
**// Anti Pattern**
// 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 않다. (191-192쪽)
public class Dangerous {
// 코드 32-1 제네릭과 varargs를 혼용하면 타입 안전성이 깨진다! (191-192쪽)
static void dangerous(List<String>... stringLists) {
List<Integer> intList = List.of(42);
Object[] objects = stringLists;
objects[0] = intList; // 힙 오염 발생
String s = stringLists[0].get(0); // ClassCastException
}
public static void main(String[] args) {
dangerous(List.of("There be dragons!"));
}
}
• 힙 오염이 발생할 수 있다. (컴파일 경고 발생)
• 자바7에 추가된 @SafeVarargs 애노테이션을 사용할 수 있다.
// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽)
**// generic 가변 인자
// Good Pattern**
public class FlattenWithVarargs {
**@SafeVarargs**
static <T> List<T> flatten(List<? extends T>... lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
public static void main(String[] args) {
List<Integer> flatList = flatten(
List.of(1, 2), List.of(3, 4, 5), List.of(6,7));
System.out.println(flatList);
}
}
• 제네릭 가변인수 배열의 참조를 밖으로 노출하면 힙 오염을 전달할 수 있다.
넘겨진 가변 인자를 리턴 하지 말자.
해당 코드는 가장 추상화된 Object 로 타입 캐스팅 되기 때문에 위험하다. → RuntimeError
// 미묘한 힙 오염 발생 (193-194쪽)
public class PickTwo {
// 코드 32-2 자신의 제네릭 매개변수 배열의 참조를 노출한다. - 안전하지 않다! (193쪽)
**static <T> T[] toArray(T... args) {
return args;
}**
static <T> T[] pickTwo(T a, T b, T c) {
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError(); // 도달할 수 없다.
}
public static void main(String[] args) { // (194쪽)
String[] attributes = pickTwo("좋은", "빠른", "저렴한");
System.out.println(Arrays.toString(attributes));
}
}
• 예외적으로, @SafeVarargs를 사용한 메서드에 넘기는 것은 안전하다
// 코드 32-3 제네릭 varargs 매개변수를 안전하게 사용하는 메서드 (195쪽)
public class FlattenWithVarargs {
@SafeVarargs
static <T> List<T> flatten(List<? extends T>... lists) {
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
public static void main(String[] args) {
List<Integer> flatList = flatten(
List.of(1, 2), List.of(3, 4, 5), List.of(6,7));
System.out.println(flatList);
}
}
• 예외적으로, 배열의 내용의 일부 함수를 호출하는 일반 메서드로 넘기는 것은 안전하다.
• 아이템 28의 조언에 따라 가변인수를 List로 바꾼다면
• 배열없이 제네릭만 사용하므로 컴파일러가 타입 안정성을 보장할 수 있다.
• @SafeVarargs 애너테이션을 사용할 필요가 없다.
• 실수로 안전하다고 판단할 걱정도 없다
// 배열 대신 List를 이용해 안전하게 바꿘 PickTwo (196쪽)
public class SafePickTwo {
**static <T> List<T> pickTwo(T a, T b, T c) {**
switch(ThreadLocalRandom.current().nextInt(3)) {
case 0: return List.of(a, b);
case 1: return List.of(a, c);
case 2: return List.of(b, c);
}
throw new AssertionError();
}
public static void main(String[] args) {
List<String> attributes = pickTwo("좋은", "빠른", "저렴한");
System.out.println(attributes);
}
}
===
**// 코드 32-4 제네릭 varargs 매개변수를 List로 대체한 예** - 타입 안전하다. (195-196쪽)
public class FlattenWithList {
**static <T> List<T> flatten(List<List<? extends T>> lists) {**
List<T> result = new ArrayList<>();
for (List<? extends T> list : lists)
result.addAll(list);
return result;
}
public static void main(String[] args) {
List<Integer> flatList = flatten(List.of(
List.of(1, 2), List.of(3, 4, 5), List.of(6,7)));
System.out.println(flatList);
}
}
쓰레드 지역 변수
• 모든 멤버 변수는 기본적으로 여러 쓰레드에서 공유해서 쓰일 수 있다. 이때 쓰레드 안 전성과 관련된 여러 문제가 발생할 수 있다.
• 경합 또는 경쟁조건 (Race-Condition)
• 교착상태 (deadlock)
• Livelock
• 쓰레드 지역 변수를 사용하면 동기화를 하지 않아도 한 쓰레드에서만 접근 가능한 값이 기 때문에 안전하게 사용할 수 있다.
• 한 쓰레드 내에서 공유하는 데이터로, 메서드 매개변수에 매번 전달하지 않고 전역 변 수처럼 사용할 수 있다
public class ThreadLocalExample implements Runnable {
// SimpleDateFormat is not thread-safe, so give one to each thread
private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyyMMdd HHmm"));
// private SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMdd HHmm");
public static void main(String[] args) throws InterruptedException {
ThreadLocalExample obj = new ThreadLocalExample();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(obj, "" + i);
Thread.sleep(new Random().nextInt(1000));
t.start();
}
}
@Override
public void run() {
System.out.println("Thread Name= " + Thread.currentThread().getName() + " default Formatter = " + formatter.get().toPattern());
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//formatter pattern is changed here by thread, but it won't reflect to other threads
formatter.set(new SimpleDateFormat());
System.out.println("Thread Name= " + Thread.currentThread().getName() + " formatter = " + formatter.get().toPattern());
}
}
스레드 지역 랜덤값 생성기
public class RandomExample {
public static void main(String[] args) {
Random random = new Random();
System.out.println(random.nextInt(10));
ThreadLocalRandom threadLocalRandom = ThreadLocalRandom.current();
System.out.println(threadLocalRandom.nextInt(10));
}
private int value;
// Atomic Class Internal main method
public synchronized int compareAndSwap(int expectedValue, int newValue)
{
int readValue = value;
if (readValue == expectedValue)
value = newValue;
return readValue;
}
}