<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>눙눙의 개발일지</title>
    <link>https://oniwon.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sat, 18 Jul 2026 23:18:03 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>oniwon</managingEditor>
    <image>
      <title>눙눙의 개발일지</title>
      <url>https://tistory1.daumcdn.net/tistory/7384996/attach/69d0b5a5f1b74ad48732ee24d78eb16c</url>
      <link>https://oniwon.tistory.com</link>
    </image>
    <item>
      <title>[F-Lab] 자바 백엔드 멘토링 1개월 후기</title>
      <link>https://oniwon.tistory.com/66</link>
      <description>&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;[F-Lab 멘토링 할인 코드]&lt;br /&gt;&lt;b&gt;커넥터스2385&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;왜 F-Lab이었나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SI 회사 퇴사 전부터 실제 트래픽을 받는 서비스 회사에 들어가 개발하고 싶다는 마음이 컸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사를 다니면서 가장 아쉬웠던 부분인 &lt;b&gt;멘토의 부재&lt;/b&gt;를 채우기 위해,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1:1 멘토링의 장점이 있는 F-Lab을 신청하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 신청 동기는 &lt;a href=&quot;https://oniwon.tistory.com/56&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;[F-Lab] 0주차 시작&lt;/a&gt;에 정리해 두었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;한 달 동안 무엇을 학습했는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 신 1, 2권을 완독했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순히 '책을 읽었다'에 그치는 것이 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책에 없는 &lt;b&gt;깊이 있는 내용들까지 함께 학습&lt;/b&gt;했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래&amp;nbsp;알고&amp;nbsp;있다고&amp;nbsp;생각했던&amp;nbsp;자바&amp;nbsp;개념들을&amp;nbsp;처음부터&amp;nbsp;다시&amp;nbsp;들여다보는&amp;nbsp;시간이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주차별 회고에 기록했듯이, 멘토링에서 그동안 다룬 주제만 해도 꽤 많다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동시성: synchronized, AtomicInteger, Mutex와 Semaphore, ThreadLocal&lt;/li&gt;
&lt;li&gt;컬렉션: ArrayList/LinkedList/Vector, HashSet/TreeSet/LinkedHashSet, 해시맵 버킷 계산의 비트 연산&lt;/li&gt;
&lt;li&gt;객체지향: 다형성, 상속, 컴포지션, DI, 인터페이스 vs 추상 클래스&lt;/li&gt;
&lt;li&gt;자바 키워드와 동작: equals/hashCode, enum, final/transient, String, finalize/clone&lt;/li&gt;
&lt;li&gt;네트워크: 블로킹, 논블로킹 &amp;times; 동기, 비동기 4조합, TCP/UDP&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에도 훨씬 많은 개념들을 공부했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념 학습 뿐만 아니라 JDK 내부 동작을 살펴보면서 &lt;b&gt;JVM, 메모리, 네트워크&lt;/b&gt; &lt;b&gt;측면&lt;/b&gt;에서도 함께 생각해 볼 수 있었고, 이 과정에서 시야가 한 단계 넓어졌다고 느낀다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토님&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멘토링을 시작하기 전에 얻고 싶은 바가 분명한 편이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로&lt;b&gt;&amp;nbsp;깊이&amp;nbsp;있는&amp;nbsp;꼬리&amp;nbsp;질문&lt;/b&gt;,&amp;nbsp;그리고&amp;nbsp;&lt;b&gt;커리어&amp;nbsp;방향&lt;/b&gt;에&amp;nbsp;대한&amp;nbsp;이야기였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 멘토님은 이 기준에서 충분히 만족스럽다.&lt;br /&gt;특히 AI에 관한 이야기를 나누면서,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안&amp;nbsp;내가&amp;nbsp;미처&amp;nbsp;보지&amp;nbsp;못했던&amp;nbsp;관점들이&amp;nbsp;많았다는&amp;nbsp;걸&amp;nbsp;알게&amp;nbsp;됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소통적인 면에서도 슬랙에 질문을 남기면 답이 빠르신 편이라,&lt;br /&gt;일주일 동안 막히는 부분이 있어도 흐름이 끊기지 않아서 좋았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvf4dE/dJMcajhXNPm/xZxCKyJiC1tnJxYcFNqfvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvf4dE/dJMcajhXNPm/xZxCKyJiC1tnJxYcFNqfvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvf4dE/dJMcajhXNPm/xZxCKyJiC1tnJxYcFNqfvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbvf4dE%2FdJMcajhXNPm%2FxZxCKyJiC1tnJxYcFNqfvK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;456&quot; height=&quot;582&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;582&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;1개월 이후 시점의 만족도&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4주차가 된 지금 시점에서 말하자면, 대체적으로 만족스럽다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크게 세 가지 측면으로 정리해 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 정의를 정확히 말하게 만드는 압박&lt;/b&gt;&lt;br /&gt;&quot;알고 있다&quot;와 &quot;설명할 수 있다&quot;는 완전히 다르다는 걸 1주차에 바로 깨달았다.&lt;br /&gt;개념을&amp;nbsp;말로&amp;nbsp;직접&amp;nbsp;꺼내&amp;nbsp;보면서&amp;nbsp;어설프게&amp;nbsp;알던&amp;nbsp;부분이&amp;nbsp;드러나고,&lt;br /&gt;멘토링 이후에 그 개념들을 정확하게 다시 정리하게 된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 개념과 개념이 연결됨&lt;/b&gt;&lt;br /&gt;자바의 pass by value와 익명 클래스의 변수 캡처가 서로 이어지는 개념인 것처럼,&lt;br /&gt;별개라고 생각했던 개념들을 구조화할 수 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;3. 학습 외의 상담&lt;/b&gt;&lt;br /&gt;3주차엔 이력서를 같이 봐주시고 프로젝트 관련 조언을 받았다.&lt;br /&gt;기술 멘토링만이 아니라, 커리어 방향까지 함께 이야기할 수 있다는 게 장점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굳이 아쉬운 점을 한 가지 꼽자면, 오프라인 네트워킹 기회가 생각보다 많지는 않았다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;앞으로&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주는 JSP 주요 챕터와 객체지향 관련 도서를 학습하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병행해서 프로젝트가 남아 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI에게 작업을 위임하려면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자 본인이 전체 아키텍처 구조를 이해하고 병목 지점을 짚어낼 수 있어야&amp;nbsp;한다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 AI에게 일을 시키는 설계자가 되어야 한다고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직은 초기 구현 단계라 많이 부족하다고 느끼지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한&lt;span&gt;&amp;nbsp;&lt;/span&gt;달&lt;span&gt;&amp;nbsp;&lt;/span&gt;뒤의&lt;span&gt;&amp;nbsp;&lt;/span&gt;후기에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;감을&lt;span&gt;&amp;nbsp;&lt;/span&gt;잡고&lt;span&gt;&amp;nbsp;&lt;/span&gt;한&lt;span&gt;&amp;nbsp;&lt;/span&gt;단계&lt;span&gt;&amp;nbsp;&lt;/span&gt;발전한&lt;span&gt;&amp;nbsp;&lt;/span&gt;모습이&lt;span&gt; &lt;/span&gt;되고&lt;span&gt; &lt;/span&gt;싶다&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DGEtI/dJMcafGGxPg/OkZIybpIyiK1mkGUOWBZsk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DGEtI/dJMcafGGxPg/OkZIybpIyiK1mkGUOWBZsk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DGEtI/dJMcafGGxPg/OkZIybpIyiK1mkGUOWBZsk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDGEtI%2FdJMcafGGxPg%2FOkZIybpIyiK1mkGUOWBZsk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;227&quot; height=&quot;226&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩&lt;span&gt;&amp;nbsp;&lt;/span&gt;관련&lt;span&gt;&amp;nbsp;&lt;/span&gt;질문은&lt;span&gt;&amp;nbsp;&lt;a href=&quot;https://open.kakao.com/o/s7WbPrui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;오픈채팅&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;/span&gt;남겨주시면&lt;span&gt;&amp;nbsp;&lt;/span&gt;제가&lt;span&gt;&amp;nbsp;&lt;/span&gt;아는&lt;span&gt;&amp;nbsp;&lt;/span&gt;선에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;답변드리겠습니다&lt;span&gt;!&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추천인 코드: 커넥터스2385&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>F-Lab</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/66</guid>
      <comments>https://oniwon.tistory.com/66#entry66comment</comments>
      <pubDate>Tue, 19 May 2026 17:07:22 +0900</pubDate>
    </item>
    <item>
      <title>[F-Lab] 4주차 정리 및 회고</title>
      <link>https://oniwon.tistory.com/65</link>
      <description>&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;[F-Lab 멘토링 할인 코드]&lt;br /&gt;&lt;b&gt;커넥터스2385&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;사전 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 신 2 4장 ~ 10장&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 질문&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ArrayList 와 LinkedList 차이, &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Vector&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;ConcurrentModificationException&lt;/li&gt;
&lt;li data-stringify-border=&quot;0&quot; data-stringify-indent=&quot;0&quot;&gt;HashSet, TreeSet, LinkedHashSet 차이&lt;/li&gt;
&lt;li&gt;Thread와 Process 차이&lt;/li&gt;
&lt;li&gt;Thread를 왜 써야 할까?&lt;/li&gt;
&lt;li&gt;synchronized 키워드&lt;/li&gt;
&lt;li&gt;AtomicInteger, AtomicBoolean 등의 클래스&lt;/li&gt;
&lt;li&gt;Mutex와 Semaphore&amp;nbsp;차이&lt;/li&gt;
&lt;li&gt;TCP, UDP 차이&lt;/li&gt;
&lt;li&gt;비대칭 키 암호화&lt;/li&gt;
&lt;li&gt;AutoClosable 인터페이스&lt;/li&gt;
&lt;li&gt;Thread Local&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;과제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;C/C++로 소켓 통신 구현 실습&lt;/li&gt;
&lt;li&gt;프로젝트 기획 및 기능 구체화&lt;/li&gt;
&lt;li&gt;JSP 책의 세션, 쿠키, 필터 관련 챕터 읽기&lt;/li&gt;
&lt;li&gt;객체지향 관련 도서 읽기&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;추가 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://oniwon.tistory.com/64&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Arrays.copyOf가 얕은 복사인 이유 &amp;mdash; 어셈블리까지 내려가기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 Q&amp;amp;A&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트 기획 관련 질문&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벌써&amp;nbsp;이번&amp;nbsp;주를&amp;nbsp;끝으로&amp;nbsp;'자바의&amp;nbsp;신'이&amp;nbsp;마무리됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한&amp;nbsp;달이&amp;nbsp;지났다는&amp;nbsp;게&amp;nbsp;믿기지&amp;nbsp;않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cw3KZi/dJMcaf7HnIb/Xyvy0IMopRbpUrYnLuKxrK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cw3KZi/dJMcaf7HnIb/Xyvy0IMopRbpUrYnLuKxrK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cw3KZi/dJMcaf7HnIb/Xyvy0IMopRbpUrYnLuKxrK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcw3KZi%2FdJMcaf7HnIb%2FXyvy0IMopRbpUrYnLuKxrK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;281&quot; height=&quot;158&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 4주간 자바 공부에 거의 집중하면서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평소 알고 있던 것보다 훨씬 깊이 있게 자바를 들여다볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배운&amp;nbsp;내용을&amp;nbsp;프로젝트에&amp;nbsp;바로&amp;nbsp;적용해&amp;nbsp;볼&amp;nbsp;수&amp;nbsp;있다는&amp;nbsp;점도&amp;nbsp;좋은&amp;nbsp;것&amp;nbsp;같다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGSrjr/dJMcafGGvpg/QpLYIToX9ZObBEfcnA4NKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGSrjr/dJMcafGGvpg/QpLYIToX9ZObBEfcnA4NKK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGSrjr/dJMcafGGvpg/QpLYIToX9ZObBEfcnA4NKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGSrjr%2FdJMcafGGvpg%2FQpLYIToX9ZObBEfcnA4NKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;217&quot; height=&quot;217&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;이번주는 개인 프로젝트도 시작했는데,&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;AI를 어떻게 잘 활용할지도 같이 고민하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음부터&amp;nbsp;기획을&amp;nbsp;너무&amp;nbsp;확실하게&amp;nbsp;짜다&amp;nbsp;보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;(완벽주의 성향이 이럴 땐 오히려 독이 된다고 느꼈다..)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;구현이 불필요하게 복잡해졌다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MVP 위주로 먼저 구현하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;살을 붙여 단계별로 확장해 나가는 게 좋은 방법이라는 걸 깨닫고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 방식으로 다시 구현하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩&lt;span&gt;&amp;nbsp;&lt;/span&gt;관련&lt;span&gt;&amp;nbsp;&lt;/span&gt;질문은&lt;span&gt;&amp;nbsp;&lt;a href=&quot;https://open.kakao.com/o/s7WbPrui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;오픈채팅&lt;/span&gt;&lt;/a&gt;&amp;nbsp;&lt;/span&gt;남겨주시면&lt;span&gt;&amp;nbsp;&lt;/span&gt;제가&lt;span&gt;&amp;nbsp;&lt;/span&gt;아는&lt;span&gt;&amp;nbsp;&lt;/span&gt;선에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;답변드리겠습니다&lt;span&gt;!&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추천인 코드: 커넥터스2385&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>F-Lab</category>
      <category>FLAB</category>
      <category>Flab 3주차 후기</category>
      <category>Flab 멘토링</category>
      <category>Flab 추천인코드</category>
      <category>Flab커넥터스</category>
      <category>Flab후기</category>
      <category>자바 다형성</category>
      <category>자바면접</category>
      <category>자바백엔드</category>
      <category>커넥터스2385</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/65</guid>
      <comments>https://oniwon.tistory.com/65#entry65comment</comments>
      <pubDate>Tue, 19 May 2026 15:50:14 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Arrays.copyOf가 얕은 복사인 이유 &amp;mdash; 어셈블리까지 내려가기</title>
      <link>https://oniwon.tistory.com/64</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;ArrayList가 가득 차면 어떻게 동작할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 배열이 1.5배로 리사이징되면서 `Arrays.copyOf()`로 기존 요소를 복사한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때&amp;nbsp;복사된&amp;nbsp;배열은&amp;nbsp;원본과&amp;nbsp;같은&amp;nbsp;객체를&amp;nbsp;참조한다.&amp;nbsp;얕은&amp;nbsp;복사이기&amp;nbsp;때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그런데 얕은 복사인 걸 어떻게 알 수 있을까?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그걸 확인하기 위해 &lt;b&gt;JDK 소스부터 HotSpot 소스, 그리고 어셈블리 stub&lt;/b&gt;까지 따라가 봤다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전체 호출 체인&lt;/h2&gt;
&lt;pre id=&quot;code_1779169344010&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;  ArrayList.add(E)
    &amp;rarr; ArrayList.grow()
      &amp;rarr; Arrays.copyOf(T[], int)
        &amp;rarr; System.arraycopy(...)              // native 진입점
          &amp;rarr; JVM_ArrayCopy (jvm.cpp)
            &amp;rarr; ObjArrayKlass::copy_array
              &amp;rarr; ArrayAccess::oop_arraycopy
                &amp;rarr; Copy::conjoint_oops_atomic
                  &amp;rarr; pd_conjoint_oops_atomic   // oop* &amp;rarr; jlong* 캐스팅
                    &amp;rarr; _Copy_conjoint_jlongs_atomic  // 어셈블리 stub&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 자바 레이어 - 객체를 새로 만들지 않음&lt;/h2&gt;
&lt;pre id=&quot;code_1779169390651&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static &amp;lt;T,U&amp;gt; T[] copyOf(U[] original, int newLength, Class&amp;lt;? extends T[]&amp;gt; newType) {
  T[] copy = ((Object)newType == (Object)Object[].class)
      ? (T[]) new Object[newLength]
      : (T[]) Array.newInstance(...);
  System.arraycopy(original, 0, copy, 0, Math.min(original.length, newLength));
  return copy;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;new Object[newLength]로 만든 것은&lt;b&gt; null로 채워진 빈 배열&lt;/b&gt;이다. 자바 코드 어디에서도 원소 객체를 새로 생성하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 복사는 native 메서드인 &lt;b&gt;System.arraycopy&lt;/b&gt;로 위임된다.&lt;/p&gt;
&lt;pre id=&quot;code_1779169432406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@IntrinsicCandidate
public static native void arraycopy(Object src, int srcPos,
                                  Object dest, int destPos, int length);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@IntrinsicCandidate 덕분에 핫 코드에서는 JIT가 이 호출을 통째로 어셈블리로 치환한다. 이하 C++ 경로는 &lt;b&gt;인터프리터/콜드 코드 기준&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. HotSpot&amp;nbsp;진입&amp;nbsp;&amp;mdash;&amp;nbsp;배열&amp;nbsp;타입에&amp;nbsp;따른&amp;nbsp;분기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`hotspot/share/prims/jvm.cpp`의 &lt;b&gt;JVM_ArrayCopy&lt;/b&gt;로&amp;nbsp;들어온&amp;nbsp;뒤&amp;nbsp;다음&amp;nbsp;한&amp;nbsp;줄에서&amp;nbsp;가상&amp;nbsp;디스패치가&amp;nbsp;일어난다.&lt;/p&gt;
&lt;pre id=&quot;code_1779169548833&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;arrayOop s = arrayOop(JNIHandles::resolve_non_null(src));
s-&amp;gt;klass()-&amp;gt;copy_array(s, src_pos, d, dst_pos, length, thread);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 &lt;b&gt;s-&amp;gt;klass()는 객체 헤더의 klass 포인터를 따라가&lt;/b&gt; 해당 객체의 메타클래스를 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 모든 객체는 헤더에 자신의 타입을 가리키는 klass 포인터를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;copy_array는 Klass 기반 클래스의 가상 함수이며, 각 하위 클래스가 &lt;b&gt;자기 방식대로 오버라이드&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 가상 디스패치는 객체의 실제 타입에 따라 다른 구현으로 분기된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Object[]로 생성된 배열의 klass는 ObjArrayKlass의 인스턴스 &amp;rarr; ObjArrayKlass::copy_array&lt;/li&gt;
&lt;li&gt;int[]로 생성된 배열의 klass는 TypeArrayKlass의 인스턴스 &amp;rarr; TypeArrayKlass::copy_array&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 인덱스를 메모리 오프셋으로 변환&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObjArrayKlass::copy_array는&lt;b&gt; 인덱스를 바이트 오프셋으로&lt;/b&gt; 바꾼다.&lt;/p&gt;
&lt;pre id=&quot;code_1779169990030&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;size_t src_offset = (size_t) objArrayOopDesc::obj_at_offset&amp;lt;oop&amp;gt;(src_pos);
size_t dst_offset = (size_t) objArrayOopDesc::obj_at_offset&amp;lt;oop&amp;gt;(dst_pos);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;obj_at_offset&amp;lt;oop&amp;gt;&lt;/b&gt;은 &quot;n번째 oop 슬롯의 위치&quot;를 계산한다. 이 시점에서 코드는 복사 대상을 &quot;객체&quot;가 아니라 &quot;oop 슬롯&quot;으로 명시한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;oop(Ordinary Object Pointer)&lt;/b&gt;: HotSpot이 자바 객체를 가리킬 때 쓰는 포인터. 64비트 JVM에서는 8바이트.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. GC&amp;nbsp;배리어&amp;nbsp;&amp;mdash;&amp;nbsp;객체&amp;nbsp;배열&amp;nbsp;경로에만&amp;nbsp;존재&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 배열 복사는 GC 배리어로 감싸여 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1779170168652&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;bool oop_arraycopy_in_heap(...) {
bs-&amp;gt;write_ref_array_pre(dst_raw, length, false);   // pre-barrier
Raw::oop_arraycopy(...);                            // 실제 비트 복사
bs-&amp;gt;write_ref_array((HeapWord*)dst_raw, length);    // post-barrier (카드 마킹)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 단계가 무엇을 하는지 구체적인 예시로 살펴보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;길이 10짜리 String[]을 길이 15짜리 새 배열로 확장하는 상황을 가정한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) Pre-barrier&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dst = [null, null, null, null, null, null, null, null, null, null, ...]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 배열에 덮어쓰는 경우&lt;b&gt; GC에 옛 참조를 알리는 처리&lt;/b&gt;가 일어난다.&lt;br /&gt;이 예시처럼 새로 할당된 빈 배열이라면 옛 참조 자체가 없으므로&amp;nbsp;실질적인&amp;nbsp;동작이&amp;nbsp;없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) 비트&amp;nbsp;복사&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&amp;nbsp;src&amp;nbsp;=&amp;nbsp;[str0,&amp;nbsp;str1,&amp;nbsp;str2,&amp;nbsp;...,&amp;nbsp;str9]&lt;br /&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;darr;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;darr;&lt;br /&gt;&amp;nbsp;&amp;nbsp;dst&amp;nbsp;=&amp;nbsp;[str0,&amp;nbsp;str1,&amp;nbsp;str2,&amp;nbsp;...,&amp;nbsp;str9,&amp;nbsp;null,&amp;nbsp;null,&amp;nbsp;null,&amp;nbsp;null,&amp;nbsp;null]&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;src&amp;nbsp;슬롯의&amp;nbsp;oop&amp;nbsp;값이&amp;nbsp;dst&amp;nbsp;슬롯으로&amp;nbsp;그대로&amp;nbsp;옮겨진다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) Post-barrier&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;G1은 dst의 해당 영역에 대응하는 카드 테이블 엔트리를 &quot;dirty&quot;로 표시한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 GC가 &lt;b&gt;&quot;이 카드 범위에 새 참조가 생겼으니 스캔해야 한다&quot;&lt;/b&gt;는 것을 알게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 post-barrier가 필요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세대별 GC는 minor GC를 빠르게 처리하기 위해 &lt;b&gt;&quot;old gen이 young gen을 가리키는 참조&quot;를 따로 추적&lt;/b&gt;한다. 카드 테이블 마킹이 그 추적의 핵심 메커니즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;old gen에 있는 객체 배열의 슬롯에 young gen의 객체 참조가 새로 들어가는 상황을 떠올려보자.&lt;br /&gt;(이 코드 경로는 모든 객체 배열 복사가 공유하므로 이런 경우도 처리해야 한다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 post-barrier가 없으면 minor GC는 old gen을 스캔하지 않으므로, 그 배열이 young gen의 객체를 참조하고 있다는 사실을 알지 못한다.&lt;b&gt; 그 결과 살아있는 객체가 회수돼버린다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;post-barrier가 카드를 dirty로 마킹해두어야 minor GC가 그 영역을 추가로 스캔해 참조 관계를 발견한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비대칭이 보여주는 것&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원시 타입 배열 복사 경로에는 이 배리어가 없다. 슬롯에 들어있는 값이 주소가 아니라 값 자체이므로 GC가 추적할 새로운 참조 관계가 발생하지 않기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 배열 경로에만&amp;nbsp;배리어가&amp;nbsp;붙는&amp;nbsp;이유는&amp;nbsp;&lt;b&gt;복사가&amp;nbsp;새로운&amp;nbsp;참조를&amp;nbsp;생성하기&amp;nbsp;때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 비대칭 자체가 &quot;객체 배열 복사는 참조값을 다룬다&quot;는 증거이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. &amp;nbsp;oop* &amp;rarr; jlong* 캐스팅&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비트 복사를 따라가면&lt;/b&gt; `hotspot/os_cpu/linux_x86/copy_linux_x86.hpp`(OS+CPU&amp;nbsp;조합별로&amp;nbsp;정의)에 도착한다.&lt;/p&gt;
&lt;pre id=&quot;code_1779170309691&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static void pd_conjoint_oops_atomic(const oop* from, oop* to, size_t count) {
#ifdef AMD64
assert(BytesPerLong == BytesPerOop, &quot;jlongs and oops must be the same size&quot;);
_Copy_conjoint_jlongs_atomic((const jlong*)from, (jlong*)to, count);
#endif
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 두 줄이 의미하는 바는 아래와 같다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;oop* (객체 포인터 배열) &amp;rarr; jlong* (8바이트 정수 배열)로 캐스팅&lt;/li&gt;
&lt;li&gt;이후 호출되는 _Copy_conjoint_jlongs_atomic은 long[] 복사에 쓰이는 바로 그 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐스팅 이후의 코드는 &quot;이 8바이트가 객체 주소인지 단순 정수인지&quot; 구분하지 않는다. 8바이트 단위로 N개를 옮기는 동작만 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 어셈블리&amp;nbsp;stub&amp;nbsp;&amp;mdash;&amp;nbsp;movq&amp;nbsp;두&amp;nbsp;줄&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`_Copy_conjoint_jlongs_atomic`은 일반 C++ 함수가 아니라 JVM 부팅 시 메모리에 동적으로 써넣는&lt;b&gt; 어셈블리 stub&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1779170475320&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;L_copy_8_bytes:
movq rax, [from + qword_count*8 - 8]
movq [to + qword_count*8 - 8], rax
dec  qword_count
jnz  L_copy_8_bytes&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86에는 메모리 간 직접 이동 명령이 없으므로 &lt;b&gt;레지스터(rax)를 거쳐 8바이트를 옮긴다&lt;/b&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 루프가 N번 반복되면서&lt;b&gt; src 슬롯의 oop 값(=객체 주소)을 dst 슬롯에 복사&lt;/b&gt;한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;복사 결과&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복사 전&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1779170528033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src[0] &amp;rarr; 0x1010 &amp;rarr; &quot;hello&quot; 객체 (힙)
dst[0] &amp;rarr; null&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복사 후&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1779170543722&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;src[0] &amp;rarr; 0x1010 ─┐
                 ├──&amp;rarr; &quot;hello&quot; 객체 (힙, 그대로 1개)
dst[0] &amp;rarr; 0x1010 ─┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체 자체는 힙에서 움직이지 않고, dst 슬롯과 src 슬롯이 동일한 주소값을 갖게 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Arrays.copyOf의 복사는 6단계의 호출 체인을 거쳐 어셈블리 movq 두 줄로 귀결된다.&lt;/li&gt;
&lt;li&gt;객체 배열 복사 경로에만 GC 배리어가 붙으며, 이는 복사 대상이 참조값임을 보여준다.&lt;/li&gt;
&lt;li&gt;pd_conjoint_oops_atomic이 oop*를 jlong*로 캐스팅하는 시점부터 코드는 객체 주소를 단순한 8바이트 정수로 취급한다.&lt;/li&gt;
&lt;li&gt;따라서 Arrays.copyOf의 복사는 얕은 복사이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>ArrayList</category>
      <category>Arrays.copyOf</category>
      <category>GC</category>
      <category>Hotspot</category>
      <category>Java</category>
      <category>JVM</category>
      <category>깊은 복사</category>
      <category>얕은 복사</category>
      <category>자바 내부 동작</category>
      <category>자바 면접</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/64</guid>
      <comments>https://oniwon.tistory.com/64#entry64comment</comments>
      <pubDate>Tue, 19 May 2026 15:28:39 +0900</pubDate>
    </item>
    <item>
      <title>[F-Lab] 3주차 정리 및 회고</title>
      <link>https://oniwon.tistory.com/63</link>
      <description>&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;[F-Lab 멘토링 할인 코드]&lt;br /&gt;&lt;b&gt;커넥터스2385&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;사전 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 신 1 10장 ~ 18장&lt;/li&gt;
&lt;li&gt;이력서 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 질문&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다형성이란? 왜 필요한가?&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;상속이 왜 위험한가&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;API란?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;finalize(), clone()&lt;/li&gt;
&lt;li&gt;Interface vs Abstract Class 차이&lt;/li&gt;
&lt;li&gt;try - catch - finally 구문&lt;/li&gt;
&lt;li&gt;Checked Exception vs Unchecked Exception&lt;/li&gt;
&lt;li&gt;Inner class란?&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;과제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RFC 6749&lt;/li&gt;
&lt;li&gt;Inner&lt;span&gt;&amp;nbsp;&lt;/span&gt;class에서 GC를 방해하는 상황&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;추가 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://oniwon.tistory.com/62&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다형성, 컴포지션, DI - 세 개념을 한줄로 잇기&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 Q&amp;amp;A&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이력서 관련 질문&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주 멘토링은 절반은 이력서와 회사 지원 이야기, 절반은 자바 꼬리 질문 위주로 진행됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이력서는 4~5번 정도 수정해서 그런지 큰 수정 사항은 거의 없었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원티드 중심으로 채용 공고를 보고 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5월이라 그런지 생각보다 공고가 많지는 않은 느낌이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;901&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WOfw2/dJMcagyKMkY/BHUOtgphvDdOdaBKIQfh31/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WOfw2/dJMcagyKMkY/BHUOtgphvDdOdaBKIQfh31/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WOfw2/dJMcagyKMkY/BHUOtgphvDdOdaBKIQfh31/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWOfw2%2FdJMcagyKMkY%2FBHUOtgphvDdOdaBKIQfh31%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;299&quot; height=&quot;224&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;901&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인 프로젝트도 구상하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전부터 앱스토어에 내 앱 하나 출시해보는 게 목표였는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 끝까지 만들어서 실제로 출시까지 하고 싶다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 깊게 공부하고 있는 것과 별개로 언제 취업할 수 있을지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;걱정과 불안이 공존하는 시기인 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조급해지기보다는 컨디션 관리 잘하면서&lt;br /&gt;지치지 않고 꾸준히 가야겠다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;236&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9pUSe/dJMcahxHkDK/4SztY9tbSBaDHSg8fk2w11/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9pUSe/dJMcahxHkDK/4SztY9tbSBaDHSg8fk2w11/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9pUSe/dJMcahxHkDK/4SztY9tbSBaDHSg8fk2w11/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9pUSe%2FdJMcahxHkDK%2F4SztY9tbSBaDHSg8fk2w11%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;236&quot; height=&quot;236&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;236&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩 관련 질문은 &lt;a href=&quot;https://open.kakao.com/o/s7WbPrui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;오픈채팅&lt;/a&gt; 남겨주시면 제가 아는 선에서 답변드리겠습니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추천인 코드: 커넥터스2385&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>F-Lab</category>
      <category>FLAB</category>
      <category>Flab 3주차 후기</category>
      <category>Flab 멘토링</category>
      <category>Flab 추천인코드</category>
      <category>Flab커넥터스</category>
      <category>Flab후기</category>
      <category>자바 다형성</category>
      <category>자바면접</category>
      <category>자바백엔드</category>
      <category>커넥터스2385</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/63</guid>
      <comments>https://oniwon.tistory.com/63#entry63comment</comments>
      <pubDate>Sun, 17 May 2026 12:54:57 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 다형성, 컴포지션, DI - 세 개념을 한 줄로 잇기</title>
      <link>https://oniwon.tistory.com/62</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;다형성을 학습하다가 &lt;span&gt;Effective Java&lt;/span&gt;의 &lt;b&gt;&quot;상속보다는 컴포지션을 사용하라&quot;&lt;/b&gt;는 문장이 떠올랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 과정에서 implements와 컴포지션의 관계가 머릿속에서 명확히 정리되지 않아 개념을 다시 정리해본다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다형성이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다형성(polymorphism)은 같은 &lt;b&gt;메소드 호출이 실제 객체에 따라 다르게 동작하는 성질&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778945207771&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Payment payment = new KakaoPayment();
payment.pay(1000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 변수 타입은 Payment&lt;span&gt;지만, 실제 실행되는 것은 &lt;/span&gt;KakaoPayment.pay()&lt;span&gt;다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 다형성의 핵심은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;부모 타입(인터페이스/상위 클래스)으로 객체를 다룰 수 있고&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;실제 어떤 구현이 실행될지는 &lt;b&gt;런타임에 결정&lt;/b&gt;된다는 점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 필요한가 - 결제 예시&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다형성이 없는 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778947617510&quot; class=&quot;fsharp&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class OrderService {
  public void checkout(String type, int amount) {
      if (type.equals(&quot;kakao&quot;)) {
          new KakaoPayment().pay(amount);
      } else if (type.equals(&quot;naver&quot;)) {
          new NaverPayment().pay(amount);
      }
      // 결제 수단이 추가될 때마다 분기 추가
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;결제 수단이 늘어날 때마다 &lt;/span&gt;&lt;b&gt;클라이언트 코드를 매번 수정&lt;/b&gt;해야 한다. if / switch가 코드 곳곳에 박힌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다형성을 적용한 뒤&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778945334387&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderService {
  private final Payment payment;

  public OrderService(Payment payment) {
      this.payment = payment;
  }

  public void checkout(int amount) {
      payment.pay(amount); // 카카오든 네이버든 신경 안 씀
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 Payment라는 &lt;b&gt;추상 타입만 의존&lt;/b&gt;한다. OrderService는 내부 구현체를 알 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추상화와의 관계&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 Payment 인터페이스에만 의존한다.&lt;/li&gt;
&lt;li&gt;구체적인 구현은 인터페이스 뒤로 &lt;b&gt;캡슐화&lt;/b&gt;된다.&lt;/li&gt;
&lt;li&gt;각 구현체의 내부 동작 방식은 서로 달라도 무방하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는&amp;nbsp;구현&amp;nbsp;방식과&amp;nbsp;무관하게&amp;nbsp;payment.pay()만&amp;nbsp;호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;런타임에 결정된다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다형성의&amp;nbsp;핵심은&lt;b&gt;&amp;nbsp;어떤&amp;nbsp;구현체가&amp;nbsp;실행될지&amp;nbsp;&lt;/b&gt;컴파일&amp;nbsp;타임이&amp;nbsp;아니라&amp;nbsp;런타임에&amp;nbsp;결정된다는&amp;nbsp;점이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778945739486&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Payment payment = getPayment(userChoice);
payment.pay(1000);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;payment의 타입은 Payment로 고정돼 있지만, 실제 호출되는 객체는 런타임에 들어온 값에 따라 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;KakaoPayment.pay()&lt;/li&gt;
&lt;li&gt;NaverPayment.pay()&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 &lt;b&gt;동적 바인딩(dynamic binding)&lt;/b&gt;이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;implements&amp;nbsp;/&amp;nbsp;컴포지션&amp;nbsp;/&amp;nbsp;DI&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 던졌던 질문으로 돌아가면,&lt;b&gt;&amp;nbsp;implements와 컴포지션은 함께 쓰이는 개념&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 인터페이스&amp;nbsp;구현&amp;nbsp;(implements)&amp;nbsp;&amp;mdash;&amp;nbsp;타입&amp;nbsp;공유&lt;/h3&gt;
&lt;pre id=&quot;code_1778946753368&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Payment {
  void pay(int amount);
}

public class KakaoPayment implements Payment { ... }
public class NaverPayment implements Payment { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;KakaoPayment와 NaverPayment는 Payment라는 타입을 공유한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념적으로 implements도 &lt;b&gt;넓은 의미의 상속&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 컴포지션 (Composition) &amp;mdash; 객체를 보유&lt;/h3&gt;
&lt;pre id=&quot;code_1778946897373&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class OrderService {
  private final Payment payment;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService는 Payment를 상속하지 않고 객체를 필드로 가진다. is-a가 아닌 has-a 관계다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. DI (의존성 주입) &amp;mdash; 외부에서 주입&lt;/h3&gt;
&lt;pre id=&quot;code_1778946928510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class OrderService {
  private final Payment payment;

  public OrderService(Payment payment) {
      this.payment = payment;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OrderService가 직접 객체를 생성하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 구현체를 넣을지는 &lt;b&gt;외부(스프링 컨테이너)가 결정&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개의 구현체가 동시에 존재한다면 &lt;b&gt;전략 패턴(Strategy Pattern)&lt;/b&gt;을 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인터페이스에 supports() 같은 메소드를 두고, 컨테이너가 모든 구현체를 List로 주입해주면, 런타임에 조건에 맞는 구현체를 골라서 호출한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778949401510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface Payment {

    boolean supports(PaymentType type);

    void pay(int amount);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1778947710085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
@RequiredArgsConstructor
public class OrderService {
  private final List&amp;lt;Payment&amp;gt; payments; // 모든 구현체가 주입됨

  public void checkout(PaymentType type, int amount) {
      payments.stream()
          .filter(p -&amp;gt; p.supports(type))
          .findFirst()
          .orElseThrow()
          .pay(amount);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새&amp;nbsp;결제&amp;nbsp;수단이&amp;nbsp;추가돼도&amp;nbsp;@Service만&amp;nbsp;붙이면&amp;nbsp;자동으로&amp;nbsp;List에&amp;nbsp;포함된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&quot;상속보다&amp;nbsp;컴포지션&quot;의&amp;nbsp;의미&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 이 문장을 봤을 때 의문이 들었다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;implements도 상속의 일종인데, 그러면 implements도 쓰지 말라는 뜻일까?&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 말하는 &amp;ldquo;상속&amp;rdquo;은 대부분 클래스 상속(extends)을 의미한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;인터페이스 상속(implements)&lt;/b&gt; &amp;mdash; 타입 상속이라 권장된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;클래스 상속(extends)&lt;/b&gt; &amp;mdash; 구현 상속이라 강한 결합 발생 가능성이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적인 사례가 &lt;b&gt;Stack&lt;/b&gt;이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Vector를&amp;nbsp;extends한&amp;nbsp;Stack&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바&amp;nbsp;표준&amp;nbsp;라이브러리의&lt;b&gt;&amp;nbsp;Stack은&amp;nbsp;Vector를&amp;nbsp;상속&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778948214199&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Stack&amp;lt;E&amp;gt; extends Vector&amp;lt;E&amp;gt; { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것의 단점이 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. &amp;nbsp;의도하지&amp;nbsp;않은&amp;nbsp;동작까지&amp;nbsp;상속됨&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vector의 모든 메소드는 &lt;b&gt;synchronized&lt;/b&gt;로 선언되어 있다. thread-safe를 보장하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 Stack이 Vector를 extends하는 순간, &lt;b&gt;Stack도 자동으로 모든 메소드가 synchronized&lt;/b&gt;가 된다는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로 정의한 push(), pop()도 synchronized&lt;/li&gt;
&lt;li&gt;부모(Vector)에서 내려온 get(), size()도 synchronized&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack을 쓸 때 락이 필요 없는 상황에서도 &lt;b&gt;무조건 모니터 락&lt;/b&gt;이 걸린다.&lt;/p&gt;
&lt;pre id=&quot;code_1778948295386&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void doSomething() {
  Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;(); // 지역 변수, 한 스레드만 접근
  stack.push(1);
  stack.pop();
  // 메소드 끝나면 사라짐 &amp;mdash; 동기화가 전혀 필요 없는데도 락 오버헤드 발생
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2.&amp;nbsp;추상화가&amp;nbsp;깨진다&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack은 LIFO(Last-In-First-Out)라는 약속을 가진 자료구조다.&lt;br /&gt;그런데 Vector를 상속하면서 Vector의 임의 위치 접근 메소드까지 그대로 노출된다.&lt;/p&gt;
&lt;pre id=&quot;code_1778948344936&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Stack&amp;lt;Integer&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
stack.push(1);
stack.push(2);
stack.push(3);

stack.remove(0);          // 가장 아래(1)를 제거 &amp;mdash; LIFO 위반
stack.insertElementAt(99, 0); // 중간에 끼워넣기 &amp;mdash; LIFO 위반
stack.get(1);             // 임의 위치 조회 &amp;mdash; LIFO 위반&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식이 원하지 않아도 부모의 모든 public 메소드는 그대로 자식의 API가 되는 단점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이&amp;nbsp;&quot;상속보다&amp;nbsp;컴포지션&quot;이라는&amp;nbsp;말이&amp;nbsp;나온&amp;nbsp;이유다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다형성은 같은 호출이 객체에 따라 다르게 동작하는 성질이다.&lt;/li&gt;
&lt;li&gt;보통 인터페이스(implements)로 공통 타입을 만들고, 클라이언트는 구현체 대신 추상 타입에 의존한다.&lt;/li&gt;
&lt;li&gt;컴포지션은 객체를 상속(extends)하지 않고 필드로 조합하는 방식이다.&lt;/li&gt;
&lt;li&gt;DI는 객체 생성 책임을 외부로 분리하는 것이다.&lt;/li&gt;
&lt;li&gt;&quot;상속보다 컴포지션&quot;은 extends를 두고 한 말이다. implements는 타입만 공유하지만, extends는 부모의 API와 동작까지 전부 따라오기 때문이다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>DI</category>
      <category>OOP</category>
      <category>객체지향</category>
      <category>다형성</category>
      <category>상속</category>
      <category>이펙티브자바</category>
      <category>인터페이스</category>
      <category>자바</category>
      <category>전략패턴</category>
      <category>컴포지션</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/62</guid>
      <comments>https://oniwon.tistory.com/62#entry62comment</comments>
      <pubDate>Sun, 17 May 2026 01:42:08 +0900</pubDate>
    </item>
    <item>
      <title>[F-Lab] 2주차 정리 및 회고</title>
      <link>https://oniwon.tistory.com/61</link>
      <description>&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;[F-Lab 멘토링 할인 코드]&lt;br /&gt;&lt;b&gt;커넥터스2385&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;사전 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 신2 9장까지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 질문&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;enum&lt;/li&gt;
&lt;li&gt;블로킹, 논블로킹 / 동기, 비동기 4가지 조합&amp;nbsp;&lt;/li&gt;
&lt;li&gt;toString() 호출 결과&lt;/li&gt;
&lt;li&gt;자바 생성자 특징&lt;/li&gt;
&lt;li&gt;jpa에서 protected 이상의 기본 생성자가 필요한 이유&lt;/li&gt;
&lt;li&gt;static 사용 시 주의점&lt;/li&gt;
&lt;li&gt;자바에서 NullPointerException 발생 이유&lt;/li&gt;
&lt;li&gt;익명 클래스의 변수 캡처&lt;/li&gt;
&lt;li&gt;자바 패키지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;과제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이력서 작성&lt;/li&gt;
&lt;li&gt;익명 클래스에 전달한 값은 어떤 복사인가&lt;/li&gt;
&lt;li&gt;패키지와 네임스페이스&lt;/li&gt;
&lt;li&gt;패키지 이름으로 금지된 예약어&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;추가 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://oniwon.tistory.com/60&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;enum - 왜 안전한가, private 생성자로 강등되는 이유&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;멘토링 Q&amp;amp;A&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;static 변수 저장 위치&lt;/li&gt;
&lt;li&gt;&lt;span data-token-index=&quot;0&quot;&gt;해시맵 버킷 계산 시 비트 연산 원리&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 주제를 깊게 파고들다 보니 다른 주제에 할애할 시간이 부족하다고 느낄 때가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학습량을 골고루 배분하는 방식으로 균형을 잘 잡아야겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;별개라고 생각했던 개념들이 하나로 이어질 때 흥미롭다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바가 pass by value라는 것은 알고 있었지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익명 클래스가 캡처한 값이 같은 원리라는 것은 모르고 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;195&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdSIdI/dJMcadWjgV1/6fGnsI7XKpqc5iqywAe0P0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdSIdI/dJMcadWjgV1/6fGnsI7XKpqc5iqywAe0P0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdSIdI/dJMcadWjgV1/6fGnsI7XKpqc5iqywAe0P0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdSIdI%2FdJMcadWjgV1%2F6fGnsI7XKpqc5iqywAe0P0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;195&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;195&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;녹음본 스크립트를 다시 보면서 답변할 때의 습관도 알게 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 질문에 질문으로 되묻는 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의도적으로라도 내 생각을 짧게 먼저 말하고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필요한 부분만 되묻는 방식으로 바꿔봐야겠다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩 관련 질문은 &lt;a href=&quot;https://open.kakao.com/o/s7WbPrui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;오픈채팅&lt;/a&gt; 남겨주시면 제가 아는 선에서 답변드리겠습니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추천인 코드: 커넥터스2385&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>F-Lab</category>
      <category>FLAB</category>
      <category>Flab2주차</category>
      <category>Flab멘토링</category>
      <category>Flab추천인코드</category>
      <category>Flab커넥터스</category>
      <category>Flab후기</category>
      <category>에프랩멘토링</category>
      <category>자바면접</category>
      <category>자바백엔드</category>
      <category>커넥터스2385</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/61</guid>
      <comments>https://oniwon.tistory.com/61#entry61comment</comments>
      <pubDate>Fri, 15 May 2026 20:22:33 +0900</pubDate>
    </item>
    <item>
      <title>[Java] enum - 왜 안전한가, private 생성자로 강등되는 이유</title>
      <link>https://oniwon.tistory.com/60</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;enum이란&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 &lt;b&gt;상수(변하지 않는 고정된 값)의 집합&lt;/b&gt;을 표현하는 열거형 클래스다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수를 단순히 `public static final int`로 정의하지 않고 enum으로 정의하면, &lt;b&gt;컴파일러&lt;/b&gt;가 여러 안전장치를 자동으로 끼워준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 안전장치들이 어떻게 만들어지는지, 왜 enum이 &quot;싱글톤처럼 동작한다&quot;고 불리는지 정리해봤다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;enum은 왜 안전한가&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 타입 체크 가능&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상수를 int나 String으로 관리하면, 메서드 파라미터로 정해진 상수 외의 다른 값이 넘어와도 컴파일러가 막지 못해 런타임에 가서야 터진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;반면 반면&lt;b&gt; enum은 타입 자체가 정해져 있어&lt;span&gt;&amp;nbsp;&lt;/span&gt;잘못된 값이 넘어오는 순간 컴파일 에러로 막힌다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778768460275&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;enum Status {
    WAITING, PROCESSING, DONE
}

void process(Status status) { ... }

process(999);            // 컴파일 에러
process(&quot;WAITING&quot;);      // 컴파일 에러
process(Status.WAITING); // 정상&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. new로 생성 불가 / 정해진 값만 존재 / 반드시 먼저 초기화&lt;/h3&gt;
&lt;pre id=&quot;code_1778769896865&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 개발자가 작성
enum Status {
    WAITING, PROCESSING, DONE
}

// 컴파일러가 변환
static {
    WAITING = new Status(&quot;WAITING&quot;, 0);
    PROCESSING = new Status(&quot;PROCESSING&quot;, 1);
    DONE = new Status(&quot;DONE&quot;, 2);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 정리하면 다음과 같다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;컴파일러가 private 생성자 자동 생성&lt;br /&gt;&amp;darr; &lt;br /&gt;외부에서 new Status() 불가&lt;br /&gt;&amp;darr; &lt;br /&gt;컴파일러만 static {} 블록에서 new로 생성&lt;br /&gt;&amp;darr; &lt;br /&gt;클래스 로딩 시점에 딱 1번 미리 생성&lt;br /&gt;&amp;darr; &lt;br /&gt;정해진 상수만 존재, 반드시 먼저 초기화됨&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;의문: Oracle 문서는 두 가지 생성자를 허용하는데, 실제는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Oracle 공식 튜토리얼 문서에는&lt;b&gt; enum 생성자&lt;/b&gt; 관련하여 다음과 같이 적혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(&lt;a title=&quot;Oracle Java Tutorial - Enum Types&quot; href=&quot;https://docs.oracle.com/javase/tutorial/java/javaOO/enum.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Oracle Java Tutorial - Enum Types&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JGUEq/dJMcaiwx4GK/yeymEF6QvklyiAMzx0GqL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JGUEq/dJMcaiwx4GK/yeymEF6QvklyiAMzx0GqL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JGUEq/dJMcaiwx4GK/yeymEF6QvklyiAMzx0GqL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJGUEq%2FdJMcaiwx4GK%2FyeymEF6QvklyiAMzx0GqL1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;802&quot; height=&quot;55&quot; data-origin-width=&quot;1756&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;enum 타입의 생성자는 반드시 package-private 또는 private 접근 제어자를 가져야 한다. 이 생성자는 enum 본문 시작 부분에 정의된 상수들을 자동으로 생성한다. 개발자가 직접 enum 생성자를 호출할 수는 없다.&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 package-private과 private 둘 다 가능한 줄 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 실제로 생성자를 따로 작성하지 않은 enum 코드를 컴파일하고 javap -p -c로 바이트코드를 확인해보니, 결과가 달랐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;확인에 사용된 코드&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778770041495&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Status {
    WAITING, PROCESSING, DONE;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;javap -p -c Status.class 결과 중 일부&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778770064686&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public final class Status extends java.lang.Enum&amp;lt;Status&amp;gt; {
  public static final Status WAITING;
  public static final Status PROCESSING;
  public static final Status DONE;
  ...
  private Status();        // &amp;larr; 소스에 안 적었는데도 private 생성자가 자동 생성됨
    Code:
       0: aload_0
       1: aload_1
       2: iload_2
       3: invokespecial #28  // Method java/lang/Enum.&quot;&amp;lt;init&amp;gt;&quot;:(Ljava/lang/String;I)V
       6: return

  static {};               // &amp;larr; static 블록에서 상수들을 new로 생성
    Code:
       0: new           #1  // class Status
       3: dup
       4: ldc           #32 // String WAITING
       6: iconst_0
       7: invokespecial #33 // Method &quot;&amp;lt;init&amp;gt;&quot;:(Ljava/lang/String;I)V
      10: putstatic     #3  // Field WAITING:LStatus;
      ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자를 아예 선언하지 않았는데도 `private Status();`가 박혀 있다. 분명히 default(package-private)도 허용된다고 했는데, &lt;b&gt;결과는 private&lt;/b&gt;이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JLS 8.9.2 확인&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JLS(Java Language Specification) 8.9.2&lt;/b&gt;를 보면 답을 찾을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;(&lt;a title=&quot;JLS 8.9.2 Enum Body Declarations&quot; href=&quot;https://docs.oracle.com/javase/specs/jls/se17/html/jls-8.html#jls-8.9.2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JLS 8.9.2 Enum Body Declarations&lt;/a&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bulTvp/dJMcacXpmav/NBUL1iR6LE6N1a1LLM265K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bulTvp/dJMcacXpmav/NBUL1iR6LE6N1a1LLM265K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bulTvp/dJMcacXpmav/NBUL1iR6LE6N1a1LLM265K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbulTvp%2FdJMcacXpmav%2FNBUL1iR6LE6N1a1LLM265K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;619&quot; height=&quot;84&quot; data-origin-width=&quot;1434&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&quot;enum 선언 안에서 접근 제어자를 생략한 생성자는 (default가 아니라) private으로 해석된다.&quot;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 소스 코드에서 접근 제어자를 생략해서 작성하면 일반 클래스와 달리 &lt;b&gt;package-private이 아니라 private으로 해석된다&lt;/b&gt;고 명세에 적혀 있다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그럼 왜 생성자를 private으로 강등시킬까?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;JVM 차원에서 enum 인스턴스의 유일성을 보장&lt;/b&gt;하기 위해서다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고&amp;nbsp;이&amp;nbsp;유일성&amp;nbsp;보장이&amp;nbsp;곧&amp;nbsp;enum이&lt;b&gt;&amp;nbsp;&quot;싱글톤처럼&amp;nbsp;동작한다&quot;&amp;nbsp;&lt;/b&gt;고&amp;nbsp;불리는&amp;nbsp;이유다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;싱글톤은&amp;nbsp;&lt;b&gt;하나의&amp;nbsp;JVM(프로세스)&amp;nbsp;안에서&amp;nbsp;인스턴스가&amp;nbsp;딱&amp;nbsp;1개만&amp;nbsp;존재하는&amp;nbsp;것&lt;/b&gt;이다.&amp;nbsp;그&amp;nbsp;조건은&amp;nbsp;두&amp;nbsp;가지다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 단 하나의 인스턴스가 만들어진다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스 로딩 시점에 static {} 블록에서 1회 생성&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 그 외 어떤 경로로도 추가 인스턴스가 만들어질 수 없다.&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;직접 new 호출 &amp;rarr;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;private 생성자&lt;/b&gt;로 차단&lt;/li&gt;
&lt;li&gt;그&amp;nbsp;외&amp;nbsp;우회&amp;nbsp;경로(리플렉션,&amp;nbsp;역직렬화,&amp;nbsp;clone)&amp;nbsp;&amp;rarr;&amp;nbsp;JVM&amp;nbsp;차원에서&amp;nbsp;별도&amp;nbsp;차단&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 중 첫 번째 통로인 직접 new 호출을 막는 장치가 바로&lt;b&gt; private 생성자&lt;/b&gt;다. 만약 enum 생성자가 package-private이면, 같은 패키지 안의 다른 클래스가 그냥 new Status(...)로 새 인스턴스를 만들어버릴 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 enum 생성자의 접근 제어자를 private 또는 생략(= 암묵적 private) 으로만 허용한다. 개발자가 public이나 protected로 선언하면 컴파일 에러가 난다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;equals()가 아니라 ==로 비교해도 true가 나온다.&amp;nbsp;&lt;b&gt;같은&amp;nbsp;주소를&amp;nbsp;가리키는&amp;nbsp;같은&amp;nbsp;객체&lt;/b&gt;이기 때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778826687831&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Status.WAITING == Status.WAITING // 항상 true&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;직접 구현한 싱글톤은 리플렉션, 역직렬화, clone() 같은 우회로로 뚫릴 수 있지만, enum은 이 모든 경로까지 막혀 있다. 그 차단 방식은 아래와 같다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;enum 인스턴스의 유일성은 어떻게 보장될까&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;private&amp;nbsp;생성자만으로는&amp;nbsp;부족하다.&amp;nbsp;&lt;b&gt;자바에는&amp;nbsp;생성자를&amp;nbsp;우회해서&amp;nbsp;객체를&amp;nbsp;만들&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;경로&lt;/b&gt;가 더 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum은 이 경로들도 전부 막혀 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 같은 패키지 내에서의 직접 호출&lt;/b&gt;&lt;br /&gt;생성자가 package-private이면 같은 패키지의 다른 클래스가 그냥 호출할 수 있다. private이면 같은 패키지여도 호출 불가다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;2. 리플렉션을 통한 호출&lt;/b&gt;&lt;br /&gt;Constructor.newInstance()로 우회 생성을 시도할 수 있다. JVM은 enum 타입에 대해 이 호출 자체를 명시적으로 차단한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Constructor.newInstance()가 enum 타입이면&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;IllegalArgumentException&lt;/b&gt;(&quot;Cannot reflectively create enum objects&quot;)을 던지도록 구현되어 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&lt;a style=&quot;color: #0070d1;&quot; href=&quot;https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/reflect/Constructor.java&quot;&gt;(&lt;/a&gt;&lt;a href=&quot;https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/reflect/Constructor.java)&quot;&gt;https://github.com/openjdk/jdk/blob/master/src/java.base/share/classes/java/lang/reflect/Constructor.java)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cyvhXR/dJMcafmktzN/AxVpQtKwFzzqK8xik8KeFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cyvhXR/dJMcafmktzN/AxVpQtKwFzzqK8xik8KeFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cyvhXR/dJMcafmktzN/AxVpQtKwFzzqK8xik8KeFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcyvhXR%2FdJMcafmktzN%2FAxVpQtKwFzzqK8xik8KeFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;656&quot; height=&quot;48&quot; data-origin-width=&quot;1226&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;3. 직렬화/역직렬화&lt;/b&gt;&lt;br /&gt;일반 클래스는 역직렬화 시 생성자를 거치지 않고 객체가 만들어질 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 enum은 직렬화 메커니즘에서 특별 취급되어, 역직렬화 시 `Enum.valueOf()`로만 인스턴스를 가져오도록 강제되어 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&lt;a title=&quot;&amp;nbsp;(https://docs.oracle.com/en/java/javase/21/docs/specs/serialization/serial-arch.html#serialization-of-enum-constants)&quot; href=&quot;https://docs.oracle.com/en/java/javase/21/docs/specs/serialization/serial-arch.html#serialization-of-enum-constants&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;(https://docs.oracle.com/en/java/javase/21/docs/specs/serialization/serial-arch.html#serialization-of-enum-constants)&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;476&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAZFCj/dJMcaiwx5vt/ofHStNTvJ9F4pnHQELTCdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAZFCj/dJMcaiwx5vt/ofHStNTvJ9F4pnHQELTCdk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAZFCj/dJMcaiwx5vt/ofHStNTvJ9F4pnHQELTCdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAZFCj%2FdJMcaiwx5vt%2FofHStNTvJ9F4pnHQELTCdk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;876&quot; height=&quot;170&quot; data-origin-width=&quot;2452&quot; data-origin-height=&quot;476&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote style=&quot;color: #666666; text-align: left;&quot; data-ke-style=&quot;style2&quot;&gt;&quot;enum 상수를 역직렬화할 때, ObjectInputStream은 스트림에서 상수의 이름만 읽어온다. 그리고 그 enum 타입과 이름을 Enum.valueOf()에 넘겨서, 이미 존재하는 상수를 찾아오는 방식으로 복원한다.&quot;&lt;/blockquote&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4.&amp;nbsp;clone()&lt;/b&gt;&lt;br /&gt;일반 객체는 clone()으로 똑같은 인스턴스를 하나 더 만들 수 있다. Enum.clone()은 final로 선언되어 있고 호출 시&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;CloneNotSupportedException&lt;/b&gt;을 던지도록 되어 있어, 오버라이드 자체가 불가능하다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;JVM&amp;nbsp;메모리&amp;nbsp;구조&amp;nbsp;관점에서&amp;nbsp;(static&amp;nbsp;관련)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;enum&amp;nbsp;상수가&amp;nbsp;JVM&amp;nbsp;안에&amp;nbsp;왜&amp;nbsp;딱&amp;nbsp;1개밖에&amp;nbsp;존재할&amp;nbsp;수&amp;nbsp;없는지는&amp;nbsp;메모리&amp;nbsp;구조를&amp;nbsp;보면&amp;nbsp;명확해진다.&lt;/p&gt;
&lt;pre id=&quot;code_1778773765264&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ClassLoader
└── Class 객체 (Status.class) &amp;mdash; 힙에 존재
      ├── static final WAITING    &amp;rarr; Status 인스턴스 (힙)
      ├── static final PROCESSING &amp;rarr; Status 인스턴스 (힙)
      └── static final DONE       &amp;rarr; Status 인스턴스 (힙)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;static 블록&lt;/b&gt;은 해당&amp;nbsp;enum&amp;nbsp;클래스가&amp;nbsp;처음&amp;nbsp;사용되어&amp;nbsp;로딩되는&amp;nbsp;시점에&amp;nbsp;딱&amp;nbsp;한&amp;nbsp;번&amp;nbsp;실행된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Status.class 객체 안&lt;/b&gt;에 WAITING, PROCESSING, DONE의 참조값이 저장된다.&lt;/li&gt;
&lt;li&gt;각 Status 인스턴스 객체는 힙의 별도 공간에 존재하고, Class 객체가 그것을 참조하고 있는 구조다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Class 객체는 ClassLoader가 살아있는 한 함께 존재하기 때문에, enum 상수는 사실상 프로세스 종료 전까지 GC 대상이 되지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, enum 상수는 클래스가 처음 로딩된 순간부터 프로세스 종료 시점까지 같은 주소에 살아있는 객체다.&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;enum은 상수의 집합을 표현하는 열거형 클래스이다.&lt;/li&gt;
&lt;li&gt;타입 체크가 컴파일 시점에 이루어져, 잘못된 값이 들어갈 수 없다.&lt;/li&gt;
&lt;li&gt;enum의 생성자를 private으로 강등시키는 이유는, JVM 차원에서 enum 인스턴스 &lt;b&gt;유일성 보장 체계&lt;/b&gt;를 작동시키기 위해서이다.&lt;/li&gt;
&lt;li&gt;Class 객체가 ClassLoader 생명주기와 함께 가기 때문에, enum 상수는 프로세스 종료 전까지 GC되지 않고 같은 주소를 유지한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>enum</category>
      <category>Java</category>
      <category>jls</category>
      <category>JVM</category>
      <category>private 생성자</category>
      <category>static</category>
      <category>싱글톤</category>
      <category>에프랩</category>
      <category>자바Enum</category>
      <category>자바면접</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/60</guid>
      <comments>https://oniwon.tistory.com/60#entry60comment</comments>
      <pubDate>Thu, 14 May 2026 23:57:14 +0900</pubDate>
    </item>
    <item>
      <title>[F-Lab] 1주차 정리 및 회고</title>
      <link>https://oniwon.tistory.com/59</link>
      <description>&lt;blockquote data-ke-style=&quot;style3&quot;&gt;[F-Lab 멘토링 할인 코드]&lt;br /&gt;&lt;b&gt;커넥터스2385&lt;/b&gt;&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사전 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바의 신 1 전체&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멘토링 질문&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;blocking, non-blocking / synchronous, asynchronous&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;synchronized 란? 어디에 사용하면 좋을까?&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;상태의 두 가지 의미&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;동시성 제어 수단(자바 락, DB 락)&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;a = b, a == b, .equals()&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;자바 키워드 final, &lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;transient&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;리터럴 String vs 힙의 new String&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;equals() &amp;amp; hashCode()&lt;/li&gt;
&lt;li&gt;연산자 종류&lt;/li&gt;
&lt;li&gt;배열 초기화 방식(프리미티브, 참조형)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;과제&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;equals() 테스트 코드 짜는 법&lt;/li&gt;
&lt;li&gt;hashCode()가 무엇이고(31의 의미) 어디에서 사용하는지&lt;/li&gt;
&lt;li&gt;Floating Point 오차 발생 이유&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가 학습&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://oniwon.tistory.com/57&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;synchronized 동작 원리 - 모니터 락, CAS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://oniwon.tistory.com/58&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;equals()&amp;nbsp;&amp;amp;&amp;nbsp;hashCode()&amp;nbsp;깊게&amp;nbsp;보기&amp;nbsp;-&amp;nbsp;31의&amp;nbsp;의미,&amp;nbsp;String&amp;nbsp;내부&amp;nbsp;동작&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;멘토링 Q&amp;amp;A&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized&amp;nbsp;가&amp;nbsp;실무에서&amp;nbsp;잘&amp;nbsp;안&amp;nbsp;쓰이는&amp;nbsp;이유&lt;/li&gt;
&lt;li&gt;hashCode()&amp;nbsp;명세는&amp;nbsp;어디까지&amp;nbsp;정의되어&amp;nbsp;있는가&lt;/li&gt;
&lt;li&gt;enum&amp;nbsp;생성자가&amp;nbsp;package-private&amp;nbsp;/&amp;nbsp;private&amp;nbsp;둘&amp;nbsp;다&amp;nbsp;허용임에도&amp;nbsp;컴파일&amp;nbsp;후&amp;nbsp;private&amp;nbsp;로&amp;nbsp;변하는&amp;nbsp;이유&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회고&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 날이라 역시나 긴장되고 떨렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바의 신 1 목차 순서대로 질문을 받을 줄 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 멘토님께서 어려웠던 개념이 있었는지 물으셨고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로킹/논블로킹과&amp;nbsp;동기/비동기&amp;nbsp;개념이&amp;nbsp;헷갈렸다고 대답했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로킹과 논블로킹의 차이를 설명했는데, synchronous, asynchronous의 개념도 더해야된다고 하셔서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 용어는 처음 접해봤다고 답변했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이후 찾아보니 synchronous/asynchronous가 동기/비동기라는 것을 알게 되었다..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;짱구_놀라는_짤.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dCCvs9/dJMcaf0Qtta/X4WhBuyxptuoHgrjnlsIN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dCCvs9/dJMcaf0Qtta/X4WhBuyxptuoHgrjnlsIN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dCCvs9/dJMcaf0Qtta/X4WhBuyxptuoHgrjnlsIN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdCCvs9%2FdJMcaf0Qtta%2FX4WhBuyxptuoHgrjnlsIN0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;423&quot; height=&quot;238&quot; data-filename=&quot;짱구_놀라는_짤.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개념을 정확히 알아야 정확히 설명할 수 있다는 것을 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 알고 있다고 생각했던 개념도 막상 정의를 말하려니 기억이 안나거나&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층적으로 헷갈리는 개념들이 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 자바 락과 DB 락의 동기화 영향을 구분하는 것, 원자성과 스레드 세이프를 구분하는 것 등&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;혼란짤.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;344&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WY5uk/dJMcag6yaBy/qWPnXIiKF4xdvFayhLlKd1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WY5uk/dJMcag6yaBy/qWPnXIiKF4xdvFayhLlKd1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WY5uk/dJMcag6yaBy/qWPnXIiKF4xdvFayhLlKd1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWY5uk%2FdJMcag6yaBy%2FqWPnXIiKF4xdvFayhLlKd1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;312&quot; height=&quot;268&quot; data-filename=&quot;혼란짤.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;344&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;질문 주제가 자유롭게 오갔지만, 오히려 다양하게 생각해볼 수 있어서 좋았던 시간이었다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에프랩 관련 질문은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://open.kakao.com/o/s7WbPrui&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;span&gt;오&lt;/span&gt;&lt;span&gt;픈채팅&lt;/span&gt;&lt;/a&gt;&amp;nbsp;남겨주시면 제가 아는 선에서 답변드리겠습니다!&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;추천인 코드: 커넥터스2385&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>F-Lab</category>
      <category>F-Lab</category>
      <category>FLAB</category>
      <category>Flab멘토링</category>
      <category>Flab추천인코드</category>
      <category>Flab커넥터스</category>
      <category>Flab후기</category>
      <category>에프랩</category>
      <category>에프랩멘토링</category>
      <category>자바면접</category>
      <category>커넥터스2385</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/59</guid>
      <comments>https://oniwon.tistory.com/59#entry59comment</comments>
      <pubDate>Tue, 12 May 2026 17:43:21 +0900</pubDate>
    </item>
    <item>
      <title>[Java] equals() &amp;amp; hashCode() 깊게 보기 - 31의 의미, String 내부 동작</title>
      <link>https://oniwon.tistory.com/58</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;equals()와 hashCode()&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java에서 객체를 비교할 때 가장 헷갈리는 개념이 ==, equals(), hashCode()이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 셋이 어떻게 다른지, 왜 equals()를 오버라이드하면 hashCode()도 같이 오버라이드해야 하는지,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 hashCode() 내부에서 왜 하필 31을 곱하는지까지 정리해봤다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;객체 비교 3가지&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 47.2093%; height: 51px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.1186%; height: 17px;&quot;&gt;&lt;b&gt;=&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.5993%; height: 17px;&quot;&gt;대입&amp;nbsp;연산자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.1186%; height: 17px;&quot;&gt;&lt;b&gt;==&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.5993%; height: 17px;&quot;&gt;동일성&amp;nbsp;비교&amp;nbsp;&amp;mdash;&amp;nbsp;같은&amp;nbsp;객체인가?&amp;nbsp;(주소값)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.1186%; height: 17px;&quot;&gt;&lt;b&gt;equals()&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 42.5993%; height: 17px;&quot;&gt;동등성&amp;nbsp;비교&amp;nbsp;&amp;mdash;&amp;nbsp;논리적으로&amp;nbsp;같은가?&amp;nbsp;(내용)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본형은 주소 개념이 없으니 ==으로 값 자체를 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 참조형(객체)은 == 이 참조값을 비교하기 때문에 내용이 같아도 다른 객체로 판별된다.&lt;/p&gt;
&lt;pre id=&quot;code_1778568883928&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Person p1 = new Person(&quot;홍길동&quot;, 20);
Person p2 = new Person(&quot;홍길동&quot;, 20);

p1 == p2        // false &amp;rarr; 힙의 서로 다른 주소
p1.equals(p2)   // true  &amp;rarr; 이름, 나이가 같으므로 같다고 정의&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;equals() 직접 구현&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5가지 수학적 조건 (JDK 명세)&lt;/h3&gt;
&lt;table style=&quot;border-collapse: collapse; width: 71.3954%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: center;&quot;&gt;조건&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%; text-align: center;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: left;&quot;&gt;&lt;b&gt;반사성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%;&quot;&gt;x.equals(x) = true &amp;mdash; 자기 자신과 비교하면 항상 true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: left;&quot;&gt;&lt;b&gt;대칭성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%;&quot;&gt;x.equals(y) = true이면 y.equals(x) = true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: left;&quot;&gt;&lt;b&gt;추이성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%;&quot;&gt;x.equals(y)&amp;nbsp;=&amp;nbsp;true이고&amp;nbsp;y.equals(z)&amp;nbsp;=&amp;nbsp;true이면&amp;nbsp;x.equals(z)&amp;nbsp;=&amp;nbsp;true&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: left;&quot;&gt;&lt;b&gt;일관성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%;&quot;&gt;비교에&amp;nbsp;쓰인&amp;nbsp;정보가&amp;nbsp;바뀌지&amp;nbsp;않는&amp;nbsp;한&amp;nbsp;여러&amp;nbsp;번&amp;nbsp;호출해도&amp;nbsp;항상&amp;nbsp;같은&amp;nbsp;결과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 13.9503%; text-align: left;&quot;&gt;&lt;b&gt;null 비교&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 62.2575%;&quot;&gt;x.equals(null)&amp;nbsp;=&amp;nbsp;false&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현 예시&lt;/h3&gt;
&lt;pre id=&quot;code_1778569089580&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public boolean equals(Object o) {
  if (this == o) return true;                       // 반사성 + 최적화
  if (o == null) return false;                      // null 비교
  if (getClass() != o.getClass()) return false;     // 타입 체크
  Person person = (Person) o;
  return age == person.age &amp;amp;&amp;amp;
         Objects.equals(name, person.name);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Lombok으로&amp;nbsp;자동&amp;nbsp;생성&lt;/h3&gt;
&lt;pre id=&quot;code_1778569107454&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EqualsAndHashCode
public class Person {
  private String name;
  private int age;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;@EqualsAndHashCode를 붙이면 컴파일 시점에 equals() + hashCode()가 자동 생성된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;equals()&amp;nbsp;테스트&amp;nbsp;코드를&amp;nbsp;작성해야&amp;nbsp;하는&amp;nbsp;이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals()가 5가지 수학적 조건을 만족하는지는 눈으로 확인하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 HashMap, HashSet 같은 컬렉션은 내부에서 equals()를 자동 호출하기 때문에 버그가 생기면 추적이 매우 어렵다. 반드시 테스트 코드로 검증해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778569196315&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class PersonEqualsTest {

  @Test void 반사성() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      assertThat(a.equals(a)).isTrue();
  }

  @Test void 대칭성() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      Person b = new Person(&quot;홍길동&quot;, 20);
      assertThat(a.equals(b)).isTrue();
      assertThat(b.equals(a)).isTrue();
  }

  @Test void 추이성() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      Person b = new Person(&quot;홍길동&quot;, 20);
      Person c = new Person(&quot;홍길동&quot;, 20);
      assertThat(a.equals(b)).isTrue();
      assertThat(b.equals(c)).isTrue();
      assertThat(a.equals(c)).isTrue();
  }

  @Test void 일관성() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      Person b = new Person(&quot;홍길동&quot;, 20);
      for (int i = 0; i &amp;lt; 100; i++) {
          assertThat(a.equals(b)).isTrue();
      }
  }

  @Test void null비교() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      assertThat(a.equals(null)).isFalse();
  }

  @Test void 다른객체() {
      Person a = new Person(&quot;홍길동&quot;, 20);
      Person b = new Person(&quot;김철수&quot;, 30);
      assertThat(a.equals(b)).isFalse();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hashCode()를 반드시 같이 오버라이드해야 하는 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;HashMap&amp;nbsp;/&amp;nbsp;HashSet&amp;nbsp;동작&amp;nbsp;원리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HashMap/HashSet&amp;nbsp;은&amp;nbsp;객체를&amp;nbsp;저장하거나&amp;nbsp;조회할&amp;nbsp;때&amp;nbsp;내부적으로&amp;nbsp;두&amp;nbsp;단계로&amp;nbsp;동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이때 hashCode() 와 equals() 는 컬렉션이 자동으로 호출하기 때문에, 이 두 메서드를 클래스에 미리 오버라이드해두기만 하면 된다.)&lt;br /&gt;&lt;br /&gt;1. &lt;b&gt;1단계:&lt;/b&gt; hashCode()로 버킷(저장 위치)을 결정&lt;br /&gt;2. &lt;b&gt;2단계&lt;/b&gt;: 같은 버킷 안에서 equals()로 동일 객체인지 판별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Person.hashCode() 내부 연산&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;여러&amp;nbsp;필드를&amp;nbsp;하나의&amp;nbsp;int로&amp;nbsp;섞음&amp;nbsp;(예:&amp;nbsp;54321)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;HashMap/HashSet 버킷 결정&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;그&amp;nbsp;int를&amp;nbsp;버킷&amp;nbsp;개수로&amp;nbsp;나눈&amp;nbsp;나머지&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr; 54321 % 16 = 1번 버킷에 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;※ 참고: 실제 HashMap 은 key.hashCode() 결과를 그대로 쓰지 않고, 내부 hash() 메서드에서 (h ^ (h &amp;gt;&amp;gt;&amp;gt; 16)) 로 상하위 비트를 한 번 섞은 뒤&lt;b&gt; (n - 1) &amp;amp; hash&lt;/b&gt; 로 버킷을 결정한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&amp;nbsp;&lt;b&gt;hashCode()를&amp;nbsp;오버라이드하지&amp;nbsp;않으면&amp;nbsp;같은&amp;nbsp;버킷에&amp;nbsp;들어가지&amp;nbsp;않아서&amp;nbsp;equals()까지&amp;nbsp;도달하지&amp;nbsp;못한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;equals()만&amp;nbsp;오버라이드하면&amp;nbsp;생기는&amp;nbsp;버그&lt;/h3&gt;
&lt;pre id=&quot;code_1778569420849&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Set&amp;lt;Person&amp;gt; set = new HashSet&amp;lt;&amp;gt;();
set.add(new Person(&quot;홍길동&quot;, 20));
set.add(new Person(&quot;홍길동&quot;, 20));&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode()를 오버라이드 안 하면 Object.hashCode()를 사용한다. 이때 JVM이 객체마다 보통 서로 다른 int 값을 부여한다. (메모리&amp;nbsp;주소나&amp;nbsp;xorshift&amp;nbsp;등&amp;nbsp;JVM&amp;nbsp;구현에&amp;nbsp;따라&amp;nbsp;다름)&lt;/p&gt;
&lt;pre id=&quot;code_1778569477879&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;set.size()  // 2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과적으로 같은 사람인데 중복 저장되게 된다. (hashCode가 달라서 서로 다른 버킷에 들어감)&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;hashCode()&amp;nbsp;내부&amp;nbsp;동작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode()가 하는 일은 단순하다.&lt;b&gt;&amp;nbsp;임의의&amp;nbsp;객체를&amp;nbsp;받아&amp;nbsp;필드값들을&amp;nbsp;연산해&amp;nbsp;int(4바이트&amp;nbsp;숫자)&amp;nbsp;하나로&amp;nbsp;압축&lt;/b&gt;해서&amp;nbsp;반환하는&amp;nbsp;것이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;31을 곱하는 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의&amp;nbsp;hashCode()&amp;nbsp;구현에서&amp;nbsp;다음&amp;nbsp;패턴이&amp;nbsp;등장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;result&amp;nbsp;=&amp;nbsp;31&amp;nbsp;*&amp;nbsp;result&amp;nbsp;+&amp;nbsp;(element&amp;nbsp;==&amp;nbsp;null&amp;nbsp;?&amp;nbsp;0&amp;nbsp;:&amp;nbsp;element.hashCode());&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;왜&amp;nbsp;하필&amp;nbsp;31일까?&amp;nbsp;네&amp;nbsp;가지&amp;nbsp;이유가&amp;nbsp;맞물려&amp;nbsp;있다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 32비트 int에서 적당한 균형점&lt;/b&gt;&lt;br /&gt;비트가 적당히 퍼지면서도, 누적 곱셈 시 앞쪽 정보가 너무 빨리 오버플로우로 손실되지 않는 경험적 균형점이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 홀수여야 한다&lt;/b&gt;&lt;br /&gt;int는 32비트로 고정이다. 짝수를 곱하면 비트가 왼쪽으로 밀려서 결국 사라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;큰 숫자에 짝수를 계속 곱하면&lt;/p&gt;
&lt;pre id=&quot;code_1778569912794&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;10000000 00000000 00000000 00000001
* 2
00000000 00000000 00000000 00000010  &amp;larr; 맨 왼쪽 비트 사라짐
* 2
00000000 00000000 00000000 00000100  &amp;larr; 계속 사라짐
* 2
...
00000000 00000000 00000000 00000000  &amp;larr; 결국 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 값인데 hashCode가 0으로 같아져 충돌이 발생한다. &lt;b&gt;홀수는 이런 정보 손실이 없다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 소수(Prime&amp;nbsp;Number)여야&amp;nbsp;한다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소수의&amp;nbsp;약수는&amp;nbsp;1과&amp;nbsp;자기&amp;nbsp;자신뿐이라,&amp;nbsp;해시값이&amp;nbsp;고르게&amp;nbsp;분산되어&lt;b&gt;&amp;nbsp;충돌(같은&amp;nbsp;버킷에&amp;nbsp;몰리는&amp;nbsp;현상)이&amp;nbsp;최소화&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. JVM 최적화가 가능하다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31&amp;nbsp;*&amp;nbsp;x는&amp;nbsp;비트&amp;nbsp;시프트로&amp;nbsp;변환된다.&lt;/p&gt;
&lt;pre id=&quot;code_1778569969407&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;31 * x
= (32 - 1) * x
= 32 * x - x
= (x &amp;lt;&amp;lt; 5) - x&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곱셈보다&amp;nbsp;빠르게&amp;nbsp;처리된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;필드가 하나일 때는 31을 안 곱해도 된다.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;31을 곱하는 목적은 &lt;b&gt;여러 필드를 섞기 위한 것&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778570127807&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 필드 여러 개 &amp;rarr; 섞어야 함
result = 31 * name.hashCode() + address.hashCode();

// 필드 하나 &amp;rarr; 섞을 대상이 없음
return Objects.hashCode(name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 name.hashCode() 자체가 이미 분산된 int값이다. 거기에 31을 곱해봤자 숫자만 커질 뿐 분산에 도움이 되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히 String은 내부에서 이미 31을 활용해 해시를 만들기 때문에, 필드가 하나라면 그 결과를 그대로 쓰면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Objects.hash() vs Objects.hashCode() 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본격적인&amp;nbsp;차이를&amp;nbsp;설명하기&amp;nbsp;전에,&amp;nbsp;이름이&amp;nbsp;비슷한&amp;nbsp;세&amp;nbsp;메서드를&amp;nbsp;먼저&amp;nbsp;구분해야&amp;nbsp;한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.9845%;&quot;&gt;이름&lt;/td&gt;
&lt;td style=&quot;width: 24.3798%;&quot;&gt;소속 클래스&lt;/td&gt;
&lt;td style=&quot;width: 47.6356%;&quot;&gt;종류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.9845%;&quot;&gt;Object.hashCode()&lt;/td&gt;
&lt;td style=&quot;width: 24.3798%;&quot;&gt;java.lang.Object&lt;/td&gt;
&lt;td style=&quot;width: 47.6356%;&quot;&gt;인스턴스&amp;nbsp;메서드&amp;nbsp;(native)&amp;nbsp;&amp;mdash;&amp;nbsp;JVM이&amp;nbsp;처리&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.9845%;&quot;&gt;String.hashCode()&lt;/td&gt;
&lt;td style=&quot;width: 24.3798%;&quot;&gt;java.lang.String&lt;/td&gt;
&lt;td style=&quot;width: 47.6356%;&quot;&gt;인스턴스&amp;nbsp;메서드&amp;nbsp;(오버라이드)&amp;nbsp;&amp;mdash;&amp;nbsp;31&amp;nbsp;곱하기&amp;nbsp;로직&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 27.9845%;&quot;&gt;Objects.hashCode(o)&lt;/td&gt;
&lt;td style=&quot;width: 24.3798%;&quot;&gt;java.util.Objects&lt;/td&gt;
&lt;td style=&quot;width: 47.6356%;&quot;&gt;&lt;b&gt;정적 유틸리티&lt;/b&gt; &amp;mdash; 내부에서 `o.hashCode()` 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 쓰는 Objects.hashCode(o) 는 결국 o.hashCode() 를 호출하고,&lt;br /&gt;그 객체가 hashCode를 &lt;b&gt;오버라이드했다면&lt;/b&gt; 오버라이드된 버전(String, 우리가 만든 Person 등)이 실행되고,&lt;br /&gt;&lt;b&gt;오버라이드 안 했다면&lt;/b&gt; Object.hashCode() 의 네이티브 구현까지 내려간다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Objects.hash() 내부 동작&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;신버전:&amp;nbsp;Objects.hash()&amp;nbsp;사용&lt;/h3&gt;
&lt;pre id=&quot;code_1778570479546&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public int hashCode() {
  return Objects.hash(name, address, email);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부&amp;nbsp;구현을&amp;nbsp;따라가&amp;nbsp;보면&amp;nbsp;결국&amp;nbsp;다음과&amp;nbsp;같다.&lt;/p&gt;
&lt;pre id=&quot;code_1778570507305&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static int hash(Object... values) {
  return Arrays.hashCode(values);
}

public static int hashCode(Object[] a) {
  if (a == null) return 0;

  int result = 1;
  // 시작값이 1인 이유: 0으로 시작하면 31 * 0 = 0이 되어 첫 번째 요소가 영향을 주지 못함

  for (Object element : a)
      result = 31 * result + (element == null ? 0 : element.hashCode());

  return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구버전:&amp;nbsp;직접&amp;nbsp;전개&lt;/h3&gt;
&lt;pre id=&quot;code_1778570535966&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Override
public int hashCode() {
  int result = Objects.hashCode(name);
  result = 31 * result + Objects.hashCode(address);
  result = 31 * result + Objects.hashCode(email);
  return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;신/구&amp;nbsp;버전&amp;nbsp;모두&amp;nbsp;결국&amp;nbsp;같은&amp;nbsp;로직이다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-style=&quot;style6&quot; data-ke-type=&quot;horizontalRule&quot; /&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필드가 하나일 때 `Objects.hash()`를 쓰면 원본 hashCode()와 다른 값이 나온다.&lt;/p&gt;
&lt;pre id=&quot;code_1778574532578&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;String name = &quot;홍길동&quot;;

name.hashCode()          // 예: 54321
Objects.hash(name)       // 예: 31 * 1 + 54321 = 54352  &amp;larr; 다름!
Objects.hashCode(name)   // 예: 54321                   &amp;larr; 이걸 써야 같음&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이유는&amp;nbsp;Objects.hash()가&amp;nbsp;내부적으로&amp;nbsp;다음과&amp;nbsp;같이&amp;nbsp;동작하기&amp;nbsp;때문이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778574532579&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Objects.hash(name)
 = Arrays.hashCode(new Object[]{name})
 = 31 * 1 + name.hashCode()   // 1에서 시작해 31을 한 번 더 곱함&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;즉 Objects.hash() 는&lt;span&gt;&amp;nbsp;&lt;/span&gt;내부적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;varargs 배열을 생성한 뒤&lt;/b&gt;, 그 배열을 순회하며 각 요소의 hashCode를 31로 누적 곱셈하는 구조이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;필드가 하나라면 배열을 만들 필요 자체가 없으므로 Objects.hashCode(field)를 쓰면 된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;String.hashCode()&amp;nbsp;깊게&amp;nbsp;보기&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;왜 String은 내부에서 31을 한 번 더 곱하는가&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int 같은 원시 타입은 값 자체가 하나라서 섞을 내부 데이터가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;age = 20&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;더&amp;nbsp;이상&amp;nbsp;쪼갤&amp;nbsp;게&amp;nbsp;없음&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;그대로&amp;nbsp;20&amp;nbsp;반환&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;Arrays.hashCode()에서&amp;nbsp;31&amp;nbsp;한&amp;nbsp;번만&amp;nbsp;곱함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면&lt;b&gt; String은&amp;nbsp;내부가&amp;nbsp;문자&amp;nbsp;배열(여러&amp;nbsp;문자)이라&lt;/b&gt;&amp;nbsp;자기&amp;nbsp;자신을&amp;nbsp;합칠&amp;nbsp;때&amp;nbsp;31이&amp;nbsp;필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;name&amp;nbsp;=&amp;nbsp;&quot;홍길동&quot;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;내부적으로&amp;nbsp;['홍',&amp;nbsp;'길',&amp;nbsp;'동']&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;이&amp;nbsp;문자들을&amp;nbsp;하나의&amp;nbsp;int로&amp;nbsp;만들어야&amp;nbsp;함&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;String&amp;nbsp;내부에서&amp;nbsp;31로&amp;nbsp;한&amp;nbsp;번&amp;nbsp;섞음&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;그&amp;nbsp;결과를&amp;nbsp;Arrays.hashCode()에서&amp;nbsp;다시&amp;nbsp;31로&amp;nbsp;섞음&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;String.hashCode() 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1778570943865&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public int hashCode() {
  int h = hash;  // 캐시된 값 (처음엔 0)

  if (h == 0 &amp;amp;&amp;amp; !hashIsZero) {
      h = isLatin1() ? StringLatin1.hashCode(value)
                     : StringUTF16.hashCode(value);

      if (h == 0) {
          hashIsZero = true;  // 진짜 0이면 다음에 재계산 방지
      } else {
          hash = h;           // 계산값 캐시
      }
  }
  return h;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;String은 불변(immutable) 이라 한 번 계산한 hashCode는 절대 바뀌지 않는다.&lt;/b&gt;&lt;br /&gt;&amp;rarr; 첫 계산 후 hash 필드에 캐싱&lt;br /&gt;&amp;rarr; 다음 호출부터는 즉시 반환 (성능 최적화)&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Latin1&amp;nbsp;vs&amp;nbsp;UTF-16&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java&amp;nbsp;String은&amp;nbsp;&lt;b&gt;인코딩&lt;/b&gt;에&amp;nbsp;따라&amp;nbsp;두&amp;nbsp;가지&amp;nbsp;방식으로&amp;nbsp;해시를&amp;nbsp;계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. Latin1 (영문/숫자, 1바이트 문자)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778571013721&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static int hashCode(byte[] value) {
  int h = 0;
  for (byte v : value) {
      h = 31 * h + (v &amp;amp; 0xff);
  }
  return h;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&amp;nbsp;UTF-16&amp;nbsp;(한글&amp;nbsp;등,&amp;nbsp;2바이트&amp;nbsp;문자)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778571046576&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static int hashCode(byte[] value) {
  int h = 0;
  int length = value.length &amp;gt;&amp;gt; 1;  // 바이트 길이 / 2 = 문자 수
  for (int i = 0; i &amp;lt; length; i++) {
      h = 31 * h + getChar(value, i);  // 2바이트를 합쳐 char로 읽음
  }
  return h;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;계산 예시 &amp;mdash;&amp;nbsp;&quot;AB&quot;&amp;nbsp;(Latin1)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778571083839&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'A' = 0x41 = 65
'B' = 0x42 = 66
bytes = [0x41, 0x42]

h = 0
h = 31 * 0  + 65 = 65
h = 31 * 65 + 66 = 2081&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;계산 예시&lt;span&gt;&amp;mdash; &quot;가나&quot; (UTF-16,&amp;nbsp;LE&amp;nbsp;플랫폼&amp;nbsp;예시)&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778571722405&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'가' = 44032
'나' = 45208

UTF-16LE(하위 바이트가 앞으로) 저장 결과
bytes = [0x00, 0xAC, 0x98, 0xB0]
           &amp;uarr;──가──&amp;uarr;    &amp;uarr;──나──&amp;uarr;

i=0: getChar = (0xAC &amp;lt;&amp;lt; 8) | 0x00 = 44032 ('가')
i=1: getChar = (0xB0 &amp;lt;&amp;lt; 8) | 0x98 = 45208 ('나')

h = 0
h = 31 * 0     + 44032 = 44032
h = 31 * 44032 + 45208 = 1,410,200&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기본형은 값이 하나라서 Arrays 레벨에서 31을 한 번만 곱한다.&lt;br /&gt;&amp;nbsp;반면 String처럼 내부가 복합 구조인 타입은 31이 두 번 쓰인다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt; int&lt;/b&gt; &amp;rarr; 원자값 &amp;rarr; 31 한 번&lt;br /&gt;&lt;b&gt;String&lt;/b&gt; &amp;rarr; 복합값(문자 배열) &amp;rarr; 자기 내부 합칠 때 31 한 번 + Arrays에서 다시 31 한 번&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;네이티브 hashCode JDK 명세&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Object.hashCode()는 결국 네이티브 메서드다.&lt;/p&gt;
&lt;pre id=&quot;code_1778571945591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@IntrinsicCandidate
public native int hashCode();&lt;/code&gt;&lt;/pre&gt;
&lt;table style=&quot;border-collapse: collapse; width: 89.7674%; height: 70px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 23.8372%; height: 19px; text-align: center;&quot;&gt;&amp;nbsp;규칙&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%; height: 19px; text-align: center;&quot;&gt;내용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.8372%; height: 17px;&quot;&gt;&lt;b&gt;일관성&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%; height: 17px;&quot;&gt;같은&amp;nbsp;객체&amp;nbsp;&amp;rarr;&amp;nbsp;항상&amp;nbsp;같은&amp;nbsp;hashCode&amp;nbsp;(단,&amp;nbsp;JVM&amp;nbsp;재시작&amp;nbsp;시는&amp;nbsp;달라도&amp;nbsp;됨)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.8372%; height: 17px;&quot;&gt;&lt;b&gt;equals &amp;rarr; hashCode&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%; height: 17px;&quot;&gt;equals()가&amp;nbsp;true이면&amp;nbsp;hashCode()는&amp;nbsp;반드시&amp;nbsp;같아야&amp;nbsp;함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 23.8372%; height: 17px;&quot;&gt;&lt;b&gt;역방향은&amp;nbsp;보장&amp;nbsp;X&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 65.9302%; height: 17px;&quot;&gt;equals()가&amp;nbsp;false여도&amp;nbsp;hashCode()는&amp;nbsp;같을&amp;nbsp;수&amp;nbsp;있음&amp;nbsp;(=&amp;nbsp;충돌,&amp;nbsp;성능&amp;nbsp;저하&amp;nbsp;요인)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;왜&amp;nbsp;역방향은&amp;nbsp;보장하지&amp;nbsp;않을까&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hashCode()는&amp;nbsp;&lt;b&gt;무한한&amp;nbsp;객체&amp;nbsp;공간을&amp;nbsp;유한한&amp;nbsp;int(약&amp;nbsp;42억&amp;nbsp;개)로&amp;nbsp;압축&lt;/b&gt;하기&amp;nbsp;때문이다.&amp;nbsp;서로&amp;nbsp;다른&amp;nbsp;객체가&amp;nbsp;같은&amp;nbsp;해시를&amp;nbsp;가질&amp;nbsp;수밖에&amp;nbsp;없다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;==는 주소 비교, equals()는 내용 비교&lt;/li&gt;
&lt;li&gt;equals()는 5가지 수학적 조건(반사성, 대칭성, 추이성, 일관성, null)을 만족해야 하고, 테스트 코드로 검증해야 한다.&lt;/li&gt;
&lt;li&gt;equals()를 오버라이드하면 hashCode()도 반드시 같이 오버라이드해야 한다. 그렇지 않으면 HashMap/HashSet에서 같은 객체가 중복으로 들어간다.&lt;/li&gt;
&lt;li&gt;hashCode()에서 31을 곱하는 이유는 (1) 적당한 분산, (2) 홀수라 정보 손실 없음, (3) 소수라 충돌 최소화, (4) JVM이 비트 시프트로 최적화 가능하기 때문이다.&lt;/li&gt;
&lt;li&gt;필드 1개라면 Objects.hashCode(), 여러 개라면 Objects.hash()를 쓴다.&lt;/li&gt;
&lt;li&gt;String.hashCode()는 불변 특성을 이용해 결과를 캐싱한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>Equals</category>
      <category>HashCode</category>
      <category>HashMap</category>
      <category>hashset</category>
      <category>Java</category>
      <category>JVM</category>
      <category>objects.hash</category>
      <category>string</category>
      <category>에프랩</category>
      <category>자바면접</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/58</guid>
      <comments>https://oniwon.tistory.com/58#entry58comment</comments>
      <pubDate>Tue, 12 May 2026 17:39:48 +0900</pubDate>
    </item>
    <item>
      <title>[Java] synchronized 동작 원리 - 모니터 락, CAS</title>
      <link>https://oniwon.tistory.com/57</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;synchronized&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;`synchronized`는 같은 프로세스(JVM) 안에서 동기화를 보장하는 키워드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;동기화&lt;/b&gt;란, 여러 스레드/프로세스가 공유 자원(같은 객체나 변수)에 접근할 때 데이터를 일관되게 유지하도록 맞추는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 정확히 말하면, &lt;b&gt;실행 순서와 메모리 상태를&amp;nbsp;서로&amp;nbsp;맞추는&amp;nbsp;것&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단일 스레드 환경에서도 동작 자체는 보장되지만, 경쟁&amp;nbsp;자체가&amp;nbsp;없기&amp;nbsp;때문에&amp;nbsp;사실상&amp;nbsp;멀티&amp;nbsp;스레드&amp;nbsp;환경에서만&amp;nbsp;의미가&amp;nbsp;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;여기서&lt;b&gt; 원자성(Atomicity)&lt;/b&gt; 과&lt;b&gt; Thread safe&lt;/b&gt; 의 개념을 구분해야 한다.&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원자성&amp;nbsp;vs&amp;nbsp;Thread&amp;nbsp;safe&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원자성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특정 연산이 중간에 끊기지 않고 완전히 실행되거나, 아예 실행되지 않는 성질을 말한다.&lt;/p&gt;
&lt;pre id=&quot;code_1778564929251&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// i++는 사실 3단계 연산
int temp = i;     // 1. read
temp = temp + 1;  // 2. increment
i = temp;         // 3. write&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronized 없이는 스레드 A가 1번을 실행한 직후 스레드 B가 끼어들 수 있다. synchronized는 이 3단계를 하나의 블록으로 묶어 원자적으로 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Thread&amp;nbsp;safe&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티스레드 환경에서 공유 상태가 항상 일관성 있게 유지되는 성질이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이&amp;nbsp;둘을&amp;nbsp;굳이&amp;nbsp;구분하는&amp;nbsp;이유는,&amp;nbsp;&lt;b&gt;원자성이&amp;nbsp;보장돼도&amp;nbsp;Thread&amp;nbsp;safe가&amp;nbsp;아닐&amp;nbsp;수&amp;nbsp;있기&amp;nbsp;때문&lt;/b&gt;이다.&lt;/p&gt;
&lt;pre id=&quot;code_1778564687335&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Counter {
  private int count = 0;

  public synchronized void increment() { count++; } // 원자성 O
  public synchronized void decrement() { count--; } // 원자성 O

  // 문제: check-then-act 패턴
  public void incrementIfZero() {
      if (count == 0) {        // A 스레드가 여기서 0 확인
          // &amp;rarr; B 스레드가 끼어들어 increment() 호출
          increment();         // A는 0이 아닌데 그냥 증가시킴
      }
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;increment() 자체는 원자적이지만, incrementIfZero()는 read &amp;rarr; check &amp;rarr; write가 하나의 원자적 단위로 묶이지 않아서 Thread safe하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉,&lt;b&gt; 원자성은 Thread safe를 위한 도구 중 하나&lt;/b&gt;일 뿐이다. synchronized로 원자성을 확보해도&lt;b&gt; 설계 수준에서 경쟁 조건(race condition)을 제거&lt;/b&gt;해야 진정한 Thread safe가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Thread safe의 3요소&lt;/b&gt;&lt;br /&gt;1. 원자성: 작업이 중간에 끊기지 않음 (상호 배제로 달성)&lt;br /&gt;2. 가시성: 변경된 값을 다른 스레드가 즉시 볼 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 순서 보장: 명령어 재정렬 방지 &amp;mdash; happens-before 규칙&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;synchronized가&amp;nbsp;단일&amp;nbsp;JVM에서만&amp;nbsp;동작하는&amp;nbsp;이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;synchronized는 JVM 힙에 있는&lt;b&gt; 객체의 모니터 락&lt;/b&gt;을 기반으로 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 JVM 프로세스끼리는 메모리를 공유하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 JVM 안의 스레드들은&lt;b&gt; 동일한 객체의 Mark Word를 보기 때문에&lt;/b&gt; 서로의 락 상태를 알 수 있지만, 다른 프로세스 간에는 락을 공유할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다중 서버 환경에서는 synchronized만으로는 부족하고&lt;b&gt;, DB Lock, Redis Lock, Zookeeper&lt;/b&gt; 같은 외부 분산 락을 사용해야 한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;모니터 락&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 Java 객체는 힙 메모리에 존재하고, &lt;b&gt;객체 헤더(Object Header)에 락 상태를 기록&lt;/b&gt;한다. 그 안의 Mark Word에는 &lt;b&gt;&quot;지금 어떤 스레드가 이 객체를 점유 중인가&quot;&lt;/b&gt; 가 기록된다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1778565048721&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;평상시 (synchronized 안 쓸 때)

obj(공유 객체)
┌─────────────────────────────┐
│  Mark Word                  │  &amp;larr; 해시코드, GC 정보 등 저장
│  (모니터 정보 없음)             │     모니터는 아직 생성 안 됨
└─────────────────────────────┘


synchronized 진입 시도 &amp;rarr; 경량 락 먼저 시도&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;용어를 정리하면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드들은&amp;nbsp;같은&amp;nbsp;힙의&amp;nbsp;같은&amp;nbsp;객체를&amp;nbsp;참조하기&amp;nbsp;때문에&amp;nbsp;Mark&amp;nbsp;Word를&amp;nbsp;공유할&amp;nbsp;수&amp;nbsp;있고,&amp;nbsp;그래서&amp;nbsp;동기화가&amp;nbsp;가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Mark Word&lt;/b&gt;: 객체&amp;nbsp;헤더의&amp;nbsp;일부로,&amp;nbsp;락&amp;nbsp;상태나&amp;nbsp;ObjectMonitor&amp;nbsp;포인터를&amp;nbsp;저장하는&amp;nbsp;공간&lt;/li&gt;
&lt;li&gt;&lt;b&gt;모니터(ObjectMonitor)&lt;/b&gt;: `owner + Entry Set + Wait Set`을 포함하는 동기화 구조체&lt;/li&gt;
&lt;li&gt;Mark&amp;nbsp;Word는&amp;nbsp;무거운&amp;nbsp;락&amp;nbsp;진입&amp;nbsp;시&amp;nbsp;ObjectMonitor의&amp;nbsp;주소를&amp;nbsp;가리킨다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CAS (Compare-And-Swap)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS는 &lt;b&gt;메모리 주소의 값이 예상값과 일치할 때만 새 값으로 교체&lt;/b&gt;하는 CPU 단일 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CAS(메모리주소,&amp;nbsp;예상값,&amp;nbsp;새값)&lt;/b&gt;&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;메모리주소의&amp;nbsp;현재값&amp;nbsp;==&amp;nbsp;예상값&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;현재값을&amp;nbsp;새값으로&amp;nbsp;교체,&amp;nbsp;true&amp;nbsp;반환&lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;메모리주소의&amp;nbsp;현재값&amp;nbsp;!=&amp;nbsp;예상값&amp;nbsp;&amp;nbsp;&amp;rarr;&amp;nbsp;&amp;nbsp;아무것도&amp;nbsp;안&amp;nbsp;함,&amp;nbsp;false&amp;nbsp;반환&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시로 보면 더 명확하다.&lt;/p&gt;
&lt;pre id=&quot;code_1778565391800&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;상황: count = 0, 스레드A와 스레드B가 동시에 count를 1로 바꾸려 함

스레드A 실행
CAS(count주소, 0, 1)
&amp;rarr; count 현재값(0) == 예상값(0)  &amp;rarr; count를 1로 교체, true 반환
&amp;rarr; 락 획득 성공

스레드B 실행 (동시에 시도)
CAS(count주소, 0, 1)
&amp;rarr; count 현재값(1) != 예상값(0)  &amp;rarr; 아무것도 안 함, false 반환
&amp;rarr; 락 획득 실패 &amp;rarr; Entry Set 대기&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&quot;읽기&amp;nbsp;&amp;rarr;&amp;nbsp;비교&amp;nbsp;&amp;rarr;&amp;nbsp;쓰기&quot;&amp;nbsp;3단계를&amp;nbsp;&lt;b&gt;CPU의&amp;nbsp;단일&amp;nbsp;명령어(x86:&amp;nbsp;CMPXCHG)&amp;nbsp;&lt;/b&gt;로&amp;nbsp;처리하므로&amp;nbsp;원자성이&amp;nbsp;보장된다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;경량 락 (Lightweight&amp;nbsp;Lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경량 락은 락 획득 시 스레드&amp;nbsp;&lt;b&gt;스택&amp;nbsp;프레임에&amp;nbsp;Lock&amp;nbsp;Record를&amp;nbsp;생성&lt;/b&gt;하고,&amp;nbsp;CAS로&amp;nbsp;Mark&amp;nbsp;Word에&amp;nbsp;그&amp;nbsp;주소를&amp;nbsp;기록하는&amp;nbsp;방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObjectMonitor를 생성하지 않기 때문에 무거운 락보다 비용이 적다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;Lock Record&lt;/b&gt;: 경량 락 획득 시 스레드 스택 프레임에 생성되는 구조체. &lt;b&gt;원래 Mark Word 값을 백업해두는 공간&lt;/b&gt;이자, 락 획득의 증거 역할을 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;경량 락에서 CAS&amp;nbsp;동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유&amp;nbsp;객체의&amp;nbsp;Mark&amp;nbsp;Word&amp;nbsp;값을&amp;nbsp;내&amp;nbsp;스레드&amp;nbsp;스택의&amp;nbsp;Lock&amp;nbsp;Record에&amp;nbsp;복사한다.&amp;nbsp;이후&lt;b&gt; 그&amp;nbsp;복사한&amp;nbsp;값이&amp;nbsp;Mark&amp;nbsp;Word&amp;nbsp;주소의&amp;nbsp;현재&amp;nbsp;값과&amp;nbsp;같다면,&amp;nbsp;Lock&amp;nbsp;Record&amp;nbsp;주소로&amp;nbsp;교체하여&amp;nbsp;락을&amp;nbsp;획득&lt;/b&gt;한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;경량락.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;696&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMK2F4/dJMcadPsKzC/luawEEHeYUJI2K1gUHWApK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMK2F4/dJMcadPsKzC/luawEEHeYUJI2K1gUHWApK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMK2F4/dJMcadPsKzC/luawEEHeYUJI2K1gUHWApK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMK2F4%2FdJMcadPsKzC%2FluawEEHeYUJI2K1gUHWApK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;576&quot; height=&quot;306&quot; data-filename=&quot;경량락.png&quot; data-origin-width=&quot;1312&quot; data-origin-height=&quot;696&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1778565857422&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;[락 획득]
CAS(MarkWord주소, 현재MarkWord값, Lock Record주소)

1단계: MarkWord 읽기
 temp = *(MarkWord주소)  &amp;rarr; 현재 들어있는 값을 읽어옴

2단계: Lock Record에 백업
 Lock Record.displaced = temp  &amp;rarr; 읽어온 값을 백업

3단계: CAS 시도
 CAS(MarkWord주소, temp, Lock Record주소)
 &amp;rarr; MarkWord 안의 값이 아직 temp랑 같으면 &amp;rarr; 교체 성공
 &amp;rarr; 다른 스레드가 이미 바꿔놨으면 &amp;rarr; 실패

[락 해제]
CAS(MarkWord주소, Lock Record주소, displaced MarkWord)
&amp;rarr; 백업해둔 원래 MarkWord 값 복원
&amp;rarr; Lock Record 소멸&lt;/code&gt;&lt;/pre&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;무거운 락 (Heavyweight&amp;nbsp;Lock)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경량 락 CAS가 실패하면, JVM이&lt;b&gt; ObjectMonitor를 생성하고 Mark Word를 그 주소로 교체&lt;/b&gt;한다. 이 과정을&lt;b&gt; 팽창(inflate)&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ObjectMonitor는 HotSpot JVM 내부(C++)에 정의된 구조체이다.&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;무거운 락에서 CAS 동작&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[1단계: 팽창]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mark Word를 ObjectMonitor 포인터로 교체&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;[2단계: 락 획득]&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CAS(ObjectMonitor.owner, null, 현재스레드)&lt;br /&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;&amp;rarr; 성공: owner가&amp;nbsp;현재&amp;nbsp;스레드로&amp;nbsp;세팅&amp;nbsp;&amp;rarr;&amp;nbsp;락&amp;nbsp;획득&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666; text-align: left;&quot;&gt;&amp;rarr; 실패:&amp;nbsp;Entry&amp;nbsp;Set에&amp;nbsp;들어가서&amp;nbsp;BLOCK&amp;nbsp;상태로&amp;nbsp;대기&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1778566206092&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;synchronized 진입 순간 (무거운 락)

obj(공유 객체)
┌─────────────────────────────┐
│  Mark Word                  │  &amp;larr; ObjectMonitor 포인터로 교체됨
│  &amp;rarr; ObjectMonitor 주소 가리킴   │
└─────────────────────────────┘
         &amp;darr;
  ObjectMonitor (JVM이 그때 생성)
┌──────────────────────────────┐
│ owner                        │  &amp;larr; 현재 락을 점유한 스레드 포인터
│ (스레드A 포인터 저장)             │
├──────────────────────────────┤
│ Entry Set                    │  &amp;larr; 락 얻으려고 대기 중인 스레드들
├──────────────────────────────┤
│ Wait Set                     │  &amp;larr; wait() 호출한 스레드들
└──────────────────────────────┘&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서&amp;nbsp;&lt;b&gt;owner&amp;nbsp;필드가&amp;nbsp;곧&amp;nbsp;뮤텍스(상호&amp;nbsp;배제)&amp;nbsp;&lt;/b&gt;역할을&amp;nbsp;한다.&amp;nbsp;한&amp;nbsp;번에&amp;nbsp;한&amp;nbsp;스레드만&amp;nbsp;owner가&amp;nbsp;될&amp;nbsp;수&amp;nbsp;있고,&amp;nbsp;나머지는&amp;nbsp;Entry&amp;nbsp;Set에서&amp;nbsp;대기한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;무거운락.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;988&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oC3Q1/dJMcagS1PPD/Kr1tjhdHYxUq8chPfdMO10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oC3Q1/dJMcagS1PPD/Kr1tjhdHYxUq8chPfdMO10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oC3Q1/dJMcagS1PPD/Kr1tjhdHYxUq8chPfdMO10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoC3Q1%2FdJMcagS1PPD%2FKr1tjhdHYxUq8chPfdMO10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;555&quot; height=&quot;421&quot; data-filename=&quot;무거운락.png&quot; data-origin-width=&quot;1302&quot; data-origin-height=&quot;988&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized는 단일 JVM 내에서의 동기화를 보장하는 키워드다.&lt;/li&gt;
&lt;li&gt;원자성과 Thread safe는 다른 개념이며, 원자성은 Thread safe의 한 요소일 뿐이다.&lt;/li&gt;
&lt;li&gt;JVM은 비용이 적은 경량 락을 먼저 시도하고, 경합이 발생하면 무거운 락으로 팽창시킨다.&lt;/li&gt;
&lt;li&gt;두 락 모두 CAS를 통해 원자적으로 락 상태를 변경한다.&lt;/li&gt;
&lt;li&gt;다중 서버 환경에서는 synchronized로는 부족하므로 분산 락을 도입해야 한다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>CAS</category>
      <category>Java</category>
      <category>JVM</category>
      <category>synchronized</category>
      <category>ThreadSafe</category>
      <category>동시성</category>
      <category>멀티스레드</category>
      <category>모니터락</category>
      <category>에프랩</category>
      <category>원자성</category>
      <author>oniwon</author>
      <guid isPermaLink="true">https://oniwon.tistory.com/57</guid>
      <comments>https://oniwon.tistory.com/57#entry57comment</comments>
      <pubDate>Tue, 12 May 2026 15:30:03 +0900</pubDate>
    </item>
  </channel>
</rss>