๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Java

[JAVA] ConcurrentHashMap

by ๋Œ€๋ณต2 2025. 5. 17.

 

์„œ๋ก 

๋ฉ€ํ‹ฐ ์Šค๋ ˆ๋“œ๋ฅผ ํ™œ์šฉํ•œ ์„œ๋น„์Šค ๊ฐœ๋ฐœ์„ ํ•˜๋˜ ์ค‘, ํ•ด๋‹น ๊ฐœ๋…์„ ์•Œ๊ฒŒ ๋˜์—ˆ๊ณ  ์ด๊ฒŒ ๋ฌด์—‡์ธ์ง€์— ๋Œ€ํ•ด ์ƒ์„ธํ•˜๊ฒŒ ๋‹ค๋ฃฐ ํ•„์š”๊ฐ€ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ด ์ž‘์„ฑํ–ˆ๋‹ค.

์ •์˜

ConcurrentHashMap์€ Java์—์„œ ๋™์‹œ์„ฑ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ์„ค๊ณ„๋œ ์Šค๋ ˆ๋“œ ์•ˆ์ „ํ•œ HashMap์ด๋‹ค.
Java 1.5๋ถ€ํ„ฐ java.util.concurrent ํŒจํ‚ค์ง€์— ํฌํ•จ๋˜์–ด ์žˆ์œผ๋ฉฐ, ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ฝ๊ณ  ์“ธ ์ˆ˜ ์žˆ๋„๋ก ํŠน๋ณ„ํ•œ ๋ฝ ๋ถ„ํ•  ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ์‚ฌ์šฉํ•œ๋‹ค.


๋งŒ๋“ค์–ด์ง„ ๋ฐฐ๊ฒฝ

  • ๊ธฐ์กด HashMap์€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ํ™˜๊ฒฝ์—์„œ ๋™๊ธฐํ™”๊ฐ€ ๋˜์–ด ์žˆ์ง€ ์•Š์•„ race condition์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
  • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Hashtable์ด๋‚˜ synchronizedMap์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ์ด๋“ค์€ ์ „์ฒด ๋งต์— ๋Œ€ํ•ด synchronized ์ฒ˜๋ฆฌ๋ฅผ ํ•˜์—ฌ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ์ด ์ƒ๊น€.

=> ์ด๋ฅผ ๊ฐœ์„ ํ•˜๊ณ ์ž ๋ณด๋‹ค ์ •๊ตํ•˜๊ฒŒ ๋™๊ธฐํ™”๋ฅผ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋Š” ConcurrentHashMap์ด ๋“ฑ์žฅํ•˜์˜€๋‹ค.

race condition: ๋‘˜ ์ด์ƒ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ์ž์›์— ์ ‘๊ทผํ•˜๊ฑฐ๋‚˜ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ, ์‹คํ–‰ ์ˆœ์„œ์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ฌ๋ผ์ง€๋Š” ๋ฌธ์ œ ์ƒํ™ฉ


ํŠน์ง•

  • ์„ธ๋ถ„ํ™”๋œ ๋ฝ(๋ถ„ํ•  ๋ฝ):
    Java 8 ์ด์ „๊นŒ์ง€๋Š” Segment ๋‹จ์œ„๋กœ ๋ฝ์„ ๋‚˜๋ˆด๊ณ , Java 8๋ถ€ํ„ฐ๋Š” bucket(๋ฐฐ์—ด์˜ ๊ฐ ์š”์†Œ) ๋‹จ์œ„๋กœ ๋™๊ธฐํ™”ํ•˜์—ฌ ๋ณ‘๋ ฌ์„ฑ์„ ๋†’์˜€๋‹ค.
  • ์ฝ๊ธฐ ์ž‘์—…์€ ๋ฝ ์—†์ด ์ฒ˜๋ฆฌ
  • ๋Œ€๋ถ€๋ถ„์˜ ์ฝ๊ธฐ ์ž‘์—…์€ ๋ฝ ์—†์ด ์ง„ํ–‰๋˜๋ฉฐ, ๋ณ€๊ฒฝ ์ž‘์—…๋งŒ ์ตœ์†Œํ•œ์˜ ๋ฝ์œผ๋กœ ์ฒ˜๋ฆฌ๋œ๋‹ค.
  • null key, null value ๊ธˆ์ง€
  • null ํ‚ค๋‚˜ null ๊ฐ’์„ ํ—ˆ์šฉํ•˜์ง€ ์•Š์œผ๋ฉฐ, NullPointerException์ด ๋ฐœ์ƒํ•œ๋‹ค.
  • ์„ฑ๋Šฅ ์ค‘์‹ฌ ์„ค๊ณ„
  • ๋ฝ์˜ ๋ฒ”์œ„๋ฅผ ์ตœ์†Œํ™”ํ•˜์—ฌ ์„ฑ๋Šฅ ์ €ํ•˜ ์—†์ด ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ์„ ํ™•๋ณดํ–ˆ๋‹ค.

๋™์ž‘ ๊ตฌ์กฐ

1. ๋ฒ„ํ‚ท์ด ๋น„์–ด ์žˆ์œผ๋ฉด ๋ฝ ์—†์ด ๋„ฃ๋Š”๋‹ค (CAS)

  • ๋ฐ์ดํ„ฐ๋ฅผ ๋„ฃ์„ ์œ„์น˜(๋ฒ„ํ‚ท)๊ฐ€ ๋น„์–ด ์žˆ์œผ๋ฉด,
  • ๋ณต์žกํ•œ ๋ฝ ์—†์ด, ํ•œ ๋ฒˆ์— ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” ๋ฐฉ๋ฒ•(CAS)์„ ์‚ฌ์šฉํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฝ์ž…
    CAS(Compare-And-Swap): "ํ˜„์žฌ ๊ฐ’์ด ์˜ˆ์ƒํ•œ ๊ฐ’๊ณผ ๊ฐ™์œผ๋ฉด ์ƒˆ๋กœ์šด ๊ฐ’์œผ๋กœ ๋ฐ”๊ฟ”๋ผ!" ๋ผ๋Š” ์กฐ๊ฑด๋ถ€ ์—ฐ์‚ฐ
// Java์˜ AtomicInteger์—์„œ ์˜ˆ์‹œ 
AtomicInteger count = new AtomicInteger(0); 
count.compareAndSet(0, 1); // ํ˜„์žฌ ๊ฐ’์ด 0์ด๋ฉด 1๋กœ ๋ฐ”๊ฟ”๋ผ`

2. ์ถฉ๋Œํ•˜๋ฉด ๊ทธ ๋ฒ„ํ‚ท๋งŒ ์ž ๊ทผ๋‹ค (synchronized)

  • ๊ฐ™์€ ๋ฒ„ํ‚ท์— ์—ฌ๋Ÿฌ ๋ฐ์ดํ„ฐ๊ฐ€ ๋“ค์–ด๊ฐ€์•ผ ํ•  ๋•Œ(ํ•ด์‹œ ์ถฉ๋Œ),
  • ์ „์ฒด ๋งต์„ ์ž ๊ทธ์ง€ ์•Š๊ณ  ๊ทธ ๋ฒ„ํ‚ท ํ•˜๋‚˜๋งŒ ์ž ๊ทผ๋‹ค.
  • ํ•ด๋‹น ๋ฐฉ๋ฒ•์œผ๋กœ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ ์„ฑ๋Šฅ ์œ ์ง€

3. ์ถฉ๋Œ์ด ๋งŽ์•„์ง€๋ฉด ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋ฅผ ํŠธ๋ฆฌ๋กœ ๋ฐ”๊พผ๋‹ค

  • ๊ฐ™์€ ๋ฒ„ํ‚ท์— ๋ฐ์ดํ„ฐ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์ด ๋ชฐ๋ฆฌ๋ฉด
  • ๋‹จ์ˆœํžˆ ์ค„ ์„ธ์šฐ๋Š” ์—ฐ๊ฒฐ ๋ฆฌ์ŠคํŠธ๋ณด๋‹ค, ๊ฒ€์ƒ‰ ๋น ๋ฅธ ํŠธ๋ฆฌ(Red-Black Tree)๋กœ ๋ฐ”๊ฟ”์„œ
  • ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ๋ง‰๋Š”๋‹ค.

์žฅ์ ๊ณผ ๋‹จ์ 

์žฅ์ 

  • ๋†’์€ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ: ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋™์‹œ์— ์ฝ๊ธฐ/์“ฐ๊ธฐ ๊ฐ€๋Šฅ
  • Deadlock ๋ฐฉ์ง€: ์ „์ฒด ๋งต์„ ์ž ๊ทธ์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๋ณ‘๋ชฉ์ด ์ ์Œ
  • ์‹ ๋ขฐ์„ฑ ์žˆ๋Š” ๋™์‹œ ์ฒ˜๋ฆฌ: ๋ฝ์ด ์„ธ๋ถ„ํ™”๋˜์–ด ์žˆ์–ด race condition ๋ฐฉ์ง€์— ํšจ๊ณผ์ 

๋‹จ์ 

  • null ํ‚ค/๊ฐ’ ๋ถˆ๊ฐ€: ๊ธฐ์กด HashMap๊ณผ ๋‹ฌ๋ฆฌ null ๊ด€๋ จ ์œ ์—ฐ์„ฑ์ด ์—†์Œ
  • ๋ณต์žกํ•œ ๋‚ด๋ถ€ ๊ตฌํ˜„: ๊ตฌ์กฐ๊ฐ€ ๋ณต์žกํ•ด ๋””๋ฒ„๊น…์ด๋‚˜ ์ปค์Šคํ„ฐ๋งˆ์ด์ง•์ด ์–ด๋ ค์›€
  • ํŠน์ • ์ƒํ™ฉ์—์„œ์˜ ์„ฑ๋Šฅ ์ €ํ•˜: ๋†’์€ ์ถฉ๋Œ๋ฅ  ์‹œ segment ๊ฐ„ contention ๋ฐœ์ƒ ๊ฐ€๋Šฅ
    contention(๋ฝ ๊ฒฝ์Ÿ, ๊ฒฝํ•ฉ): ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ํ•œ๊ฐœ ๋ฒ„ํ‚ท์— ์ ‘๊ทผํ•  ๋•Œ ๋ฒ„ํ‚ท์˜ ๋ฝ์ด ๋ณ‘๋ชฉ ์ง€์ ์ด ๋ผ์„œ ์—ฌ๋Ÿฌ ์Šค๋ ˆ๋“œ๊ฐ€ ๋Œ€๊ธฐ ์ƒํƒœ์— ๋น ์ง€๋Š” ์ƒํ™ฉ

๐Ÿ”ธ ๋™์‹œ์„ฑ ์ฒ˜๋ฆฌ ๋น„๊ต

์ปฌ๋ ‰์…˜ ์Šค๋ ˆ๋“œ ์•ˆ์ „์„ฑ ์„ฑ๋Šฅ ๋น„๊ณ 
HashMap X ๋น ๋ฆ„ ๋™๊ธฐํ™” ๋ถˆ๊ฐ€
Hashtable O ๋А๋ฆผ ์ „์ฒด ๋ฉ”์„œ๋“œ ๋™๊ธฐํ™”
Collections.synchronizedMap() O ๋ณดํ†ต ๋‹จ์ผ ๋ฝ ๊ธฐ๋ฐ˜
ConcurrentHashMap O ๊ฐ€์žฅ ๋น ๋ฆ„ ์„ธ๋ถ„ํ™”๋œ ๋ฝ, ๋ณ‘๋ ฌ์„ฑ ์šฐ์ˆ˜

'Java' ์นดํ…Œ๊ณ ๋ฆฌ์˜ ๋‹ค๋ฅธ ๊ธ€

[JAVA] GC - JAVA ๋ฒ„์ „ ๋ณ„ ์ฃผ์š” ์•Œ๊ณ ๋ฆฌ์ฆ˜  (0) 2025.05.17
[JAVA] GC - Mark & Sweep ๋™์ž‘ ์›๋ฆฌ  (0) 2025.05.17
[JAVA] File I/O  (0) 2024.07.28
[JAVA] Exception  (0) 2024.07.28
[JAVA] Stream API  (0) 2024.07.25