<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>물음표&amp;amp;느낌표</title>
    <link>https://doingsomething.tistory.com/</link>
    <description>github.com/minyun02
myun02302@gmail.com</description>
    <language>ko</language>
    <pubDate>Sun, 19 Jul 2026 04:04:41 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>삼공비</managingEditor>
    <image>
      <title>물음표&amp;amp;느낌표</title>
      <url>https://tistory1.daumcdn.net/tistory/2985198/attach/d56bd6690b7441c381f51eaad1b114c9</url>
      <link>https://doingsomething.tistory.com</link>
    </image>
    <item>
      <title>JPA로 soft delete를 어떻게 구현할까?</title>
      <link>https://doingsomething.tistory.com/125</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Soft Delete vs Hard Delete&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;soft delete(논리 삭제)
&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;주로 isDeleted와 같은 boolean 필드를 만들거나 삭제 날짜 필드를 사용한다&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;hard delete(물리 삭제)
&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;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;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA로 어떻게 Soft Delete를 어떻게 구현할까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SQLDelete, @Where
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SQLDelete(sql = &quot;삭제 쿼리&quot;)&lt;/li&gt;
&lt;li&gt;데이터를 삭제하는 경우에 @SQLDelete의 sql 옵션으로 지정해놓은 쿼리가 나간다&lt;/li&gt;
&lt;li&gt;여기서 soft delete에 사용하는 필드값을 변경해주는 update 문을 작성해서 soft delete를 구현한다&lt;/li&gt;
&lt;li&gt;그리고 데이터를 조회하는 경우에는 삭제된 데이터를 제외하고 조회하는 조건을 기본값으로 지정할 수 있다&lt;/li&gt;
&lt;li&gt;이때 사용하는 애너테이션이 @Where이다&lt;/li&gt;
&lt;li&gt;@Where(clause = &quot;isDeleted = false&quot;) 이렇게 삭제되지 않은 데이터를 조회하는 조건을 걸어둔다&lt;/li&gt;
&lt;li&gt;@SQLRestriction 애너테이션은 하이버네이트 5.4 버전 이상부터 @Where을 대신해서 사용된다&lt;/li&gt;
&lt;li&gt;@SQLRestriction(&quot;isDeleted = false&quot;) 이렇게 사용한다&lt;/li&gt;
&lt;li&gt;모든 조회 쿼리에 조건이 붙어서 나가기 때문에 만약에 삭제된 데이터를 조회해야하는 경우가 생기면 네이티브 쿼리를 이용하는 등, 다른 방법이 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@SQLDelete(sql = &quot;UPDATE table SET isDeleted = true WHERE id = ?&quot;)
@SQLRestriction(&quot;isDeleted = false&quot;)
public class SampleEntity() {
	...
}

&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;@PreRemove
&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;다만 soft delete를 구현하려면 추가적인 처리가 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Entity
public class SampleEntity() {
	...

	@PreRemove
	private void preRemove() {
		// 수행 로직
	}
}

&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;@SQLDelete VS @PreRemove
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@SQLDelete
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;remove() 메서드가 호출되면 실행된다&lt;/li&gt;
&lt;li&gt;Delete 쿼리 대신 지정된 SQL 문이 실행된다&lt;/li&gt;
&lt;li&gt;SQL을 직접 실행한다&lt;/li&gt;
&lt;li&gt;구현이 간단하고 직관적이다&lt;/li&gt;
&lt;li&gt;하지만 데이터베이스에 종속적인 SQL문을 작성해야한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@PreRemove
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;remove() 메서드가 호출되면 실행된다&lt;/li&gt;
&lt;li&gt;엔티티의 상태를 변경할 수 있지만, 기본적으로 Delete 쿼리가 실행된다&lt;/li&gt;
&lt;li&gt;코드 내에서 로직을 실행해서 좀 더 유연한 로직을 구현할 수 있다&lt;/li&gt;
&lt;li&gt;Delete 쿼리가 실행되니까 soft delete를 구현하려면 추가 처리가 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <category>hard delete</category>
      <category>JPA</category>
      <category>preRemove</category>
      <category>soft delete</category>
      <category>sqldelete</category>
      <category>sqlrestriction</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/125</guid>
      <comments>https://doingsomething.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 25 Jun 2024 22:59:45 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트를 하다가 JPA의 지연 로딩과 프록시를 만났다</title>
      <link>https://doingsomething.tistory.com/124</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;프로젝에는 Match 엔티티는 inviter라는 Team 엔티티와 관계를 맺고 있다. 이 관계는 지연 로딩으로 설정되어 있다. 매칭을 수락하는 로직이 있는데 여기서 매칭을 보내온 inviter의 현재 상태를 확인해야 했는데, 여기서 생각과 다른 상황이 발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719234693591&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;matchValidator.validateTeamStatus(match.getInviter().getStatus());&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;위 코드가 그 상황이다. 지연 로딩을 사용할 때 조회되지 않은 엔티티는 프록시 객체 상태라는 개념은 알고는 있었다. 그리고 해당 엔티티가 필요한 순간에 조회 쿼리가 나간다는 사실도 알고 있었다. 그렇지만 정확히 어떤 시점이 해당 엔티티가 필요한 시점인지는 잘 모르고 있었다. 처음에는 match.getInviter() 이 부분에서 inviter라는 Team 엔티티가 필요하니까 조회 쿼리가 나갈거라고 생각했다. 로그를 보니 match.getInviter().getStatus() 처럼 실제 Team 정보가 필요한 순간에 조회 쿼리가 발생했다.&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;왜 그럴까? getInviter()로 inviter라는 Team 엔티티를 가져오는 순간까지는 프록시 객체를 전달하기 때문이다. 이 프록시 객체에는 inviter의 아이디값만을 가지고 있는 빈 겁데기 객체다. 그렇기 때문에 상태값이 필요한 순간에는 더 이상 진짜인 척 할 수 없기 때문에 inviter를 조회해온다.&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;이걸 알고 나니까 한가지 생각이 들었다. getStatus()로 상태를 가져오려는 inviter 엔티티가 DB에서 삭제된 상태라면 무슨 일이 생길까? 엔티티가 존재하지 않아서 EntityNotFoundException가 발생한다. 이런 이유로 match.getInviter().getStatus()는 안전하지 않다고 판단했고 inviter의 아이디값으로 실제 inviter 엔티티를 조회해서 존재 여부를 확인하는 방식으로 변경했다.&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;위에서 말했듯 inviter 프록시 객체가 가지고 있는 아이디값을 사용해서 좀 더 안전하게 처리할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1719235246730&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Team inviter = teamService.getTeam(match.getInviter().getTeamId());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>TIL</category>
      <category>JPA</category>
      <category>lazy fetch</category>
      <category>Proxy</category>
      <category>지연 로딩</category>
      <category>프록시</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/124</guid>
      <comments>https://doingsomething.tistory.com/124#entry124comment</comments>
      <pubDate>Mon, 24 Jun 2024 22:21:03 +0900</pubDate>
    </item>
    <item>
      <title>mocking, stubbing은 뭐고 어떤 차이가 있을까?</title>
      <link>https://doingsomething.tistory.com/123</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mocking&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Stubbing&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용 예시&lt;/h2&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;def &quot;검색 조건이 있는 경우&quot;() {
    given:
    def matchRepository = Mock(MatchRepository) // Mocking
    def matchService = new MatchService(matchRepository)
    def teamId = 1L
    def matchSearchDTO = new MatchSearchDTO()

    and: // Stubbing
    matchRepository.findAllMatchesByInviteeId(teamId, _, _) &amp;gt;&amp;gt; [new Match(), new Match(), new Match(), new Match()]

    when:
    def result = matchService.getReceivedMatchList(teamId, matchSearchDTO)

    then:
    result.size() == 4 // Stubbing?
    1 * matchRepository.findAllMatchesByInviteeId(teamId, _, _)
}

&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;/li&gt;
&lt;li&gt;이 테스트 코드는 spock이라는 테스트 프레임워크를 가지고 만들었기 때문에 groovy를 사용했다&lt;/li&gt;
&lt;li&gt;given 블록에서는 테스트에 필요한 재료들을 만들어준다&lt;/li&gt;
&lt;li&gt;해당 로직에서는 매치를 DB에서 조회해야 하기 때문에 MatchRepository가 필요하다&lt;/li&gt;
&lt;li&gt;def matchRepository = Mock(MatchRepository) 라인에서 가짜 MatchRepository 객체를 만들어준다&lt;/li&gt;
&lt;li&gt;이 작업이 Mocking이다&lt;/li&gt;
&lt;li&gt;그리고 이제 MatchRepository의 매치 조회 메서드가 호출됐을 때 반환값을 정의해줘야 한다&lt;/li&gt;
&lt;li&gt;and절에서 이 작업이 이루어지고 이걸 Stubbing이라고 부른다&lt;/li&gt;
&lt;li&gt;그러면 then 절에서 result 크기가 4인지 비교하는 건 Stubbing일까?&lt;/li&gt;
&lt;li&gt;처음 Stubbing 개념을 잘 이해하지 못했을 때는 4라는 값을 정의한 거니까 이 부분도 Stubbing이라고 생각했다&lt;/li&gt;
&lt;li&gt;하지만 찾아보니까 result의 크기를 비교하는 부분은 설정한 결과를 검증하는 assertion에 해당한다&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/123</guid>
      <comments>https://doingsomething.tistory.com/123#entry123comment</comments>
      <pubDate>Sat, 22 Jun 2024 23:56:46 +0900</pubDate>
    </item>
    <item>
      <title>CopyOnWriteArrayList은 어떻게 동시성 문제를 해결해주나?</title>
      <link>https://doingsomething.tistory.com/122</link>
      <description>&lt;h1&gt;CopyOnWriteArrayList&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 세이프 한 ArrayList&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;h1&gt;어떻게 동시성을 해결할까?&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;ArrayList는 동시성 문제가 발생하면 어떤 일이 생길까?&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&gt;ArrayIndexOutOfBoundsException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 스레드가 요소 추가나 삭제로 ArrayList의 크기를 변경하는 동안 다른 스레드가 동일한 ArrayList의 요소에 접근하려고할 때 인덱스 초과 예외가 발생할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ConcurrentModificationException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;한 스레드가 ArrayList를 반복하는 동안 다른 스레드가 요소를 변경하면 반복자가 상태를 감지해서 해당 예외가 발생한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/122</guid>
      <comments>https://doingsomething.tistory.com/122#entry122comment</comments>
      <pubDate>Fri, 21 Jun 2024 01:00:30 +0900</pubDate>
    </item>
    <item>
      <title>임계영역을 알아보고 어떤 방법으로 구현할 수 있을까?</title>
      <link>https://doingsomething.tistory.com/121</link>
      <description>&lt;h1&gt;임계영역(Critical Section)&lt;/h1&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;h1&gt;임계영역 특징&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상호 배제 (Mutual Exclusion)
&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;한정된 대기 (Bounded Waiting)
&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;진행 가능성
&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;공유 자원 보호
&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;데드락 회피
&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;h1&gt;임계영역을 구현하는 방법&lt;/h1&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;synchronized 키워드
&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;Lock 객체
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lock(), unlock() 사이의 코드 블록이 임계영역이 된다&lt;/li&gt;
&lt;li&gt;ReentrantLock을 가장 많이 사용한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Lock 객체의 구현체로 이름처럼 재진입이 가능한 락이다&lt;/li&gt;
&lt;li&gt;즉, 동일한 스레드가 이미 락을 획득한 상태에서 다시 락을 요청할 수 있다&lt;/li&gt;
&lt;li&gt;공정성 옵션을 줄 수 있는데 생성자에 true를 주는 방식이다.&lt;/li&gt;
&lt;li&gt;이 옵션을 주면 대기 중인 스레드 중 가장 오래 기다린 스레드에게 락을 부여한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class Counter {
	private int count = 0;
	private Lock lock = new ReentrantLock();

	public void increment() {
		lock.lock();
		try {
			count++;
		} finally {
			lock.unlock();
		}
	}

	public int getCount() {
		lock.lock();
		try {
			return count;
		} finally {
			lock.unlock();
		}
	}
}

&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;ReadWriteLock
&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;ReentrantReadWriteLock을 사용하면 여러 스레드가 동시에 읽기 작업을 수행할 수 있다&lt;/li&gt;
&lt;li&gt;쓰기 작업은 단일 스레만 수행할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public class Counter {
    private int count = 0;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();

    public void increment() {
        lock.writeLock().lock();
        try {
            count++;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getCount() {
        lock.readLock().lock();
        try {
            return count;
        } finally {
            lock.readLock().unlock();
        }
    }
}

&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;Atomic 변수
&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;pre class=&quot;axapta&quot;&gt;&lt;code&gt;public class Counter {
    private final AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}

&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;Semaphore
&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;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class Resource {
    private final Semaphore semaphore = new Semaphore(1);

    public void access() {
        try {
            semaphore.acquire();
            // 임계영역
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            semaphore.release();
        }
    }
}

&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;StampedLock
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ReadWriteLock과 비슷하지만, 낙관적 잠금(optimistic lockign) 기능을 제공한다&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;volatile 키워드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;volatile 키워드는 원자성을 보장하지 않고 가시성만 보장한다&lt;/li&gt;
&lt;li&gt;가시성이란 한 스레드가 volatile 변수를 수장하면, 다른 스레드에서는 항상 수정된 값을 읽게 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <category>critical section</category>
      <category>ReentrantLock</category>
      <category>동시성</category>
      <category>스레드</category>
      <category>임계영역</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/121</guid>
      <comments>https://doingsomething.tistory.com/121#entry121comment</comments>
      <pubDate>Tue, 18 Jun 2024 22:33:04 +0900</pubDate>
    </item>
    <item>
      <title>스레드가 코어 수보다 많아지면 어떤 문제가 생기고 어떻게 해결할 수 있나?</title>
      <link>https://doingsomething.tistory.com/120</link>
      <description>&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;데이터 경쟁 (Race Condition)
&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;데드락
&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;라이브락
&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;기아 (Starvation)
&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;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;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;메모리 누수
&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;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;/ul&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;너무 많은 스레드가 생기면 CPU 자원 분배에 부담이 생기고 컨텍스트 스위칭 오버헤드가 증가하며 성능이 저하된다&lt;/li&gt;
&lt;li&gt;스레드 경쟁으로 자원 획득을 위해 대기하는 시간이 길어져 성능이 저하된다&lt;/li&gt;
&lt;/ul&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;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ExecutorService와 같은 스레드 풀을 사용해서 스레드 수를 제어하고 관리한다&lt;/li&gt;
&lt;li&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;자바에서는 ExecutorService로 구현할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;적절한 스레드 수를 결정하는 기준은 앱 특성과 실행 환경을 고려해야 한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cpu 바운드
&lt;ul style=&quot;list-style-type: disc;&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;li&gt;그렇지만 너무 많은 스레드는 컨텍스트 스위칭 오버헤드를 증가시킬 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;IO 바운드
&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;cpu 사용량은 비교적 낮고, IO 대기 시간이 많은 작업이다&lt;/li&gt;
&lt;li&gt;코어 수 * (1 + 평균 대기 시간 / 평균 작업 시간)으로 스레드 수를 구할 수 있다&lt;/li&gt;
&lt;li&gt;IO 바운드 작업은 더 많은 스레드를 사용할 수 있다&lt;/li&gt;
&lt;li&gt;이유는 스레드가 IO작업을 기다리는 동안 다른 스레드가 cpu를 사용하도록해서 자원을 효율적으로 활용하는게 중요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;NIO
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자바 1.4에서 도입된 새로운 IO API다&lt;/li&gt;
&lt;li&gt;비동기식 방식과 채널, 버퍼를 사용해서 IO 작업을 효율적으로 처리할 수 있게 해준다&lt;/li&gt;
&lt;li&gt;비동기식 방식을 지원
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드가 IO 작업이 완료될 때까지 블로킹하지 않는다&lt;/li&gt;
&lt;li&gt;기존의 블로킹 IO방식에서는 각 클라이언트 요청마다 별도의 스레드를 할당해야 했다&lt;/li&gt;
&lt;li&gt;이는 블로킹 상태에 빠지게하고 IO작업이 완료될 때까지 아무 작업도 수행하지 않고 대기하게 만든다&lt;/li&gt;
&lt;li&gt;이러면 많은 동시 접근을 처리할 때 스레드 수가 급격하게 늘어나는 문제가 생긴다&lt;/li&gt;
&lt;li&gt;NIO는 IO 작업이 완료될 때까지 스레드가 블로킹되지 않아서 적은 수의 스레드로 많은 수의 동시 연결을 처리할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;채널과 버퍼를 사용해서 더 유연하고 효율적인 IO 처리가 가능하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <category>NIO</category>
      <category>nio io 차이점</category>
      <category>스레드 문제점</category>
      <category>스레드 풀</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/120</guid>
      <comments>https://doingsomething.tistory.com/120#entry120comment</comments>
      <pubDate>Mon, 17 Jun 2024 22:43:07 +0900</pubDate>
    </item>
    <item>
      <title>ConcurrentHashMap은 어떤 방식으로 동시성 문제를 해결할까?</title>
      <link>https://doingsomething.tistory.com/119</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;ConcurrentHashMap&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;세그먼트 락킹
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java8 이전
&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;Java8
&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;/li&gt;
&lt;li&gt;락 프리 읽기 (Lock-free Reads)
&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동시성 문제 해결
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;락 프리 읽기와 CAS 연산을 통해 쓰기 작업이 필요한 경우에도 락을 최소화하여 성능 저하를 방지한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;세그먼트 락킹 동작 원리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;java8 이후 변경 사항
&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;Compare And Swap 연산
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CAS 연산을 통해 락을 사용하지 않고도 안전하게 값을 업데이트할 수 있다&lt;/li&gt;
&lt;li&gt;이는 쓰기 작업에서는 성능을 향상한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;버킷과 세그먼트의 차이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <category>ConcurrentHashMap</category>
      <category>Java</category>
      <category>동시성</category>
      <category>버킷</category>
      <category>세그먼트</category>
      <category>자바</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/119</guid>
      <comments>https://doingsomething.tistory.com/119#entry119comment</comments>
      <pubDate>Sun, 16 Jun 2024 17:57:56 +0900</pubDate>
    </item>
    <item>
      <title>프로세스가 있는데 왜 스레드가 필요한가?</title>
      <link>https://doingsomething.tistory.com/118</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로세스와 스레드의 차이점&lt;/b&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;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;li&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;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;주로 [[프로세스 간 통신(IPC)]]을 위해서 사용하고 이 경우 프로세스는 특별히 할당된 공유 메모리 영역에 접근할 수 있다&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;/li&gt;
&lt;li&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;예를 들면, 리눅스에서는 ptrace로 다른 프로세스의 메모리를 읽거나 쓸 수 있다&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그렇지만 모든 메모리에 접근이 가능한 건 아니다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프로세스 간 전환(컨텍스트 스위칭)은 비교적 무겁고 많은 비용을 소모한다&lt;/li&gt;
&lt;li&gt;컨텍스트 스위칭
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU가 다른 프로세스로 전환될 때 발생하는 작업이다&lt;/li&gt;
&lt;li&gt;멀티태스킹을 지원하기 위해서는 여러 프로세스를 번갈아 가며 실행해줘야 한다&lt;/li&gt;
&lt;li&gt;이 과정에서 현재 실행 중인 프로세스의 상태를 저장하고, 실행할 프로세스의 상태를 로드한다&lt;/li&gt;
&lt;li&gt;프로세스의 상태 정보는 Process Control Block(PCB)에 담긴다&lt;/li&gt;
&lt;li&gt;왜 프로세스 컨텍스트 스위칭은 오래 걸리나?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복잡한 상태 저장 및 복원
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU의 모든 레지스터와 프로세스의 상태를 저장하고 복원하는데 오래 걸린다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;캐시 무효화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스를 바꿀 때 남아있는 CPU 캐시를 없애고 재적재하는 작업이 필요하다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;li&gt;스레드 간 전환은 프로세스보다 비용도 적게 들고 빠르다!&lt;/li&gt;
&lt;li&gt;왜 비용이 적게 들고 빠를까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스레드 컨텍스트 스위칭 과정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 스레드 상태 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;현재 CPU 레지스터 상태를 저장한다&lt;/li&gt;
&lt;li&gt;Thread Control Block(TCB)에 스레드 상태 정보를 저장한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스레드 vs 프로세스 컨텍스트 스위칭
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하나의 프로세스 내에서 여러 작업을 동시에 수행할 수 있고 이는 CPU 사용률을 높이고, 응용 프로그램 성능을 향상한다&lt;/li&gt;
&lt;li&gt;IO 작업 같이 시간이 오래 걸리는 작업을 비동기적으로 처리해서 다른 작업을 중단하지 않고 처리할 수 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>TIL</category>
      <category>Process</category>
      <category>thread</category>
      <category>스레드</category>
      <category>스레드와 프로세스 차이점</category>
      <category>쓰레드</category>
      <category>프로세스</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/118</guid>
      <comments>https://doingsomething.tistory.com/118#entry118comment</comments>
      <pubDate>Thu, 13 Jun 2024 23:21:40 +0900</pubDate>
    </item>
    <item>
      <title>프록시 객체란 무엇인가?</title>
      <link>https://doingsomething.tistory.com/117</link>
      <description>&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;프록시는 실제 객체 대신 클라이언트와 상호 작용하며, 필요할 때 실제 객체에 요청을 전달한다&lt;/li&gt;
&lt;li&gt;스프링은 프록시로 트랜잭션 관리, 로깅, 보안 등 다양한 부가 기능을 제공한다&lt;/li&gt;
&lt;li&gt;프록시는 스프링 AOP를 구현하는 핵심 기술이다&lt;/li&gt;
&lt;li&gt;당연한 얘기지만, 빈으로 등록된 객체에만 프록시를 사용할 수 있다&lt;/li&gt;
&lt;/ul&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;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;투명성
&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;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;JDK 동적 프록시
&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;CGLIB 프록시
&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;Code Generation Library를 사용하여 실제 클래스를 상속받아 프록시 객체를 생성한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;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;이때 @Transactional과 같은 어노테이션이 있는 경우, 스프링은 해당 빈에 대한 프록시 객체를 생성할 준비를 한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프록시 생성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈 타입에 따라 JDK 동적 프록시나 CGLIB 프록시가 생성된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;클라이언트가 프록시 객체의 메서드를 호출&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;TeamService teamService = applicationContext.getBean(TeamService.class);
teamService.deleteTeam(); //프록시 객체의 메서드 호출&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;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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public void deleteTeam() {
	// 트랜잭션 시작
	actualTeamService.deleteTeam();
	// 트랜잭션 커밋 혹은 롤백
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TIL</category>
      <category>aop 동작 방식</category>
      <category>Proxy</category>
      <category>TiL</category>
      <category>스프링 프록시</category>
      <category>프록시</category>
      <category>프록시 객체</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/117</guid>
      <comments>https://doingsomething.tistory.com/117#entry117comment</comments>
      <pubDate>Tue, 4 Jun 2024 21:40:03 +0900</pubDate>
    </item>
    <item>
      <title>주입할 스프링 빈이 없어도 동작해야 하는 경우</title>
      <link>https://doingsomething.tistory.com/116</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;3가지 방법이 있다&lt;/li&gt;
&lt;li&gt;@Autowired에 required false 옵션을 준다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;false 옵션을 주면 해당 빈이 없는 경우에는 아래 메서드를 아예 실행 시키지 않는다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Autowired(required = false)
public void setNoBean1(Member noBean1) {
	콘솔 출력
}

&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;주입 받아야하는 빈에 @Nullable 어노테이션을 달아준다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈이 없는 경우에는 null을 반환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Autowired
public void setNoBean1(@Nullable Member noBean1) {
	콘솔 출력
}

&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;빈을 Optional로 감싸준다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;빈이 없는 경우 Optional.empty를 반환한다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Autowired(required = false)
public void setNoBean1(Optional&amp;lt;Member&amp;gt; noBean1) {
	콘솔 출력
}

&lt;/code&gt;&lt;/pre&gt;</description>
      <category>TIL/Java &amp;amp; Spring Boot</category>
      <category>NoSuchBeanDefinitionException</category>
      <category>Spring</category>
      <category>spring bean</category>
      <category>TiL</category>
      <author>삼공비</author>
      <guid isPermaLink="true">https://doingsomething.tistory.com/116</guid>
      <comments>https://doingsomething.tistory.com/116#entry116comment</comments>
      <pubDate>Mon, 3 Jun 2024 00:48:58 +0900</pubDate>
    </item>
  </channel>
</rss>