1. λμμ± μ²λ¦¬λ μ νλκ±ΈκΉ?
λμμ± μ²λ¦¬λ₯Ό νμ§ μμΌλ©΄ μ μ κ° ν¬μΈνΈλ₯Ό λμμ μΆ©μ λλ μ¬μ©ν λ λ°μ΄ν° λΆμΌμΉ λ¬Έμ κ° λ°μν©λλ€. μ΄λ μ¬λ¬ μ€λ λκ° κ°μ 리μμ€μ μ κ·Όνλ©΄μ λ°μνλ Race Condition(κ²½μ μν)
νμμ΄λΌ λ³Ό μ μλλ°, Race Condition
μ΄λ 곡μ μμμ λν΄ μ¬λ¬ νλ‘μΈμ€κ° λμμ μ κ·Όμ μλν λ, μ€ν μμλ μκ°μ μν΄ κ²°κ³Ό κ°μ μν₯μ μ€ μ μλ μνλ₯Ό λ§ν©λλ€.
μλ κ·Έλ¦Όμ ν΅ν΄ Race Conditionμ λν μμλ₯Ό λ€μ΄λ³΄κ² μ΅λλ€.
μ κ·Έλ¦Όκ³Ό κ°μ΄ 2κ°μ μ€λ λμμ μ€νλ κ°κ°μ νμ€ν¬κ° point
λΌλ λ³μμ λμμ μ κ·Όνμ¬ μ΄κΈ°κ° 0μ μ½μ΄μμ΅λλ€. κ·Έλ¦¬κ³ κ°κ° 500μ λνλ μ°μ°μ μννκ³ , pointμ κ²°κ³Ό κ°μ λμ
νμ΅λλ€. κ·Έλ¬λ κΈ°λμλ λ€λ₯΄κ² 1000μ΄ μλ 500μ΄ λμ€κ² λ©λλ€.
ν΄λΉ λ¬Έμ μν©μ ν μ€νΈ μ½λλ‘ μ΄ν΄λ³΄κ² μ΅λλ€.
class RaceConditionTest {
inner class PointManager {
private var point = 0
fun add(amount: Int) {
Thread.sleep(100)
point += amount
}
fun getPoint(): Int {
return point
}
}
@Test
fun `λμμ ν¬μΈνΈλ₯Ό 500μ© μΆ©μ νλ©΄ ν¬μΈνΈκ° 1000μμ΄ λλ€`() {
// given
val pointManager = PointManager()
val executor = Executors.newFixedThreadPool(2)
val tasks = listOf(
Callable { pointManager.add(500) },
Callable { pointManager.add(500) },
)
// when
executor.invokeAll(tasks)
executor.shutdown()
// then
assertEquals(1000, pointManager.getPoint())
}
}
μ μ½λλ μ€λ λ νμ 2κ°μ μ€λ λλ₯Ό μμ±νκ³ , ν¬μΈνΈλ₯Ό 500 μ¦κ° μν€λ νμ€ν¬ 2κ°λ₯Ό μνν μμ μ λλ€. μ ν μ€νΈλ₯Ό μνν΄λ³΄λ©΄ μ±κ³΅νλ κ²½μ°λ μμ§λ§ μ€ν¨νλ κ²½μ°λ μκΈ°κ² λ©λλ€. κ·Έ μ΄μ λ μ€λ λκ° μννλ μμλ νμ΄λ°μ΄ λ§€λ² λ¬λΌμ Έ κ²°κ³Όκ°μ΄ λ¬λΌμ§κΈ° λλ¬Έμ λλ€.
κ·ΈλΌ μ°λ¦¬λ μ΄λ»κ² ν΄μΌ ν μ€νΈλ₯Ό νμ μ±κ³΅μ μΌλ‘ μν κ°λ₯νκ² λ§λ€ μ μμκΉμ?
2. synchronizedλ₯Ό μ΄μ©ν μ κ·Ό μ ν
μ΄λ²μλ λ€λ₯Έ μμ μ κ·Έλ¦Όμ 보면μ λ€μ μκ°ν΄ λ³΄κ² μ΅λλ€.
μ΄λ²μλ pointμ μ κΈ μ₯μΉλ₯Ό λΆμ¬νμ΅λλ€. μ΄μ λ₯Ό κ°μ§κ³ μλ μ€λ λλ§ μ κ·Όμ νμ©νκ³ , μ΄μ λ₯Ό κ°μ§κ³ μμ§ μμ μ€λ λλ point λ‘λΆν° μ΄μ λ₯Ό μ»κΈ° μ κΉμ§λ λκΈ° μνμ λΉ μ§κ² λ©λλ€. μ΄ν μ΄μ λ₯Ό λ°κ² λλ©΄ λ‘μ§μ μννκ² λ©λλ€.
μ΄λ κ² λλ©΄ μ΄μ μ λ°μν λμμ± λ¬Έμ λ₯Ό μ½κ² ν΄κ²°ν μ μμ΅λλ€. pointμ κ°μ 곡μ μμμκ² μ€λ λκ° μμ°¨μ μΌλ‘ μ κ·Όνλλ‘ λ³΄μ₯νλ©΄μ μνλ κ²°κ³Όκ°μ λμΆν μ μκ² λμμ΅λλ€. μ΄μ κ°μ΄ λμμ κ·Ό νλ €κ³ νλ μμμμ λ¬Έμ κ° λ°μνμ§ μκ² λ
μ μ 보μ₯ν΄μ€μΌ νλ μμμ μκ³ μμ
μ΄λΌκ³ ν©λλ€.
Java μ§μμμ μκ³ μμμ μ½κ² μ€μ ν΄μ€ μ μμ΅λλ€. synchronized
λΌλ ν€μλλ₯Ό μ¬μ©ν μ μλλ°, μ΄ ν€μλλ₯Ό μ¬μ©νμ¬ μκ³μμμ μ€μ νλ©΄, λμμ μ¬λ¬ μ€λ λκ° κ°μ 리μμ€μ μ κ·Όνλ λ¬Έμ λ₯Ό ν΄κ²°ν μ μμ΅λλ€. κ°λ¨νκ² μκ³ μμμ μ§μ ν λ©μλ μμ@Synchronized
μ΄λ
Έν
μ΄μ
λ§ μ μΈν΄μ£Όλ©΄ λ©λλ€.
class RaceConditionTest {
inner class PointManager {
private var point = 0
@Synchronized
fun add(amount: Int) {
Thread.sleep(10)
point += amount
}
fun getPoint(): Int {
return point
}
}
@Test
fun `λμμ ν¬μΈνΈλ₯Ό 500μ© μΆ©μ νλ©΄ ν¬μΈνΈκ° 1000μμ΄ λλ€`() {
// given
val pointManager = PointManager()
val executor = Executors.newFixedThreadPool(2)
val tasks = listOf(
Callable { pointManager.add(500) },
Callable { pointManager.add(500) },
)
// when
executor.invokeAll(tasks)
executor.shutdown()
// then
assertEquals(1000, pointManager.getPoint())
}
}
첫 λ²μ§Έλ‘ λ΄€λ μμ μ½λμ λΉκ΅ν΄λ³΄λ©΄ add()
λ©μλ μ @Synchronized
λ§ μΆκ°ν κ² λΉΌκ³€ μμ λμΌν μ½λμ
λλ€. μκ³ μμμ μ€μ ν¨μΌλ‘μ¨ κ³΅μ μμμ λν΄ μ¬λ¬ μ€λ λκ° μ κ·Όν΄λ λμΌν κ²°κ³Όκ°μ λ°μ μ μκ² λμμ΅λλ€.
κ·ΈλΌ μ΄μ κ° μ μ λ³λ‘ ν¬μΈνΈλ₯Ό κ΄λ¦¬νλλ‘ μμ λ₯Ό λ³κ²½ν΄λ³΄κ² μ΅λλ€.
μ μ IDλ₯Ό ν€λ‘ κ°μ§λ HashMap
μ μ¬μ©ν΄ μ μ λ³λ‘ ν¬μΈνΈλ₯Ό κ΄λ¦¬νλλ‘ λ³κ²½νμ΅λλ€. μλλ λ³κ²½ν μ½λμ
λλ€.
class RaceConditionTest {
inner class PointManager {
private val userPoints: MutableMap<Int, Int> = HashMap()
@Synchronized
fun add(userId: Int, amount: Int) {
Thread.sleep(100)
val point = userPoints.getOrDefault(userId, 0)
userPoints[userId] = point + amount
}
fun getPoint(userId: Int): Int {
return userPoints.getOrDefault(userId, 0)
}
}
@Test
fun `λλͺ
μ μ μ μκ² ν¬μΈνΈλ₯Ό 100μ© 10λ² μΆ©μ νλ©΄ ν¬μΈνΈκ° κ° 1000μμ΄ λλ€`() {
// given
val pointManager = PointManager()
val executor = Executors.newFixedThreadPool(10)
val tasks = mutableListOf<Callable<Unit>>()
repeat(10) {
tasks.add(Callable { pointManager.add(1, 100) }) // μ μ 1
tasks.add(Callable { pointManager.add(2, 100) }) // μ μ 2
}
// when
executor.invokeAll(tasks)
executor.shutdown()
// then
assertEquals(1000, pointManager.getPoint(1))
assertEquals(1000, pointManager.getPoint(2))
}
}
Synchronized
λλΆμ μμ κ°μ΄ λμμ μΆ©μ ν΄λ κ° μ μ λ³λ‘ 1000μμ΄ μΆ©μ μ΄ μ λλ κ²μ νμΈν μ μμ΅λλ€.
κ·Έλ°λ° μ΄μν μ μ΄ μμ΅λλ€. μ리ν κ΄μ°°λ ₯μ΄λΌλ©΄ μ΄λ―Έ λ°κ²¬νμ μλ μλλ°, λ°λ‘ ν
μ€νΈ κ²°κ³Όμ μν μκ°μ
λλ€. add()
λ₯Ό μννλλ° μ½ 0.1μ΄κ° κ±Έλ¦°λ€λ©΄ κ° μ μ λ³λ‘ 1μ΄κ° 걸리λ ν
μ€νΈλ μ½ 1μ΄κ° μνλμ΄μΌ ν©λλ€. νμ§λ§ μ μ 1κ³Ό μ μ 2λ μλ‘ κ°μ Έκ° λ½μ λ°κΈ° μν΄ λκΈ° μνμ λΉ μ§κ³ κ²°κ΅ μν μκ°μ΄ 2μ΄κ° λμ΄κ°λ κ²μ νμΈν μ μμ΅λλ€. μ¬κΈ°μ Synchronized μ λ¨μ μ λ°κ²¬ν μ μμ΅λλ€.
Synchronized λ μ μμ μΌλ‘ λ½μ κ±ΈκΈ° λλ¬Έμ λ λͺ μ΄μμ μ μ κ° ν΄λΉ λΈλ‘μ μ κ·Όν κ²½μ°, μλ‘μ λ½μ κ±Έλ €λ²λ¦¬λ μν©μ΄ μκΉλλ€. μ°λ¦¬κ° μνλ κ²μ λ€λ₯Έ μ μ μ μΆ©μ μμ²μ μκ΄μμ΄ μ½ 1μ΄ λ§μ ν¬μΈνΈλ₯Ό μΆ©μ νλ κ²μ λλ€.
3. μ μ λ³ Lock κ°μ²΄ ꡬν
Synchronized λ Java 1.0λΆν° μ 곡λλ κΈ°λ₯μ΄λ©°, Java 1.5λΆν° java,util.concurrentλΌλ λμμ± λ¬Έμ ν΄κ²°μ μν ν¨ν€μ§κ° μΆκ°λμμ΅λλ€. ν΄λΉ ν¨ν€μ§μλ Lock
μΈν°νμ΄μ€μ ReentrantLock
ꡬνμ²΄κ° μ 곡λ©λλ€.
ReentrantLock λ Synchronized μ λ§μ°¬κ°μ§λ‘ μκ³ μμμ μ€μ νμ§λ§, λ³΄λ€ μ κ΅νκ² μ¬μ©μ΄ κ°λ₯ν©λλ€. μ°λ¦¬μκ² νμν κ²μ μ μ λ³λ‘ λ½μ κ΄λ¦¬νλ κ²μ λλ€. λ€μ μμλ₯Ό μλ κ·Έλ¦Όμ ν΅ν΄ μμλ³΄κ² μ΅λλ€.
Lockμ λ΄λΉνλ userLocks
λ₯Ό λ§λ€μ΄ μ΄μ μ μ λ§λ€ λ½μ κ΄λ¦¬νλλ‘ λ³κ²½νμ΅λλ€. λ³κ²½λ μ½λλ₯Ό μμ±ν΄λ³΄κ² μ΅λλ€.
class RaceConditionTest {
inner class LockManager {
private val userLocks: MutableMap<Int, Lock> = HashMap()
fun getLock(userId: Int): Lock {
return userLocks.computeIfAbsent(userId) { ReentrantLock() }
}
}
inner class PointManager(private val lockManager: LockManager) {
private val userPoints: MutableMap<Int, Int> = HashMap()
fun add(userId: Int, amount: Int) {
val lock = lockManager.getLock(userId)
lock.lock()
try {
Thread.sleep(100)
val point = userPoints.getOrDefault(userId, 0)
userPoints[userId] = point + amount
} finally {
lock.unlock()
}
}
fun getPoint(userId: Int): Int {
return userPoints.getOrDefault(userId, 0)
}
}
@Test
fun `λλͺ
μ μ μ μκ² ν¬μΈνΈλ₯Ό 100μ© 10λ² μΆ©μ νλ©΄ ν¬μΈνΈκ° κ° 1000μμ΄ λλ€`() {
// given
val lockManager = LockManager()
val pointManager = PointManager(lockManager)
val executor = Executors.newFixedThreadPool(2)
val tasks = mutableListOf<Callable<Unit>>()
repeat(10) {
tasks.add(Callable { pointManager.add(1, 100) }) // μ μ 1
tasks.add(Callable { pointManager.add(2, 100) }) // μ μ 2
}
// when
executor.invokeAll(tasks)
executor.shutdown()
// then
assertEquals(1000, pointManager.getPoint(1))
assertEquals(1000, pointManager.getPoint(2))
}
}
LockManager
ν΄λμ€λ₯Ό λ§λ€μ΄ μ μ λ³ ReentrantLock
μ κ΄λ¦¬νλλ‘ νμ΅λλ€. κ·Έλ¦¬κ³ μ€ν κ²°κ³Όλ μλμ κ°μ΅λλ€.
μ΄λ‘μ¨ μ ν¬κ° μνλ λλ‘ λ€λ₯Έ μ μ μ ν¬μΈνΈ μΆ©μ μμ²κ³Όλ μκ΄μμ΄ μΆ©μ μμ μκ°μ΄ μ½ 1μ΄ κ±Έλ¦¬λ κ²μ λ³Ό μ μκ² λμμ΅λλ€!