<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>기억의 고체화를 위한 개발 블로그</title>
    <link>https://devseok.tistory.com/</link>
    <description>현재 Java 개발자로 재직중입니다.
블로그는 제가 성장 하는데 도움이 되는 글입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 11 May 2026 17:34:09 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>오개발</managingEditor>
    <image>
      <title>기억의 고체화를 위한 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/4364263/attach/4dd5a46233884a91a5b588e8917481bd</url>
      <link>https://devseok.tistory.com</link>
    </image>
    <item>
      <title>맥미니로 홈 서버 구축하기</title>
      <link>https://devseok.tistory.com/79</link>
      <description>&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6MFai/btsO5lBECBA/XtRByZmXvtqhoqiAfHXKP0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6MFai/btsO5lBECBA/XtRByZmXvtqhoqiAfHXKP0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6MFai/btsO5lBECBA/XtRByZmXvtqhoqiAfHXKP0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6MFai%2FbtsO5lBECBA%2FXtRByZmXvtqhoqiAfHXKP0%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;278&quot; height=&quot;320&quot; data-origin-width=&quot;278&quot; data-origin-height=&quot;320&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 1. 이더넷 연결 &lt;/b&gt;&lt;/h4&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt; 2. 내부 IP 고정 (DHCP 고정 할당)&lt;/b&gt;&lt;/h4&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 data-end=&quot;247&quot; data-start=&quot;224&quot;&gt;&lt;b&gt;공유기 관리자 페이지&lt;/b&gt;에 접속&lt;/li&gt;
&lt;li data-end=&quot;311&quot; data-start=&quot;248&quot;&gt;상태정보 &amp;gt; DHCP할당정보 확인 후 DHCP 고정 할당 설정&lt;br /&gt;&amp;rarr; 맥미니의 내부 IP를 고정해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://ansan-survivor.tistory.com/108&quot;&gt;해당부분 참고 링크&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w328y/btsO513FfWU/zOfgkn2l2dibL5ik15pHO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w328y/btsO513FfWU/zOfgkn2l2dibL5ik15pHO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w328y/btsO513FfWU/zOfgkn2l2dibL5ik15pHO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw328y%2FbtsO513FfWU%2FzOfgkn2l2dibL5ik15pHO1%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;909&quot; height=&quot;540&quot; data-origin-width=&quot;909&quot; data-origin-height=&quot;540&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;339&quot; data-start=&quot;313&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. NAT 설정 (포트포워딩)&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;441&quot; data-start=&quot;340&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;381&quot; data-start=&quot;340&quot;&gt;공유기 설정에서 &lt;b&gt;NAT 설정 또는 포트포워딩 설정&lt;/b&gt; 메뉴 진입&lt;/li&gt;
&lt;li data-end=&quot;381&quot; data-start=&quot;340&quot;&gt;&lt;b&gt;22번 포트&lt;/b&gt; (SSH용 포트) 활성화&lt;br /&gt;&amp;rarr; 외부에서 내부 맥미니에 SSH 접속 가능하게 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;306&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nkOAf/btsO6DHOLnF/OTzT7JIOgwmioLkxDr7aU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nkOAf/btsO6DHOLnF/OTzT7JIOgwmioLkxDr7aU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nkOAf/btsO6DHOLnF/OTzT7JIOgwmioLkxDr7aU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnkOAf%2FbtsO6DHOLnF%2FOTzT7JIOgwmioLkxDr7aU1%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;907&quot; height=&quot;306&quot; data-origin-width=&quot;907&quot; data-origin-height=&quot;306&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;461&quot; data-start=&quot;443&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 맥미니 설정&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;562&quot; data-start=&quot;462&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;511&quot; data-start=&quot;462&quot;&gt;설정 &amp;gt; 일반 &amp;gt; 공유 &amp;gt; 원격 로그인 활성화&lt;br /&gt;&amp;rarr; SSH 접속을 허용함&lt;/li&gt;
&lt;li data-end=&quot;562&quot; data-start=&quot;512&quot;&gt;'i' 버튼을 누르면 내부 IP가 보이지만, 외부에서는 &lt;b&gt;공인 IP로 접속해야 함&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdg8e8/btsO6a7aAuL/TkfkkGgnhOCswfLeiTIVK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdg8e8/btsO6a7aAuL/TkfkkGgnhOCswfLeiTIVK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdg8e8/btsO6a7aAuL/TkfkkGgnhOCswfLeiTIVK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcdg8e8%2FbtsO6a7aAuL%2FTkfkkGgnhOCswfLeiTIVK0%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;509&quot; height=&quot;444&quot; data-origin-width=&quot;709&quot; data-origin-height=&quot;618&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;383&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nLjZN/btsO5BRJWE9/nS4nzkJlieeCgXhg5vkHlK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nLjZN/btsO5BRJWE9/nS4nzkJlieeCgXhg5vkHlK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nLjZN/btsO5BRJWE9/nS4nzkJlieeCgXhg5vkHlK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnLjZN%2FbtsO5BRJWE9%2FnS4nzkJlieeCgXhg5vkHlK%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;471&quot; height=&quot;383&quot; data-origin-width=&quot;471&quot; data-origin-height=&quot;383&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-end=&quot;573&quot; data-start=&quot;564&quot; data-ke-size=&quot;size20&quot;&gt;✅ 결과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;611&quot; data-start=&quot;574&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;611&quot; data-start=&quot;574&quot;&gt;외부에서 &lt;b&gt;공인 IP:22 포트&lt;/b&gt;로 &lt;b&gt;SSH 접속 성공&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;553&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v8xop/btsO5TxJ5Lp/scUULrIsfq1WlHLPJjXFvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v8xop/btsO5TxJ5Lp/scUULrIsfq1WlHLPJjXFvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v8xop/btsO5TxJ5Lp/scUULrIsfq1WlHLPJjXFvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv8xop%2FbtsO5TxJ5Lp%2FscUULrIsfq1WlHLPJjXFvk%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;483&quot; height=&quot;553&quot; data-origin-width=&quot;483&quot; data-origin-height=&quot;553&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>일상 및 생각</category>
      <category>맥미니</category>
      <category>홈서버구축</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/79</guid>
      <comments>https://devseok.tistory.com/79#entry79comment</comments>
      <pubDate>Sat, 5 Jul 2025 16:48:00 +0900</pubDate>
    </item>
    <item>
      <title>[kafka] Node -1 disconnected.</title>
      <link>https://devseok.tistory.com/78</link>
      <description>&lt;h2 data-end=&quot;117&quot; data-start=&quot;69&quot; data-ke-size=&quot;size26&quot;&gt;Kafka Consumer Group 충돌로 인한 비정상 동작 원인 분석 및 해결&lt;/h2&gt;
&lt;p data-end=&quot;256&quot; data-start=&quot;119&quot; data-ke-size=&quot;size16&quot;&gt;개발 중 Kafka 기반 시스템을 테스트하다가, 예상치 못한 이슈를 겪었습니다.&lt;br /&gt;바로 &lt;b&gt;Consumer가 정상적으로 실행되지 않는 문제&lt;/b&gt;였는데요, 겉으로 보기엔 오류가 없었고, 로그 레벨도 INFO여서 단순한 상태 정보처럼 보였습니다.&lt;/p&gt;
&lt;p data-end=&quot;312&quot; data-start=&quot;258&quot; data-ke-size=&quot;size16&quot;&gt;그런데 실제 테스트 결과, &lt;b&gt;Consumer가 메시지를 수신하지 못하고 있는 상황&lt;/b&gt;이었습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;153&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5vRHX/btsOQoEMbks/gglC9Lb0kCiDiLkCTRLkhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5vRHX/btsOQoEMbks/gglC9Lb0kCiDiLkCTRLkhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5vRHX/btsOQoEMbks/gglC9Lb0kCiDiLkCTRLkhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5vRHX%2FbtsOQoEMbks%2FgglC9Lb0kCiDiLkCTRLkhK%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;1492&quot; height=&quot;153&quot; data-origin-width=&quot;1492&quot; data-origin-height=&quot;153&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;h3 data-end=&quot;325&quot; data-start=&quot;319&quot; data-ke-size=&quot;size23&quot;&gt;현상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;422&quot; data-start=&quot;327&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;352&quot; data-start=&quot;327&quot;&gt;Kafka Consumer가 동작하지 않음&lt;/li&gt;
&lt;li data-end=&quot;391&quot; data-start=&quot;353&quot;&gt;로그에는 특별한 ERROR 없이 INFO 수준의 로그만 출력됨&lt;/li&gt;
&lt;li data-end=&quot;422&quot; data-start=&quot;392&quot;&gt;Producer는 정상적으로 메시지를 전송하고 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;439&quot; data-start=&quot;429&quot; data-ke-size=&quot;size23&quot;&gt;삽질기(?)&lt;/h3&gt;
&lt;p data-end=&quot;468&quot; data-start=&quot;441&quot; data-ke-size=&quot;size16&quot;&gt;해결을 위해 아래와 같은 방법들을 시도해봤습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;588&quot; data-start=&quot;470&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;537&quot; data-start=&quot;470&quot;&gt;&lt;b&gt;개발 서버에서 Telnet 테스트&lt;/b&gt;&lt;br /&gt;&amp;rarr; Kafka 브로커와 통신은 정상 (포트도 열려있고 연결 가능)&lt;/li&gt;
&lt;li data-end=&quot;588&quot; data-start=&quot;539&quot;&gt;&lt;b&gt;Kafka Config 파일 설정 변경&lt;/b&gt;&lt;br /&gt;&amp;rarr; 의미 있는 변화는 없었음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-end=&quot;616&quot; data-start=&quot;590&quot; data-ke-size=&quot;size16&quot;&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;style5&quot; /&gt;
&lt;h3 data-end=&quot;629&quot; data-start=&quot;623&quot; data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-end=&quot;717&quot; data-start=&quot;631&quot; data-ke-size=&quot;size16&quot;&gt;로컬 PC에서 개발 중 Kafka Consumer를 실행한 상태로,&lt;br /&gt;개발 서버에서도 동일한 Group ID로 Kafka Consumer를 실행했습니다.&lt;/p&gt;
&lt;p data-end=&quot;841&quot; data-start=&quot;719&quot; data-ke-size=&quot;size16&quot;&gt;즉, &lt;b&gt;같은 Group ID로 두 개의 Consumer가 실행&lt;/b&gt;되었고,&lt;br /&gt;Kafka의 &lt;b&gt;Consumer Group Rebalancing&lt;/b&gt; 정책에 의해&lt;br /&gt;하나의 Consumer만 메시지를 처리할 수 있었습니다.&lt;/p&gt;
&lt;p data-end=&quot;885&quot; data-start=&quot;843&quot; data-ke-size=&quot;size16&quot;&gt;결과적으로, &lt;b&gt;개발 서버의 Consumer가 동작하지 않게 된 것&lt;/b&gt;이죠.&lt;/p&gt;
&lt;p data-end=&quot;885&quot; data-start=&quot;843&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;901&quot; data-start=&quot;892&quot; data-ke-size=&quot;size23&quot;&gt;해결 방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-end=&quot;1033&quot; data-start=&quot;903&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li data-end=&quot;978&quot; data-start=&quot;903&quot;&gt;&lt;b&gt;로컬 Consumer 종료 (✅ 필자가 선택한 방법)&lt;/b&gt;&lt;br /&gt;&amp;rarr; 개발 서버의 Consumer가 정상적으로 메시지를 수신함&lt;/li&gt;
&lt;li data-end=&quot;1033&quot; data-start=&quot;980&quot;&gt;&lt;b&gt;Group ID 분리&lt;/b&gt;&lt;br /&gt;&amp;rarr; 로컬과 서버에서 서로 다른 Group ID를 설정&lt;/li&gt;
&lt;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-end=&quot;1047&quot; data-start=&quot;1040&quot; data-ke-size=&quot;size23&quot;&gt;마무리&lt;/h3&gt;
&lt;p data-end=&quot;1227&quot; data-start=&quot;1049&quot; data-ke-size=&quot;size16&quot;&gt;Kafka는 같은 Consumer Group 내에서 파티션을 분배하여 메시지를 처리합니다.&lt;br /&gt;따라서 &lt;b&gt;동일한 Group ID로 여러 서버에서 동시에 실행할 경우&lt;/b&gt;&lt;br /&gt;의도치 않게 메시지 처리가 이루어지지 않을 수 있으니,&lt;br /&gt;개발 및 테스트 환경에서는 &lt;b&gt;Group ID를 명확하게 구분&lt;/b&gt;하는 습관이 필요합니다.&lt;/p&gt;
&lt;blockquote data-end=&quot;1298&quot; data-start=&quot;1229&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-end=&quot;1298&quot; data-start=&quot;1231&quot; data-ke-size=&quot;size16&quot;&gt;  Kafka Consumer Group은 &quot;서로 협력하는 하나의 논리적 Consumer&quot;로 이해하는 것이 중요합니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>DevOps</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/78</guid>
      <comments>https://devseok.tistory.com/78#entry78comment</comments>
      <pubDate>Wed, 25 Jun 2025 13:50:57 +0900</pubDate>
    </item>
    <item>
      <title>Java volatile</title>
      <link>https://devseok.tistory.com/77</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;java에서 volatile 키워드는 스레드들 사이에서 변수에 저장된 값을 변경하는 것에 대한 가시성을 보장합니다 여기서 가시성이란 최&lt;b&gt;신 상태를 유지 하고 변경된 값을 즉시 읽을수 있도록 보장한다는 말이다.&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;&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;Using Cache in Multi CPU&lt;a style=&quot;color: #2f7d95;&quot; href=&quot;https://junhyunny.github.io/information/java/java-volatile/#using-cache-in-multi-cpu&quot;&gt;&lt;span&gt;Permalink&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 개의 CPU를 사용하는 멀티 스레드 환경에서 데이터 동기화 문제가 발생합니다.&lt;/li&gt;
&lt;li&gt;각 CPU들이 각자의 스레드를 실행합니다.&lt;/li&gt;
&lt;li&gt;어플리케이션의 같은 변수를 사용하지만, CPU 캐시에 데이터를 로딩해서 사용합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;385&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sCh6k/btsKKoIXQki/SQkNEbJjktmB1jWi2pGVK1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sCh6k/btsKKoIXQki/SQkNEbJjktmB1jWi2pGVK1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sCh6k/btsKKoIXQki/SQkNEbJjktmB1jWi2pGVK1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsCh6k%2FbtsKKoIXQki%2FSQkNEbJjktmB1jWi2pGVK1%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;450&quot; height=&quot;385&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;385&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;11-data-not-matched-between-main-memory-and-cpu-cache&quot; data-ke-size=&quot;size23&quot;&gt;1.1. Data not matched between main memory and cpu cache&lt;a style=&quot;color: #2f7d95;&quot; href=&quot;https://junhyunny.github.io/information/java/java-volatile/#11-data-not-matched-between-main-memory-and-cpu-cache&quot;&gt;&lt;span&gt;Permalink&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU1은 Thread1을 실행합니다.&lt;/li&gt;
&lt;li&gt;CPU2는 Thread2를 실행합니다.&lt;/li&gt;
&lt;li&gt;CPU1은 Thread1을 수행하면서 count 변수를 읽어 증가시키면서 작업을 수행합니다.&lt;/li&gt;
&lt;li&gt;CPU2는 Thread2를 수행하면서 값의 변경은 없이 사용합니다.&lt;/li&gt;
&lt;li&gt;같은 변수를 다른 값으로 사용하게 되면서 로직 상의 문제가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIcYju/btsKKbXmdXm/CFOfFZGpGS96VfK6E8Oi2k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIcYju/btsKKbXmdXm/CFOfFZGpGS96VfK6E8Oi2k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIcYju/btsKKbXmdXm/CFOfFZGpGS96VfK6E8Oi2k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIcYju%2FbtsKKbXmdXm%2FCFOfFZGpGS96VfK6E8Oi2k%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;466&quot; height=&quot;396&quot; data-origin-width=&quot;466&quot; data-origin-height=&quot;396&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;12-synchronize-in-multi-thread-environment&quot; style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;1.2. Synchronize in Multi Thread Environment&lt;a style=&quot;color: #2f7d95;&quot; href=&quot;https://junhyunny.github.io/information/java/java-volatile/#12-synchronize-in-multi-thread-environment&quot;&gt;&lt;span&gt;Permalink&lt;/span&gt;&lt;/a&gt;&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같은 문제를 해결하기 위해 volatile 키워드를 사용합니다. volatile 키워드를 사용하면 CPU 캐시가 아닌 메인 메모리에 저장된 데이터를 사용합니다. 데이터 불일치 문제는 해결할 수 있지만, 캐시를 사용하지 않는 만큼 성능이 떨어질 수 있습니다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Java는 멀티 스레드 환경에서 스레드 안전한 프로그래밍을 위해 다음과 같은 기능들을 제공합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized&lt;/li&gt;
&lt;li&gt;Atomic classes&lt;/li&gt;
&lt;li&gt;volatile&lt;/li&gt;
&lt;/ul&gt;
&lt;p style=&quot;background-color: #ffffff; color: #3d4144; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;synchronized 키워드나 Atomic 클래스는 동기화를 지원하지만, volatile 키워드는 가시성을 보장할 뿐 동기화를 지원하지 않습니다. 데이터 동기화를 위해선 synchronized 키워드나 Atomic 클래스를 함께 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/77</guid>
      <comments>https://devseok.tistory.com/77#entry77comment</comments>
      <pubDate>Fri, 15 Nov 2024 16:12:09 +0900</pubDate>
    </item>
    <item>
      <title>CompletableFuture에 대해서</title>
      <link>https://devseok.tistory.com/76</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CompletableFuture는 Java 8에서 도입된 비동기 프로그래밍을 위한 클래스입니다. 이 클래스는 비동기 작업을 수행하고 그 결과를 비동기적으로 처리할 수 있는 메커니즘을 제공합니다. 기존의 Future 인터페이스의 단점을 보완하여, 비동기 작업의 결과를 쉽게 조합하고 체이닝(Chaining)할 수 있도록 설계되었습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;b&gt;[&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;Future&lt;/span&gt;&lt;span&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;의 단점 및 한계&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&amp;nbsp;]&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CompletableFuture가 갖는 작업의 종류는 크게 다음과 같이 구분할 수 있는데, 이에 대해서는 자세히 코드로 살펴보도록 하자.&lt;/p&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;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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;비동기 작업 실행&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;runAsync
&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;supplyAsync
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;runAsync는 반환 값이 없으므로 Void 타입이며, 아래의 코드를 실행해보면 future가 별도의 쓰레드에서 실행됨을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657113452287&quot; class=&quot;reasonml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void runAsync() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.runAsync(() -&amp;gt; {
        System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
    });

    future.get();
    System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
}&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;supplyAsync는 runAsync와 달리 반환값이 존재한다. 그래서 비동기 작업의 결과를 받아올 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657113678792&quot; class=&quot;reasonml&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void supplyAsync() throws ExecutionException, InterruptedException {

    CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Thread: &quot; + Thread.currentThread().getName();
    });

    System.out.println(future.get());
    System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;runAsync와 supplyAsync는 기본적으로 자바7에 추가된 ForkJoinPool의 commonPool()을 사용해 작업을 실행할 쓰레드를 쓰레드 풀로부터 얻어 실행시킨다. 만약 원하는 쓰레드 풀을 사용하려면, ExecutorService를 파라미터로 넘겨주면 된다. 직접 비동기 작업에 대한 코드를 실행해보려면 &lt;a href=&quot;https://github.com/MangKyu/java-concurrency/blob/master/src/test/java/com/mangkyu/concurrency/java8/CompletableFutureRunTest.java&quot;&gt;깃허브&lt;/a&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;/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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;작업 콜백&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;thenApply
&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;함수형 인터페이스 Function을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;thenAccpet
&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;함수형 인터페이스 Consumer를 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;thenRun
&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;함수형 인터페이스 Runnable을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;Java8에는 다양한 함수형 인터페이스들이 추가되었는데, CompletableFuture 역시 이들을 콜백으로 등록할 수 있게 한다. 그래서 비동기 실행이 끝난 후에 전달 받은 작업 콜백을 실행시켜주는데,&amp;nbsp;thenApply는 값을 받아서 다른 값을 반환시켜주는 콜백이다.&lt;/p&gt;
&lt;pre id=&quot;code_1657114606761&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void thenApply() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Thread: &quot; + Thread.currentThread().getName();
    }).thenApply(s -&amp;gt; {
        return s.toUpperCase();
    });

    System.out.println(future.get());
}&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;thenAccept는 반환 값을 받아서 사용하고, 값을 반환하지는 않는 콜백이다.&lt;/p&gt;
&lt;pre id=&quot;code_1657114677102&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void thenAccept() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Thread: &quot; + Thread.currentThread().getName();
    }).thenAccept(s -&amp;gt; {
        System.out.println(s.toUpperCase());
    });

    future.get();
}&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;thenRun은 반환 값을 받지 않고, 그냥 다른 작업을 실행하는 콜백이다. 작업 콜백 관련 추가 예시 코드들은 &lt;a href=&quot;https://github.com/MangKyu/java-concurrency/blob/master/src/test/java/com/mangkyu/concurrency/java8/CompletableFutureCallbackTest.java&quot;&gt;깃허브&lt;/a&gt;에서 참고하도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1657114724406&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void thenRun() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Thread: &quot; + Thread.currentThread().getName();
    }).thenRun(() -&amp;gt; {
        System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
    });

    future.get();
}&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;&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;작업 조합&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;thenCompose
&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;함수형 인터페이스 Function을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;thenCombine
&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;함수형 인터페이스 Function을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;allOf
&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;/li&gt;
&lt;li&gt;anyOf
&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;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 살펴볼 thenCompose와 thenCombine 예제의 실행 결과는 같지만 동작 과정은 다르다. 먼저 thenCompose를 살펴보면 hello Future가 먼저 실행된 후에 반환된 값을 매개변수로 다음 Future를 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1657114956154&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void thenCompose() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; hello = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Hello&quot;;
    });

    // Future 간에 연관 관계가 있는 경우
    CompletableFuture&amp;lt;String&amp;gt; future = hello.thenCompose(this::mangKyu);
    System.out.println(future.get());
}

private CompletableFuture&amp;lt;String&amp;gt; mangKyu(String message) {
    return CompletableFuture.supplyAsync(() -&amp;gt; {
        return message + &quot; &quot; + &quot;MangKyu&quot;;
    });
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 thenCombine은 각각의 작업들이 독립적으로 실행되고, 얻어진 두 결과를 조합해서 작업을 처리한다.&lt;/p&gt;
&lt;pre id=&quot;code_1657114974805&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void thenCombine() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; hello = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Hello&quot;;
    });

    CompletableFuture&amp;lt;String&amp;gt; mangKyu = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;MangKyu&quot;;
    });

    CompletableFuture&amp;lt;String&amp;gt; future = hello.thenCombine(mangKyu, (h, w) -&amp;gt; h + &quot; &quot; + w);
    System.out.println(future.get());
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 다음은 allOf와 anyOf를 살펴볼 차례이다. 아래의 코드를 실행해보면 모든 결과에 콜백이 적용됨을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657115037882&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void allOf() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; hello = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;Hello&quot;;
    });

    CompletableFuture&amp;lt;String&amp;gt; mangKyu = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;MangKyu&quot;;
    });

    List&amp;lt;CompletableFuture&amp;lt;String&amp;gt;&amp;gt; futures = List.of(hello, mangKyu);

    CompletableFuture&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; result = CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]))
            .thenApply(v -&amp;gt; futures.stream().
                    map(CompletableFuture::join).
                    collect(Collectors.toList()));

    result.get().forEach(System.out::println);
}&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;반면에 anyOf의 경우에는 가장 빨리 끝난 1개의 작업에 대해서만 콜백이 실행됨을 확인할 수 있다. 관련 코드는 &lt;a href=&quot;https://github.com/MangKyu/java-concurrency/blob/master/src/test/java/com/mangkyu/concurrency/java8/CompletableFutureComposeTest.java&quot;&gt;깃허브&lt;/a&gt;를 참고하도록 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1657115084650&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void anyOf() throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; hello = CompletableFuture.supplyAsync(() -&amp;gt; {
        try {
            Thread.sleep(1000L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        return &quot;Hello&quot;;
    });

    CompletableFuture&amp;lt;String&amp;gt; mangKyu = CompletableFuture.supplyAsync(() -&amp;gt; {
        return &quot;MangKyu&quot;;
    });

    CompletableFuture&amp;lt;Void&amp;gt; future = CompletableFuture.anyOf(hello, mangKyu).thenAccept(System.out::println);
    future.get();
}&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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;예외 처리&lt;/b&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;exeptionally
&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;함수형 인터페이스 Function을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;handle, handleAsync
&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;함수형 인터페이스 BiFunction을 파라미터로 받음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;각각에 대해 throw하는 경우와 throw하지 않는 경우를 모두 실행시켜보도록 하자. 아래의 @ParameterizedTest는 동일한 테스트를 다른 파라미터로 여러 번 실행할 수 있도록 도와주는데, 실행해보면 throw 여부에 따라 실행 결과가 달라짐을 확인할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1657115325503&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@ParameterizedTest
@ValueSource(booleans =  {true, false})
void exceptionally(boolean doThrow) throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        if (doThrow) {
            throw new IllegalArgumentException(&quot;Invalid Argument&quot;);
        }

        return &quot;Thread: &quot; + Thread.currentThread().getName();
    }).exceptionally(e -&amp;gt; {
        return e.getMessage();
    });

    System.out.println(future.get());
}

java.lang.IllegalArgumentException: Invalid Argument
// Thread: ForkJoinPool.commonPool-worker-19&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;마찬가지로 handle을 실행해보면 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;throw 여부에 따라 실행 결과가 달라짐을 확인할 수 있다. 예외 처리 관련 예제 코드는 &lt;a href=&quot;https://github.com/MangKyu/java-concurrency/blob/master/src/test/java/com/mangkyu/concurrency/java8/CompletableFutureExceptionTest.java&quot;&gt;깃허브&lt;/a&gt;에 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1657115442870&quot; class=&quot;livescript&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@ParameterizedTest
@ValueSource(booleans =  {true, false})
void handle(boolean doThrow) throws ExecutionException, InterruptedException {
    CompletableFuture&amp;lt;String&amp;gt; future = CompletableFuture.supplyAsync(() -&amp;gt; {
        if (doThrow) {
            throw new IllegalArgumentException(&quot;Invalid Argument&quot;);
        }

        return &quot;Thread: &quot; + Thread.currentThread().getName();
    }).handle((result, e) -&amp;gt; {
        return e == null
                ? result
                : e.getMessage();
    });

    System.out.println(future.get());
}&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 외에도 아직 완료되지 않았으면 get을 바로 호출하고, 실패 시에 주어진 exception을 던지게 하는 completeExceptionally와 강제로 예외를 발생시키는 obtrudeException과 예외적으로 완료되었는지를 반환하는 isCompletedExceptionally 등과 같은 기능들도 있으니, 관련해서는 추가적으로 살펴보도록 하자.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참조 : &lt;a href=&quot;https://mangkyu.tistory.com/263&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://mangkyu.tistory.com/263&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/76</guid>
      <comments>https://devseok.tistory.com/76#entry76comment</comments>
      <pubDate>Fri, 15 Nov 2024 15:37:36 +0900</pubDate>
    </item>
    <item>
      <title>Java에서 dead lock이란 무엇인가요?</title>
      <link>https://devseok.tistory.com/75</link>
      <description>&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;/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;교착상태(deadlock)&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이라고 합니다.&amp;nbsp;&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;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;DeakLock 발생 조건&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc; color: #333333; text-align: left;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: unset; color: #000000;&quot;&gt;&lt;b&gt;상호 배제 (Mutual Exclusion)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: &lt;span style=&quot;color: #000000; text-align: left;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;한 자원에 대해 여러 쓰레드 동시 접근 불가&lt;/span&gt;&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset; color: #000000;&quot;&gt;&lt;b&gt;점유와 대기 (Hold and Wait)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 자원을 가지고 있는 상태에서 다른 쓰레드가 사용하고 있는 자원 반납을 기다리는 것&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset; color: #000000;&quot;&gt;&lt;b&gt;비선점 (Non Preemptive)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 다른 쓰레드의 자원을 실행 중간에 강제로 가져올 수 없음&lt;/li&gt;
&lt;li style=&quot;list-style-type: unset; color: #000000;&quot;&gt;&lt;b&gt;환형대기 (Circle Wait)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 각 쓰레드가 순환적으로 다음 쓰레드가 요구하는 자원을 가지고 있는 것&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/75</guid>
      <comments>https://devseok.tistory.com/75#entry75comment</comments>
      <pubDate>Thu, 14 Nov 2024 11:55:03 +0900</pubDate>
    </item>
    <item>
      <title>ThreadLocal vs synchronized 차이점</title>
      <link>https://devseok.tistory.com/74</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 스레드 환경에서 동시성 이슈는 자주 일어난다. 자바에서 &lt;b&gt;synchronized &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;동시성 관련 내용을 해결&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;1. synchronized&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized는 객체 공유를 제어 하는 역할이다.&lt;/li&gt;
&lt;li&gt;여러 스레드가 동일한 객체를 접근할때 블로킹을 통해 안전성을 보장한다.&lt;/li&gt;
&lt;li&gt;동기화 비용이 있어서 블로킹으로 인해 성능 저하가 있을수도 있습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. ThreadLocal&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ThreadLocal는 각 독립적인 스레드의 값을 가지게 한다. 스레드 마다 고유의 값을 보장한다.&lt;/li&gt;
&lt;li&gt;각 스레드에 고유의 값을 저장한다.&lt;/li&gt;
&lt;li&gt;동기화를 신경 쓸일 없어서 성능적으로 높지만 메모리 소모가 많을수도 있다.&lt;/li&gt;
&lt;/ul&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;&lt;b&gt;synchronized&lt;/b&gt;는 &lt;b&gt;공유 자원&lt;/b&gt;에 대해 &lt;b&gt;동시 접근을 제어&lt;/b&gt;합니다. 즉, 하나의 인스턴스를 여러 스레드가 순차적으로 접근하게 하여 &lt;b&gt;동기화&lt;/b&gt;하는 방식입니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ThreadLocal&lt;/b&gt;은 &lt;b&gt;동시성을 회피&lt;/b&gt;하는 방식입니다. &lt;b&gt;공유 자원 없이&lt;/b&gt; 각 스레드에 고유한 인스턴스를 가지므로 동시성 문제가 발생하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/74</guid>
      <comments>https://devseok.tistory.com/74#entry74comment</comments>
      <pubDate>Tue, 12 Nov 2024 15:11:40 +0900</pubDate>
    </item>
    <item>
      <title>Monitor</title>
      <link>https://devseok.tistory.com/73</link>
      <description>&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;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #404040; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;mutual exclusion(상호 배제) &amp;ndash; 하나의 스레드만이 특정한 시점에 메서드를 실행할 수 있습니다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;locks&lt;/b&gt;기법을 사용합니다.&lt;/li&gt;
&lt;li&gt;cooperation &amp;ndash; 특정 조건이 충족될 때까지 스레드를 대기시키는 기능.&lt;span&gt;&amp;nbsp;&lt;/span&gt;wait-set을 사용합니다.&lt;/li&gt;
&lt;/ul&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;h3 id=&quot;3-모니터의-특징&quot; style=&quot;background-color: #ffffff; color: #404040; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;모니터의 특징&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #404040; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;모니터는 동시성 프로그래밍에 세 가지 주요 기능을 제공합니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #404040; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 번에 하나의 스레드만 critical code section에 mutually exclusive(상호 배제적)으로 접근할 수 있습니다.&lt;/li&gt;
&lt;li&gt;모니터에서 실행 중인 스레드가 특정 조건이 충족될 때까지 차단될 수 있습니다.&lt;/li&gt;
&lt;li&gt;스레드는 조건이 충족될 때 다른 스레드에 알릴 수 있습니다.&lt;/li&gt;
&lt;/ul&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;h3 data-ke-size=&quot;size23&quot;&gt;건물로 비유 하자면..?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;스레드 1, 스레드 2&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;복도 -&amp;gt; 전용실 -&amp;gt; 대기실 총 3가지로 비유 하고자 합니다.&lt;/span&gt;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;Hallway : 복도&amp;nbsp; ( 공유 자원이 있는곳 )&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Exclusive Room : 전용실 ( 실제 행위 )&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;Wait Room : 대기실 ( 블락킹 )&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/div&gt;
&lt;div style=&quot;background-color: #ffffff; color: #000000; text-align: start;&quot;&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드1 , 스레드2에서 두개의 스레드중에 JVM이 동일시에 우선순위를 갇는 경우 FIFO(선입 선출 방식 )로 구분한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드1이 들어와서 복도로 접근을 하여 전용실에서 일을 보고 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드2도 복도로 들어왔지만 전용실로 들어갔다가 사람이 있어서 결국 대기실로 다시 왔다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드1이 전용실에서 끝났다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드2가 대기실에 있다가 스레드1이 끝나는걸 확인하여 전용실로 들어갔다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot;&gt;스레드2가 전용실에서 일을 끝나고 왔다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;253&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xVb7F/btsKEuXuIRt/Nr6YXoPY3xxliJ5J078Tpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xVb7F/btsKEuXuIRt/Nr6YXoPY3xxliJ5J078Tpk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xVb7F/btsKEuXuIRt/Nr6YXoPY3xxliJ5J078Tpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxVb7F%2FbtsKEuXuIRt%2FNr6YXoPY3xxliJ5J078Tpk%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;253&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;253&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 synchronized를 사용한다면 다음과 같습니다.&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;첫 번째 스레드 (스레드1)&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;synchronized&lt;/b&gt; 블록에 들어갑니다.&lt;/li&gt;
&lt;li&gt;해당 객체의 &lt;b&gt;모니터 락&lt;/b&gt;을 얻고, &lt;b&gt;전용실&lt;/b&gt;에서 작업을 시작합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;두 번째 스레드 (스레드2)&lt;/b&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;b&gt;스레드1이 이미 락을 가지고 있어서&lt;/b&gt; 접근할 수 없습니다.&lt;/li&gt;
&lt;li&gt;스레드2는 **wait()**를 호출하여 &lt;b&gt;대기실로 가는 상태&lt;/b&gt;가 됩니다.&lt;/li&gt;
&lt;li&gt;**wait()**는 해당 스레드가 &lt;b&gt;락을 놓고 대기&lt;/b&gt;하도록 하며, 다른 스레드가 락을 해제할 때까지 기다립니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스레드1이 작업을 마친 후&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드1이 작업을 마치고 &lt;b&gt;synchronized&lt;/b&gt; 블록을 벗어나면서 &lt;b&gt;모니터 락&lt;/b&gt;을 &lt;b&gt;해제&lt;/b&gt;합니다.&lt;/li&gt;
&lt;li&gt;이 시점에서 대기 중인 &lt;b&gt;스레드2&lt;/b&gt;는 &lt;b&gt;notify()&lt;/b&gt; 또는 **notifyAll()**을 호출하여 대기 중인 스레드를 깨웁니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스레드2&lt;/b&gt;:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;notify()&lt;/b&gt; 호출로 스레드2가 깨워지면, 스레드2는 &lt;b&gt;다시 복도에 들어가 전용실로 들어가&lt;/b&gt; 작업을 수행할 수 있습니다.&lt;/li&gt;
&lt;li&gt;이때 스레드2는 &lt;b&gt;wait()&lt;/b&gt; 상태에서 벗어나고, 다시 &lt;b&gt;synchronized&lt;/b&gt; 블록을 얻을 수 있게 됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;&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;a href=&quot;https://jayhyun-hwang.github.io/2021/08/23/Monitor/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://jayhyun-hwang.github.io/2021/08/23/Monitor/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/73</guid>
      <comments>https://devseok.tistory.com/73#entry73comment</comments>
      <pubDate>Tue, 12 Nov 2024 14:31:42 +0900</pubDate>
    </item>
    <item>
      <title>spinlock에 대해서</title>
      <link>https://devseok.tistory.com/72</link>
      <description>&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731376879313&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    private final AtomicBoolean lock = new AtomicBoolean(false);

    public void lock() {
        while (!lock.compareAndSet(false, true)) {
            System.out.println(&quot;빈 루프...&quot;);
            long currentTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread().getName() + &quot; - Critical section at &quot; + currentTime + &quot; ms&quot;);
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + &quot; - 락 획득&quot;);
    }


    public void unlock() {
        // 락을 해제
        lock.set(false);
        System.out.println(Thread.currentThread().getName() + &quot; - 락 해제&quot;);
    }

    public static void main(String[] args) {

        Main spinLock = new Main();
        Runnable criticalSection = () -&amp;gt; {
            spinLock.lock();
            try {
                long currentTime = System.currentTimeMillis();
                System.out.println(Thread.currentThread().getName() + &quot; - Critical section at &quot; + currentTime + &quot; ms&quot;);

                // 중요한 작업 수행
                Thread.sleep(100); // 예시로 스레드가 락을 잡고 쉬도록 함
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            } finally {
                System.out.println(&quot;락 풀기&quot;);
                spinLock.unlock();
            }
        };

        Thread t1 = new Thread(criticalSection, &quot;Thread 1&quot;);
        Thread t2 = new Thread(criticalSection, &quot;Thread 2&quot;);

        t1.start();
        t2.start();

        System.out.println(&quot;test&quot;);
    }
}&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;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1731376903051&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;빈 루프...
Thread 1 - 락 획득
Thread 1 - Critical section at 1731375397898 ms
락 해제
Thread 1 - 락 해제
빈 루프...
Thread 2 - 락 획득
Thread 2 - Critical section at 1731375397999 ms
Thread 2 - 락 해제&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;스핀락이 장단점&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;장점 : 만약에 긴 대기시간이면 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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/72</guid>
      <comments>https://devseok.tistory.com/72#entry72comment</comments>
      <pubDate>Tue, 12 Nov 2024 11:10:23 +0900</pubDate>
    </item>
    <item>
      <title>Context switching</title>
      <link>https://devseok.tistory.com/71</link>
      <description>&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;공부하는 도중 Context&amp;nbsp;switching이란 무엇인가 한번 공부를 해봤습니다.&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;먼저 Context란 &lt;span style=&quot;background-color: #f8f9fa; color: #212529; text-align: start;&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;다시 말해 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot;&gt;Process의 경우 현재 프로세스가 중단 되었을 때, 중단된 시점 부터 다시 프로세스를 실행하기 위한 정보를 Context라고 부른다&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;3-context-switching이란&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;Context Switching이란?&lt;/h3&gt;
&lt;blockquote style=&quot;color: #212529; text-align: start;&quot; data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;작업의 주체가 현재 Context를 잠시 중단하고 다른 Context를 실행하는 것을 Context Switching이라 한다.&lt;/span&gt;&lt;/p&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 프로세서가 지금까지 실행되던 프로세스(A)를 중지하고 다른 프로세스(B)의 PCB정보를 바탕으로 프로세스(B)를 실행하는 것을 Process Context Switching이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;동일한 프로세스 속에서 하나의 쓰레드(a)를 중지하고 다른 쓰레드(b)의 TCB정보를 바탕으로 쓰레드 (b)를 실행하는 것을 Thread Context Switching이라고 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;4-process-context-switching-vs-thread-context-switching&quot; style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;process context switching vs thread context switching&lt;/h3&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;프로세스는 하나 이상의 쓰레드를 포함한다. 이 쓰레드들은 고유한 Stack영역의 메모리와 고유한 registers를 할당 받으며 Heap영역의 메모리에서 선언된 데이터는 서로 공유한다.&lt;br /&gt;동일한 프로세스 속에서 thread context switching이 발생할 경우 processor는 stack영역의 주소와 registers 주소를 포함한 thread의 context 정보만을 변경하면 된다.&lt;br /&gt;하지만 process context switching이 발생할 경우 processor는 thread의 context뿐만 아니라 process의 context까지 모두 변경해야 한다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;769&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2628v/btsKDA4wiAH/VHmRnOZ77t3KxCRpy9nHqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2628v/btsKDA4wiAH/VHmRnOZ77t3KxCRpy9nHqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2628v/btsKDA4wiAH/VHmRnOZ77t3KxCRpy9nHqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2628v%2FbtsKDA4wiAH%2FVHmRnOZ77t3KxCRpy9nHqK%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;1280&quot; height=&quot;769&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;769&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>DevOps</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/71</guid>
      <comments>https://devseok.tistory.com/71#entry71comment</comments>
      <pubDate>Tue, 12 Nov 2024 09:59:29 +0900</pubDate>
    </item>
    <item>
      <title>에프랩(F-Lab) Java Backend 1개월 후기</title>
      <link>https://devseok.tistory.com/70</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;521&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhfeNT/btsKDhCUC9H/Xl1ojTAzpUhjAtpirP56PK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhfeNT/btsKDhCUC9H/Xl1ojTAzpUhjAtpirP56PK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhfeNT/btsKDhCUC9H/Xl1ojTAzpUhjAtpirP56PK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhfeNT%2FbtsKDhCUC9H%2FXl1ojTAzpUhjAtpirP56PK%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;934&quot; height=&quot;521&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;521&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;국비지원 출신으로 처음 개발을 시작하였다. 판교에서 6개월 동안 자바 백엔드 국비지원 학원을 다녔고, 어찌어찌해서 첫 회사에 입사를 하였다, 입사해보니, Java언어가 아닌 PHP 개발을 하였다 거기서부터 나의 커리어는 PHP 개발자로 입문하였다. PHP 개발자들이 주변에 많이 있었다 하지만 다들 나이 때가 정말 많았다. 젊은 나이에 PHP 잘하시는 사람도 했던 분들도 잘 없다고 오히려 강점이라고 하였다&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;그 덕분인지 모를까 외주 개발 대부분 PHP로 개발을 하여 성공적으로 끝났다. 첫 회사에서 2년을 지나서 바로 쉽게 이직할 수 있었다. 무려 연봉도 직전 연봉에 +1000만원을 올려서 이직을 했다. 주변에서 이직할 때 쫌 쉬면서 이직하라고 했지만 금요일 퇴사후 바로 그 다음주 입사를 하였다. 조건과 규모 그리고 연봉까지 전부 만족 하였다.&amp;nbsp; 하지만 2년동안 개발을 하였지만 점점 PHP대한 흥미는 잃어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;배움에 대한 끝이 없다고 하였다. 다들 하나의 언어만 잘하면, 다른 언어는 금방 따라 갈수 있어&lt;/span&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 말을 자주 하곤 듣곤 한다. 그래서 그런지 언어에 변환에 대한 자신감이 어느정도 있었다. 그리고 개발 입문 시기에 Java 언어로 개발자로 입문 하여 어느정도 자신 있었다. 그리고 공부 한다는 빌미로 PHP 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6개월 공부후.. 인프런에 인터넷 강의만 60만원치 결제를 하여 독서실에 모니터랑 노트북을 구매해서 공부를 하였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PHP에서 라라벨이라는 프레임워크가 있었는데 거기서 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;Eloquent ORM 이라는걸 사용 해봤고, 특이하게 자바 진영에서는 JPA를 사용하고 있었다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;서론이 너무 길었지만 결국 Java 개발자로 이직은 성공 하였지만 여러가지 사유로 몇번의 실패를 하였다.&amp;nbsp;&lt;/span&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;&lt;b&gt;1. 왜 F-Lab을 신청 했는가?&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PHP에서 Java로 언어 변경을 하면서 실패를 맛본 이후에 해성처럼 등장하는 F-Lab을 소개하는 영상을 몇개 보았다. 무엇인가 나의 이야기와 비슷했다. 그때 당시에 기억이 가물가물하지만 금액이 상당히 높았다. 나의 급여에서 결국 포기를 하고, 몇년이 지난, 금액이 매우 저렴해져서 다시 찾아와 신청 하게 되었다.&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;&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;하지만 나의 생각은 조금 다르다. 결국엔 빈부격차가 커질것이다.&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;p data-ke-size=&quot;size16&quot;&gt;업무상에 GPT등 상당히 많이 나도 사용하였다.&amp;nbsp; AI가 나오기전에도 구글링을 할때 개발자가 기본적으로 어떤 이유 때문인지 실마리를 알아야 검색과 대응이 가능하였다. 기본적인 테크닉이나 CS 관련 내용을 잘 알아야 검색도 잘할수 있다는 말이다. 현재까지 내가 느끼는 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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 이번 달 동안 무엇을 배웠는가&lt;/b&gt;&lt;/h3&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; 물론 멘티는 멘토님이 누구인지 모른다. 대략 기억으론 6명 정도에서 선택 할수있었다. 내가 선택한 멘토님의 커리어는 상당하였고, 내가 원하는분이셨다.&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;1. 카카오 초창기 개발자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 대용량 트레픽 경험&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. MSA 개발 경험&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;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nn9h6/btsKDT9sO4i/fLQ564O5nKadgtqOzGyH1k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nn9h6/btsKDT9sO4i/fLQ564O5nKadgtqOzGyH1k/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nn9h6/btsKDT9sO4i/fLQ564O5nKadgtqOzGyH1k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNn9h6%2FbtsKDT9sO4i%2FfLQ564O5nKadgtqOzGyH1k%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;324&quot; height=&quot;259&quot; data-origin-width=&quot;735&quot; data-origin-height=&quot;588&quot;/&gt;&lt;/span&gt;&lt;/figure&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;그리고 수업의 날이 다가 왔다. Java 관련 내용을 신나게 나를 팼다.&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;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;main 함수는 왜 static일까에 대해서 찾아보기 등등 아주 기초적인 지식을 이야기 했다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&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;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;몇가지 이야기 하지만 시멘틱 버저닝이란?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;박싱과 언박싱이란? 그리고 어떻게 피할수 있고 자바에서 편의성은 있지만 성능이점?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&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;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;자바 관련 기초 CS등과 마지막엔 기초 CS에서 조금 더 나아갔다.&lt;/span&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;&lt;b&gt;3. 1개월 동안 만나본 멘토님의 수준은 어때보였는가&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 뒤로 갈수록 개발에 대한 초고수의 느낌이 확실히 난다. 또한 대용량 트레픽을 하셨기 때문에 그에 대한 지식이 매우 많으신거 같다. 앞으로 현재는 1개월차이지만 2~3개월차에 대한 기대감이 너무 부풀고 있다. 질문도 여쭤보면 대답좀 잘해주신다. 아직까진 질문을 하진 않았지만 앞으로 많이 더 할꺼 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/chFZa3/btsKEGVWvqE/OCXrtNvRrwcjP502phBGGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/chFZa3/btsKEGVWvqE/OCXrtNvRrwcjP502phBGGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/chFZa3/btsKEGVWvqE/OCXrtNvRrwcjP502phBGGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FchFZa3%2FbtsKEGVWvqE%2FOCXrtNvRrwcjP502phBGGK%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;500&quot; height=&quot;557&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;557&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 앞으로의 각오&lt;/b&gt;&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;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 최근 1개월 멘토링에 대한 자유로운 감상 및 후기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1개월차 이지만 정말 국비지원과 인터넷강의 등등 수강이란 차원이 다르다. 하나의 기술에 깊게 파고 드는 수업이다. 또한 기술을 선택 할때 얻는것이 있다면 잃는것도 있다는 말씀을 하셨다. 이러한 고민을 해보진 않았지만 이러한 고민도 정말 좋은 지적 같다 수강시 금액을 지불하는건 누군가에겐 큰돈이지만 유튜브에서 보면 &lt;b&gt;나의 커리어 개발에 투자를 하여라&lt;/b&gt; 라는 글귀를 본적이 있다. 나는 지금 나에게 투자를 하고 있고 그에 맞게 공부를 하여야된다. 앞으로 더욱 열심히 해서 좋은 개발자가 될것이다.&lt;/p&gt;</description>
      <category>일상 및 생각</category>
      <category>f-lab</category>
      <category>f-lab 1개월 후기</category>
      <category>java backend 멘토링</category>
      <category>에프랩</category>
      <category>에프랩 1개월 후기</category>
      <category>후기</category>
      <author>오개발</author>
      <guid isPermaLink="true">https://devseok.tistory.com/70</guid>
      <comments>https://devseok.tistory.com/70#entry70comment</comments>
      <pubDate>Mon, 11 Nov 2024 11:08:46 +0900</pubDate>
    </item>
  </channel>
</rss>