|
| 1 | +# 명시적인 락 |
| 2 | + |
| 3 | +- **13.1 Lock과 ReentrantLock** |
| 4 | + - 13.1.1 폴링과 시간 제한이 있는 락 확보 방법 |
| 5 | + - 13.1.2 인터럽트 걸 수 있는 락 확보 방법 |
| 6 | + - 13.1.3 블록을 벗어나는 구조의 락 |
| 7 | + |
| 8 | +- **13.2 성능에 대한 고려 사항** |
| 9 | +- **13.3 공정성** |
| 10 | +- **13.4 synchronized 또는 ReentrantLock 선택** |
| 11 | +- **13.5 읽기-쓰기 락** |
| 12 | +- **분산락** |
| 13 | + |
| 14 | +--- |
| 15 | + |
| 16 | +# 13.1 Lock과 ReentrantLock |
| 17 | + |
| 18 | +**ReentrantLock 은 Synchronized 에 비하여, 락을 제대로 확보하기 어려운 시점에 훨씬 능동적으로 대처할 수 있다. (p406)** |
| 19 | + |
| 20 | +- Timeout 을 지정하여 유연하게 대처 가능 |
| 21 | + |
| 22 | +``` java |
| 23 | +public boolean tryLock(long timeout, TimeUnit unit) |
| 24 | + |
| 25 | +LockSupport.parkNanos(this, nanos); |
| 26 | +``` |
| 27 | + |
| 28 | +하지만 정확한 시간초 뒤에 깨우는게 아니다. 약간의 오차 발생 가능 그래서 루프를 돌면서 확인해야 함 |
| 29 | + |
| 30 | +--- |
| 31 | + |
| 32 | +**왜 명시적인 락이 필요할까?** |
| 33 | + |
| 34 | +- 대기 상태에 들어가지 않으면서 락을 확보하는 방법이 필요 |
| 35 | + - tryLock() |
| 36 | +- 락을 확보하는데 시간이 오래 걸릴 수 있는 상황에서, 타임아웃을 지정하여 대기 시간을 제한할 수 있어야 함 |
| 37 | + - tryLock(timeout, unit) |
| 38 | +- 하지만 finally 블록에서 반드시 해제해야 함 |
| 39 | + |
| 40 | +--- |
| 41 | + |
| 42 | +## 13.1.1 폴링과 시간 제한이 있는 락 확보 방법 |
| 43 | + |
| 44 | +두가지 방식의 핵심은 락을 획득하려는 시도 뒤에 통제권을 얻을 수 있다는 것이다. |
| 45 | + |
| 46 | +- tryLock() : 즉시 반환 -> 폴링 방식으로 활용 가능 |
| 47 | +- tryLock(timeout, unit) : 지정된 시간 동안 락을 얻기 위해 대기 -> 타임아웃 방식으로 활용 가능 |
| 48 | + |
| 49 | +--- |
| 50 | + |
| 51 | +## 13.1.2 인터럽트 걸 수 있는 락 확보 방법 |
| 52 | + |
| 53 | +- lockInterruptibly() : 락을 얻기 위해 대기하는 동안 인터럽트가 걸리면, InterruptedException 발생 |
| 54 | +- tryLock(timeout, unit) : 지정된 시간 동안 락을 얻기 위해 대기하는 동안 인터럽트가 걸리면, InterruptedException 발생 |
| 55 | + |
| 56 | +두 메서드 모두 Thread 가 interrupted 상태인지 확인 후 인터럽트된 상태 일 경우 `acquire` 메서드에서 음수 반환 그리고, 이를 호출한 쓰레드에서 음수 판단 후 예외 발생 |
| 57 | + |
| 58 | +왜 쓸까? -> 처음에 Timeout 시간을 정하기 애매하고, 특정 트리거를 받아서 lock 대기를 해제하고 싶을 때? |
| 59 | + |
| 60 | +--- |
| 61 | + |
| 62 | +## 13.1.3 블록을 벗어나는 구조의 락 |
| 63 | + |
| 64 | +synchronized 는 진입 시 락을 획득하고 블록을 벗어날 때 자동으로 락을 해제하는 구조 |
| 65 | +-> 락 해제에 대한 실수를 방지해 준다. |
| 66 | + |
| 67 | +하지만, 복잡한 프로그램에서는 좀 더 유연한 구조의 lock 획득과 해제가 필요하다. |
| 68 | +-> hash collection 같은 경우 여러 개의 해시 블록을 구성하여 각각의 블록마다 락을 유연하게 거는 구조이다. |
| 69 | + |
| 70 | +--- |
| 71 | + |
| 72 | +# 13.2 성능에 대한 고려 사항 |
| 73 | + |
| 74 | +- 자바 5까지만 해도 성능적 측면에서, ReentrantLock > synchronized 이다. 특히, 스레드 개수가 늘어날 수록 성능차이는 심해진다. |
| 75 | +- 자바 6부터 JVM 에서 synchronized 를 최적화 하면서, 두 방식의 성능차이는 거의 없어졌다. |
| 76 | +- 교훈: `X 가 Y 보다 더 빠르다` 라는 명제는 그다지 오래 가지 못한다. |
| 77 | + |
| 78 | +--- |
| 79 | + |
| 80 | +# 13.3 공정성 |
| 81 | + |
| 82 | +- `ReentrantLock` 의 설정 방식은 두 가지가 있다. |
| 83 | + - 공정한 락 (fair lock) : 가장 오래 기다린 스레드가 가장 먼저 락을 획득 |
| 84 | + - 불공정한 락 (unfair lock) : 락을 기다리는 순서와 상관없이, 락이 해제되면 바로 획득 시도 |
| 85 | +``` java |
| 86 | +public ReentrantLock() { |
| 87 | + sync = new NonfairSync(); |
| 88 | +} |
| 89 | + |
| 90 | +public ReentrantLock(boolean fair) { |
| 91 | + sync = fair ? new FairSync() : new NonfairSync(); |
| 92 | +} |
| 93 | +``` |
| 94 | +- 두 방식 모두 Queue 에서 대기하는 것은 동일, 하지만 불공정락은 처음 락을 획득하려는 시도를 한다. |
| 95 | +``` java |
| 96 | +불공정 락 (NonfairSync) |
| 97 | +final boolean initialTryLock() { |
| 98 | + Thread current = Thread.currentThread(); |
| 99 | + if (compareAndSetState(0, 1)) { -> 냅다 락을 획득 시도 |
| 100 | + setExclusiveOwnerThread(current); |
| 101 | + return true; |
| 102 | + } |
| 103 | + ... |
| 104 | +} |
| 105 | + |
| 106 | +공정 락 (FairSync) |
| 107 | +final boolean initialTryLock() { |
| 108 | + Thread current = Thread.currentThread(); |
| 109 | + int c = getState(); |
| 110 | + if (c == 0) { -> 락 누가 쓰고있는지 봄 |
| 111 | + if (!hasQueuedThreads() && compareAndSetState(0, 1)) { -> 대기중인 스레드가 없으면 락 획득 시도 |
| 112 | + setExclusiveOwnerThread(current); |
| 113 | + return true; |
| 114 | + } |
| 115 | + } |
| 116 | + ... |
| 117 | +} |
| 118 | +``` |
| 119 | + |
| 120 | +- 언제 공정한 락 / 비공정한 락을 써야될까? |
| 121 | + - (1). 락을 얻으려했던 시점의 순서가 락을 획득하려했던 시점의 순서와 일치해야하는 경우 |
| 122 | + - (2). 락을 점유하고, 그 후의 실행 task 가 오래 걸리는 경우 -> cas 알고리즘으로 락 획득 시도를 하는 것이 오히려 낭비가 될 수 있다. |
| 123 | + - 그 외에는 비공정락이 성능적으로 좋기 때문에 사용할 것 같다. |
| 124 | +- 왜 비공정 락이 성능적으로 좋을까? |
| 125 | +- 공정락의 획득 순서를 보자. |
| 126 | +- ``` |
| 127 | + 1. T1: 락 점유중 |
| 128 | + 2. T2: 락 획득 시도 -> 실패 -> 대기 큐에 들어감 |
| 129 | + 3. T1: 락 해제 -> T2 이 대기 큐에서 가장 오래 기다렸으므로, T2 이 락 획득 |
| 130 | + ``` |
| 131 | +- 여기서 T1 이 락을 해제하고 T2 깨어나 다시 락을 획득하기 까지 시간이 걸린다. 이 간격의 손실이 있다. |
| 132 | +- 하지만 비공정 락은 해당 간격 사이에 T3 가 들어와서 락을 획득할 수 있다. (처리량 증가) |
| 133 | + |
| 134 | +--- |
| 135 | + |
| 136 | +# 13.4 synchronized 또는 ReentrantLock 선택 |
| 137 | + |
| 138 | +| 기능 | synchronized | Lock | |
| 139 | +|-----------|---------------------|-----------------------| |
| 140 | +| **기본 락** | `synchronized(obj)` | `lock.lock()` | |
| 141 | +| **인터럽트** | 불가능 | `lockInterruptibly()` | |
| 142 | +| **타임아웃** | 불가능 | `tryLock(time, unit)` | |
| 143 | +| **조건 변수** | 1개 (wait/notify) | 여러 개 (Condition) | |
| 144 | +| **공정성** | 불공정 | 공정/불공정 선택 | |
| 145 | +| **성능** | JVM 최적화 | 유연성 높음 | |
| 146 | + |
| 147 | +자바 5에는 synchronized 가 성능이 떨어졌지만, 어느 곳에서 블록됐는지 모니터링할 수 있었다. (ReentrantLock 은 불가) |
| 148 | +하지만, 자바 6부터 ReentrantLock 도 모니터링이 가능해졌다. |
| 149 | +``` java |
| 150 | +LockSupport.park(this); |
| 151 | +``` |
| 152 | +this 는 쓰레드를 대기하도록 한 주체인데, blocker 라고 칭한다. |
| 153 | +그냥 ReentrantLock 을 쓸 것 같다. |
| 154 | + |
| 155 | +--- |
| 156 | + |
| 157 | +# 13.5 읽기-쓰기 락 |
| 158 | +ReentrantLock 은 하나의 스레드만이 락을 확보할 수 있다. |
| 159 | +너무 엄격한거 아닌가? |
| 160 | +-> ReentrantReadWriteLock |
| 161 | +Read / Write 락을 따로 관리 |
| 162 | +- Write 락 있을 시 Read 락 못 얻음 |
| 163 | +- Read 락 있을 시 Write 락 못 얻음 |
| 164 | +- Read 락 여러 개 가능 |
| 165 | +-> MySql 의 공유 락 / 베타 락 같은 느낌인 듯 함 (for Share / for Update) |
| 166 | + |
| 167 | +--- |
| 168 | + |
| 169 | +# 분산락 (추가) |
| 170 | +Multi instance 에서 Lock 을 걸어야 하는 상황이 발생할 수 있다. |
| 171 | +Redisson 을 이용하여 분산락을 구현할 수 있다. |
| 172 | +LuaScript 를 이용하여 원자적으로 처리할 수 있다. |
| 173 | + |
| 174 | +``` java |
| 175 | +불공정락 |
| 176 | +client.getLock() // pub/sub + broadcast |
| 177 | + |
| 178 | +공정락 |
| 179 | +client.getFairLock() // pub/sub + FIFO |
| 180 | + |
| 181 | +스핀락 |
| 182 | +client.getSpinLock() // polling + backoff strategy |
| 183 | + |
| 184 | +``` |
0 commit comments