- JUC란? JUC는 Java에서 제공하는 동시성 도구 패키지로, java.util.concurrent라는 클래스 라이브러리를 의미합니다. 이 패키지는 고성능 멀티스레드 애플리케이션을 개발하기 위해 다양한 클래스와 인터페이스를 제공합니다. 주요 기능은 CPU 자원을 효율적으로 활용하는 것입니다. 비고:
- 쓰레드는 start() 메서드로 시작하지 않고, 사실상 내부의 native 메서드인 start0()를 통해 시작됩니다.
- wait와 sleep의 차이점은 다음과 같습니다:
- wait는 Object 클래스의 메서드이며, sleep는 Thread 클래스의 메서드입니다.
- wait는琐锁를 해제하지만, sleep는琐锁를 해제하지 않습니다.
- wait는 synchronized 키워드와 함께 사용해야 하지만, sleep는 그렇지 않습니다.
- wait는 다른 스레드에 의해 깨워지지만, sleep는 깨워지 않습니다.
- Lock Lock은 인터페이스로 세 가지 구현체가 있습니다:
- ReentrantLock: 기본적으로不公平 lock이며, 편의를 위해 공평锁로 설정할 수 있습니다.
- ReentrantReadWriteLock.ReadLock
- ReentrantReadWriteLock.WriteLock 공유 자원에 대한 독점을 보장합니다. 즉, 한번에 하나의 스레드만이琐锁를 획득하고, 공유 자원에 접근할 수 있습니다. synchronized와의 차이점:
- Lock은琐锁 상태를 확인할 수 있지만, synchronized는 확인할 수 없습니다.
- Lock은 수동으로 해제해야 하지만, synchronized는 자동으로 해제됩니다.
- 둘 다는 재입가능琐锁(reentrant lock)를 지원하며, 기본적으로는不公平 lock입니다.
- synchronized는 스레드가琐锁를 획득하면 다른 스레드는 대기해야 하지만, Lock의 경우 tryLock()을 통해琐锁를 시도할 수 있습니다.
- 생산자-소비자 문제 synchronized로 구현한 예제:
코드 보기
public class ProducerConsumer {
public static void main(String[] args) {
Number number = new Number();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
number.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "a").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
number.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "b").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
number.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "c").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
try {
number.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "d").start();
}
}
class Number {
private int count = 0;
public synchronized void increment() throws InterruptedException {
while (count != 0) {
this.wait();
}
count++;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "->" + count);
}
public synchronized void decrement() throws InterruptedException {
while (count == 0) {
this.wait();
}
count--;
this.notifyAll();
System.out.println(Thread.currentThread().getName() + "->" + count);
}
}
비고: 동기화 코드 블록 내에서 if 대신 while 문을 사용해야 합니다.这是因为 if 조건을 사용하면 false wake-up이 발생할 수 있습니다.
Lock을 사용한 JUC 구현 예제:
코드 보기
public class ProducerConsumer2 {
public static void main(String[] args) {
Number2 number2 = new Number2();
new Thread(() -> { for (int i = 0; i < 5; i++) { number2.increment(); } }, "a").start();
new Thread(() -> { for (int i = 0; i < 5; i++) { number2.decrement(); } }, "b").start();
new Thread(() -> { for (int i = 0; i < 5; i++) { number2.increment(); } }, "c").start();
new Thread(() -> { for (int i = 0; i < 5; i++) { number2.decrement(); } }, "d").start();
}
}
class Number2 {
private int count = 0;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void increment() {
lock.lock();
try {
while (count != 0) {
condition.await();
}
count++;
condition.signalAll();
System.out.println(Thread.currentThread().getName() + "->" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
while (count == 0) {
condition.await();
}
count--;
condition.signalAll();
System.out.println(Thread.currentThread().getName() + "->" + count);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition의 강점: 특정 스레드를 정확하게 깨울 수 있습니다.
멀티 단계 생산자-소비자 예제:
코드 보기
public class MultiStage {
public static void main(String[] args) {
StageTest test = new StageTest();
new Thread(() -> { for (int i = 0; i < 5; i++) { test.stageA(); } }, "a").start();
new Thread(() -> { for (int i = 0; i < 5; i++) { test.stageB(); } }, "b").start();
new Thread(() -> { for (int i = 0; i < 5; i++) { test.stageC(); } }, "c").start();
}
}
class StageTest {
private int state = 1;
private final Lock lock = new ReentrantLock();
private final Condition conditionA = lock.newCondition();
private final Condition conditionB = lock.newCondition();
private final Condition conditionC = lock.newCondition();
public void stageA() {
lock.lock();
try {
while (state != 1) {
conditionA.await();
}
state = 2;
conditionB.signal();
System.out.println(Thread.currentThread().getName() + "-> stage A");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void stageB() {
lock.lock();
try {
while (state != 2) {
conditionB.await();
}
state = 3;
conditionC.signal();
System.out.println(Thread.currentThread().getName() + "-> stage B");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void stageC() {
lock.lock();
try {
while (state != 3) {
conditionC.await();
}
state = 1;
conditionA.signal();
System.out.println(Thread.currentThread().getName() + "-> stage C");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}