Thread 동기화는 여러 스레드가 공유 자원에 동시에 접근할 때 데이터 불일치 문제가 발생하지 않도록 관리하는 기술이다.
주요 동기화 방법으로는 synchronized가 있다.
class Counter {
private int count = 0;
// 동기화하지 않은 메서드 (문제 발생 가능)
public void increment() {
count++; // 실제로는 읽기 → 증가 → 쓰기 과정
}
public int getCount() {
return count;
}
}
public class SynchronizedCounter {
private int count = 0;
// 동기화된 메서드
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedCounter counter = new SynchronizedCounter();
// 10개 스레드가 각각 1000번씩 증가
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.increment();
}
});
threads[i].start();
}
// 모든 스레드 종료 대기
for (Thread t : threads) {
t.join();
}
System.out.println("최종 카운트: " + counter.getCount());
}
}
public class BlockSynchronization {
private final Object lock = new Object();
private int count = 0;
public void increment() {
// 필요한 부분만 동기화
synchronized (lock) {
count++;
}
}
}
Thread 간 통신은 여러 스레드가 협력하거나 데이터를 주고받기 위해 정보를 교환하는 기술을 의미한다.
주요 메서드는 wait, notify, notifyAll이며 생산자-소비자 패턴에서 많이 사용된다.
import java.util.ArrayList;
import java.util.List;
public class ProducerConsumer {
private final List<Integer> buffer = new ArrayList<>();
private final int MAX_SIZE = 5;
private final Object lock = new Object();
// 생산자
public void produce(int value) throws InterruptedException {
synchronized (lock) {
while (buffer.size() >= MAX_SIZE) {
System.out.println("버퍼가 가득 참, 대기 중...");
lock.wait();
}
buffer.add(value);
System.out.println("생산: " + value + ", 버퍼 크기: " + buffer.size());
lock.notifyAll();
}
}
// 소비자
public int consume() throws InterruptedException {
synchronized (lock) {
while (buffer.isEmpty()) {
System.out.println("버퍼가 비어있음, 대기 중...");
lock.wait();
}
int value = buffer.remove(0);
System.out.println("소비: " + value + ", 버퍼 크기: " + buffer.size());
lock.notifyAll();
return value;
}
}
}
Thread는 다음과 같은 상태를 가진다.
Thread 상태 관련 메서드로는 join, interrupt, 우선순위 설정 등이 있다.
join은 다른 스레드의 종료를 기다리는 메서드이다.
public class JoinExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
System.out.println("작업 시작");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("작업 완료");
});
worker.start();
System.out.println("작업자 스레드 종료 대기...");
worker.join();
System.out.println("모든 작업 완료!");
}
}
interrupt는 스레드를 안전하게 중단시키기 위한 요청이다.
public class InterruptExample {
public static void main(String[] args) throws InterruptedException {
Thread worker = new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
System.out.println("작업 진행: " + i + "/10");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
System.out.println("작업이 중단되었습니다!");
return;
}
System.out.println("작업 완료");
});
worker.start();
Thread.sleep(3500);
worker.interrupt();
}
}
우선순위는 스레드가 CPU 자원을 얼마나 자주 할당받는지에 영향을 주는 값이다.
1~10 사이의 정수로 지정하며 기본값은 5이다.
public class PriorityExample {
public static void main(String[] args) {
Thread highPriority = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("높은 우선순위: " + i);
}
});
Thread lowPriority = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println("낮은 우선순위: " + i);
}
});
highPriority.setPriority(Thread.MAX_PRIORITY);
lowPriority.setPriority(Thread.MIN_PRIORITY);
highPriority.start();
lowPriority.start();
}
}
데드락은 두 개 이상의 스레드가 서로가 가진 자원을 기다리면서 무한정 대기하는 상태이다.

public class DeadlockExample {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + ": lock1 획득");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + ": lock2 획득");
}
}
}
public void method2() {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + ": lock2 획득");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + ": lock1 획득");
}
}
}
}
public class DeadlockSolution {
private final Object lock1 = new Object();
private final Object lock2 = new Object();
public void method1() {
synchronized (lock1) {
synchronized (lock2) {
// 작업 수행
}
}
}
public void method2() {
synchronized (lock1) {
synchronized (lock2) {
// 작업 수행
}
}
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
public class TimeoutSolution {
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();
public void performTask() throws InterruptedException {
while (true) {
if (lock1.tryLock(1, TimeUnit.SECONDS)) {
try {
if (lock2.tryLock(1, TimeUnit.SECONDS)) {
try {
// 작업 수행
break;
} finally {
lock2.unlock();
}
}
} finally {
lock1.unlock();
}
}
Thread.sleep(100);
}
}
}
람다 표현식은 익명 함수를 간단하게 표현하는 문법이다.
기본 문법은 (매개변수) -> { 실행문 } 형태이다.
public class PlusFunctionExam {
public static void main(String[] args) {
PlusFunction plusObj = (int i, int j) -> {
return i + j;
};
int value = plusObj.plus(100, 200);
System.out.println(value);
}
}
@FunctionalInterface
interface PlusFunction {
int plus(int i, int j);
}
@FunctionalInterface
interface Greeting {
void sayHello();
}
@FunctionalInterface
interface StringProcessor {
String process(String str);
}
@FunctionalInterface
interface Calculator {
double calculate(double a, double b);
}
public class LambdaExamples {
public static void main(String[] args) {
Greeting greeting = () -> System.out.println("안녕하세요!");
greeting.sayHello();
StringProcessor upperCase = str -> str.toUpperCase();
System.out.println(upperCase.process("hello"));
Calculator multiply = (a, b) -> a * b;
System.out.println(multiply.calculate(5.0, 3.0));
Calculator complexCalc = (a, b) -> {
double result = a + b;
System.out.println("계산 중: " + a + " + " + b);
return result;
};
System.out.println(complexCalc.calculate(10.0, 20.0));
}
}
import java.util.function.*;
public class StandardFunctionalInterfaces {
public static void main(String[] args) {
Predicate<Integer> isEven = num -> num % 2 == 0;
System.out.println(isEven.test(4));
System.out.println(isEven.test(5));
Function<String, Integer> stringLength = str -> str.length();
System.out.println(stringLength.apply("Hello"));
Consumer<String> printer = str -> System.out.println("출력: " + str);
printer.accept("람다식 테스트");
Supplier<Double> randomSupplier = () -> Math.random();
System.out.println("랜덤 값: " + randomSupplier.get());
BinaryOperator<Integer> add = (a, b) -> a + b;
System.out.println(add.apply(10, 20));
}
}
오늘은 빠르게 지나가서 쓰레드나 람다에서 궁금한 점들을 찾아보고 정리하느라 시간이 좀 걸린 것 같다.
쓰레드는 기능이 생각보다 많아서 전부 이해하는 데 시간이 걸릴 것 같다.
람다식은 자바스크립트에서 잠깐 사용해봤는데, 자바에서 사용해보니까 아직은 어색한 것 같다.
그래도 자주 사용해서 익숙해지면 엄청 편하게 사용할 수 있을 것 같다.
내일은 스트림을 공부하는데 재미있을 것 같다.