ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Lock, Conditon
    Java/Java 문법 종합반 강의 정리 2024. 1. 3. 20:37

    !- 코드 예시는 가장 마지막에 -!

     

    Lock

    - synchronized 블럭으로 동기화하면 자동적으로 Lock이 걸리고 풀리지만 같은 메서드 내에서만 Lock을 걸 수 있다는

     제약 이 있다.

    - 이런 제약을 해결하기 위해 Lock 클래스를 사용한다.

     

    ReentrantLock

    - 재진입 가능한 Lock, 가장 일반적인 베타 Lock

    - 특정 조건에서 Lock을 풀고, 나중에 다시 Lock을 얻어 임계영역으로 진입이 가능하다.

    public class MyClass {
        private Object lock1 = new Object();
        private Object lock2 = new Object();
        
        public void methodA() {
            synchronized (lock1) {
                methodB();
            }
        }
        
        public void methodB() {
            synchronized (lock2) {
                // do something
                methodA();
            }
        }
    }

    - methodA 는 lock1을 가지고, methodB는 lock2를 가진다.

    - methodB 에서 methodA를 호출하고 있으므로, methodB에서 lock2를 가진 상태에서 methodA를 호출하면 lock1을

     가지려고 할 것이다.

    - 그러나 이때, methodA에서 이미 lock1을 가지고 있으므로, lock2를 기다리는 상태가 되어 데드락이 발생할

     가능성이 있다.

    - 하지만 ReentrantLock을 사용하면, 같은 쓰레드가 이미 락을 가지고 있더라도 락을 유지하며 계속 실행할 수 있기

     때문에 데드락이 발생하지 않는다.

    - 즉, ReentrantLock을 사용하면 코드의 유연성을 높일 수 있다.

     

    ReentrantReadWriteLock

    - 읽기를 위한 Lock과 쓰기를 위한 Lock을 따로 제공한다

    - 읽기에는 공유적이고, 쓰기에는 베타적인 Lock이다.

    - 읽기 Lock이 걸려있으면 다른 쓰레드들도 읽기 Lock을 중복으로 걸고 읽기를 수행할 수 있다.(read-only)

    - 읽기 Lock이 걸려있는 상태에서 쓰기 Lock을 거는 것은 허용되지 않는다.(데이터 변경 방지)

     

    StampedLock

    - ReentrantReadWriteLock에 낙관적인 Lock의 기능을 추가했다.

     - 낙관적인 Lock : 데이터를 변경하기 전에 락을 걸지 않는 것을 말한다. 낙관적인 락은 데이터 변경을 할 때 충돌이

     일어날 가능성이 적은 상황에서 사용한다.

     - 낙관적인 락을 사용하면 읽기와 쓰기 작업 모두가 빠르게 처리된다. 쓰기 작업이 발생했을 때, 데이터가 이미 변경된 경   우 다시 읽기 작업을 수행하여 새로운 값을 읽어들이고, 변경 작업을 다시 수행한다. 이러한 방식으로 쓰기 작업이 빈번하   지 않은 경우에는 낙관적인 락을 사용하여 더 빠른 처리가 가능하다.

    - 낙관적인 읽기 Lock은 쓰기 Lock에 의해 바로 해제 가능하다.

    - 무조건 읽기 Lock을 걸지 않고, 쓰기와 읽기가 충돌할 때만 쓰기 후 읽기 Lock을 건다.

     

     

    Condition

    wait() & notify()의 문제점인 waiting pool 내 쓰레드를 구분하지 못한다는 것을 해결한 것이 Condition 이다.

     

    추가 설명 : wait() 와 notify() 는 대기중인 스레드를 구분하지 못한다.

    Condition은 waiting pool 내의 쓰레드를 분리하여 특정 조건이 만족될 때만 깨우도록 할 수 있으며,

    ReetrantLock 클래스와 함께 사용된다. 따라서 Condition을 사용하면 wait(), notify()의 문제점을 보완할 수 있다.

     

    - wait() & notify() 대신 Condition의 await() & signal() 을 사용한다.

    - 아래 코드와 같이 Condition 을 만들어서 대기줄(waiting pool)을 사용할 수 있다.

    public class Main {
        public static final int MAX_TASK = 5;
    
        private ReentrantLock lock = new ReentrantLock();
    
        // lock으로 condition 생성
        private Condition condition1 = lock.newCondition();
        private Condition condition2 = lock.newCondition();
    
        private ArrayList<String> tasks = new ArrayList<>();
    
        // 작업 메서드
        public void addMethod(String task){
            lock.lock(); // 임계영역 시작
    
            try{
                while(tasks.size() >= MAX_TASK){
                    String name = Thread.currentThread().getName();
                    System.out.println(name + " is waiting.");
                    try{
                        condition1.await(); // wait() : condition1 쓰레드를 기다리게 한다.
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
    
                    }
                }
    
                tasks.add(task);
                condition2.signal();
                System.out.println("Tasks:" + tasks.toString());
            } finally {
                lock.unlock();
            }
        }
    }

    https://tangpoo.tistory.com/137

     

    wait(), notify()

    침범을 막은 코드(synchronized)를 수행하다가 작업을 더 이상 진행할 상황이 아니면, wait()을 호출하여 쓰레드가 Lock을 반납하고 기다리게 할 수 있다. - 그럼 다른 쓰레드가 락을 얻어 해당 객체에

    tangpoo.tistory.com

     

    위 글에서 겪었던 waiting poll 안의 쓰레드를 구분하지 못하여 발생했던 문제들을 개선할 수 있었다.

    여전히 점원과 고객1, 2 가 모두 멈추는 상황이 나올 수 있어서 적절치 않은 예시라는 생각이 들기는 한다...

    그래도 wait(), notify() 의 개선점을 위주로 초점을 맞춰 학습하도록 하자.

Designed by Tistory.