JUC 프로그래밍: Lock과 Condition

  1. JUC란? JUC는 Java에서 제공하는 동시성 도구 패키지로, java.util.concurrent라는 클래스 라이브러리를 의미합니다. 이 패키지는 고성능 멀티스레드 애플리케이션을 개발하기 위해 다양한 클래스와 인터페이스를 제공합니다. 주요 기능은 CPU 자원을 효율적으로 활용하는 것입니다. 비고:
  • 쓰레드는 start() 메서드로 시작하지 않고, 사실상 내부의 native 메서드인 start0()를 통해 시작됩니다.
  • wait와 sleep의 차이점은 다음과 같습니다:
  • wait는 Object 클래스의 메서드이며, sleep는 Thread 클래스의 메서드입니다.
  • wait는琐锁를 해제하지만, sleep는琐锁를 해제하지 않습니다.
  • wait는 synchronized 키워드와 함께 사용해야 하지만, sleep는 그렇지 않습니다.
  • wait는 다른 스레드에 의해 깨워지지만, sleep는 깨워지 않습니다.
  1. Lock Lock은 인터페이스로 세 가지 구현체가 있습니다:
  • ReentrantLock: 기본적으로不公平 lock이며, 편의를 위해 공평锁로 설정할 수 있습니다.
  • ReentrantReadWriteLock.ReadLock
  • ReentrantReadWriteLock.WriteLock 공유 자원에 대한 독점을 보장합니다. 즉, 한번에 하나의 스레드만이琐锁를 획득하고, 공유 자원에 접근할 수 있습니다. synchronized와의 차이점:
  • Lock은琐锁 상태를 확인할 수 있지만, synchronized는 확인할 수 없습니다.
  • Lock은 수동으로 해제해야 하지만, synchronized는 자동으로 해제됩니다.
  • 둘 다는 재입가능琐锁(reentrant lock)를 지원하며, 기본적으로는不公平 lock입니다.
  • synchronized는 스레드가琐锁를 획득하면 다른 스레드는 대기해야 하지만, Lock의 경우 tryLock()을 통해琐锁를 시도할 수 있습니다.
  1. 생산자-소비자 문제 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();
        }
    }
}

태그: JUC Lock Condition 멀티스레드 동시성 프로그래밍

6월 15일 02:46에 게시됨