synchronized
- synchronized๋ฅผ ๋ฉ์๋์ ๋ช ์ํด์ฃผ๋ฉด ํ๋์ ์ค๋ ๋๋ง ์ ๊ทผ์ด ๊ฐ๋ฅํ๋ค.
- ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ์ค๋ ๋๊ฐ ๋ฐ์ดํฐ ๋๊ธฐํ๋ฅผ ์ํด ์๋ฐ์์ ์ ๊ณตํ๋ ํค์๋๋ค.
- synchronized๋ ํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๊ณ ์๋ ์ค๋ ๋๋ฅผ ์ ์ธํ๊ณ ๋๋จธ์ง ์ค๋ ๋๋ค์ด ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์๋๋ก ๋ง์ ์์ฐจ์ ์ผ๋ก ๋ฐ์ดํฐ์ ์ ๊ทผํ ์ ์๋๋ก ํ๋ค.
๋ณธ๋ฌธ
์ ์์ด n๋ช ์ธ ๋ ์ ๋ชจ์์ด ์๋ค. ํ์ฌ ๋ชจ์์์ n-1๋ช ์ผ๋ก, ํ ๋ช ๋ง ๋ ๊ฐ์ ํ ์ ์๋ค. ์ด๋ ๋ ๋ช ์ ์ ์ ๊ฐ ๋์์ ๊ฐ์ ์ ์๋ํ๋ฉด ๋ ์ค ํ ๋ช ๋ง ๊ฐ์ ์ด ๋์ด์ผ ํ๋ค. ๊ฐ์ ์, ๋ชจ์ ์ ์์ ํ์ธํ์ฌ ๋จ์ ์๋ฆฌ๊ฐ ์๋ค๋ฉด ์ ์ ์ด๊ณผ ์์ธ๋ฅผ ๋ฐ์์ํจ๋ค.
๋ชจ์ ๊ฐ์ ๊ธฐ๋ฅ ์ ์ฒด ์ฝ๋
- Club.java : ๋ ์ ๋ชจ์ ์ํฐํฐ
- ClubService.java : ๋ ์ ๋ชจ์์ ๋น์ฆ๋์ค ๋ก์ง ์ฒ๋ฆฌ๋จ
- ClubServiceTest.java : ๋ ์ ๋ชจ์์ ๋น์ฆ๋์ค ๋ก์ง ํ ์คํธ
@Entity
public class Club {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "book_club_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "leader_id")
private User leader;
@OneToMany(mappedBy = "club")
private final List<MemberRegister> memberRegisters = new ArrayList<>();
@NotNull
private String clubName;
@NotNull
private int capacity;
@NotNull
private int memberCount = 0;
public void addMemberRegister(MemberRegister memberRegister) {
memberRegisters.add(memberRegister);
++memberCount;
}
}โ
[Entity] ๋ชจ์ ์ ์ ์ฒดํฌ ๋ก์ง
public void checkCapacity() {
if (memberCount + 1 > capacity) {
throw new NotEnoughCapacityException("member count is over capacity");
}
}
ํ์ฌ ๋ฉค๋ฒ ์ + 1์ด ์ ์๋ณด๋ค ํฌ๋ฉด, ์ ์ ์ด๊ณผ ์์ธ๊ฐ ๋ฐ์ํ๋ค.
[Test] ๋์ ๊ฐ์ ์, ์ ์ ์ด๊ณผ ์์ธ ๋ฐ์ ํ ์คํธ
@Test
void joinParallel() throws InterruptedException {
//given
User leader = newUser("leader");
User member1 = newUser("member1");
User member2 = newUser("member2");
List<User> members = new ArrayList<>(List.of(member1, member2));
Long clubId = newClub(leader, 2); //์ ์์ด 2๋ช
์ธ ๋ชจ์ ์์ฑ, ํ์ฌ ๋ฉค๋ฒ ์๋ 1
//then
ExecutorService executorService = Executors.newFixedThreadPool(2);
CountDownLatch latch = new CountDownLatch(2);
//when
for (User member: members) {
executorService.submit(() -> {
try {
clubService.join(new SessionUser(member), clubId);
} catch (Exception e) {
assertThat(e.getClass()).isEqualTo(NotEnoughCapacityException.class);
} finally {
latch.countDown();
}
});
}
latch.await();
//then
Club club = clubRepository.findById(clubId).get();
assertThat(club.getMemberCount()).isEqualTo(2);
List<MemberRegister> memberRegistersOfClub = memberRegisterRepository.findByClubId(club.getId());
assertThat(memberRegistersOfClub.size()).isEqualTo();
}
๋ ๋ช ์ ์ ์ ๊ฐ ๋์ ๊ฐ์ ์, ์ ์ ์ด๊ณผ ์์ธ(NotEnoughCapacityException)๊ฐ ๋ฐ์ํ๋ ํ ์คํธ ์ฝ๋๋ฅผ ์์ฑํ์๋ค. ๋ ์ค ํ ๋ช ๋ง ๊ฐ์ ๋์ด ๋ชจ์์ ๋ฉค๋ฒ ์๋ 2๋ช ์ด๊ธธ ๊ธฐ๋ํ๋ค.
[Service] ๊ธฐ์กด join() ๋ฉ์๋
@Transactional
public Long join(SessionUser sessionUser, Long id) {
Club club = clubRepository.findById(id)
.orElseThrow(IllegalArgumentException::new);
club.checkCapacity();
User user = findUser(sessionUser);
MemberRegister memberRegister = MemberRegister.register(club, user);
return memberRegisterRepository.save(memberRegister).getId();
}
synchronized ํค์๋๊ฐ ์๋ ์ผ๋ฐ ๋ฉ์๋๋ค.
[Test] ๋ฐ์ํ ๋ฌธ์
๋ ์ ์ ๊ฐ ์ฐจ๋ก๋๋ก ๋ชจ์์ ๋ฉค๋ฒ ์๋ฅผ ์กฐํํ์ง ์๊ณ ๋์์ ์กฐํ๋๋ค. ๊ธฐ์กด ๋ฉค๋ฒ ์(memberCount)๊ฐ 1, 2 ์์ผ๋ก ์กฐํ๋๊ธธ ๊ธฐ๋ํ์ผ๋ 1, 1๋ก ์กฐํ๋๋ฉฐ Race Condition์ด ๋ฐ์ํ๋ค. ์ ์ ์ด๊ณผ ์์ธ๊ฐ ๋ฐ์ํ์ง ์์ ๋ ์ ์ ๋ชจ๋ ๊ฐ์ ๋๋ฉฐ ๋ชจ์์ ์ด ๋ฉค๋ฒ ์๋ 3๋ช ์ด ๋์ด ํ ์คํธ์ ์คํจํ๋ค.
[Service] ์์ ๋ join() ๋ฉ์๋
@Service
public class ClubService {
private final UserRepository userRepository;
private final ClubRepository clubRepository;
private final MemberRegisterRepository memberRegisterRepository;
@Transactional
public synchronized Long join(SessionUser sessionUser, Long id) {
Club club = clubRepository.findById(id)
.orElseThrow(IllegalArgumentException::new);
club.checkCapacity();
User user = findUser(sessionUser);
MemberRegister memberRegister = MemberRegister.register(club, user);
return memberRegisterRepository.saveAndFlush(memberRegister).getId();
}
}
๋ฉ์๋์ synchronized ํค์๋๋ฅผ ๋ถ์ฌ ์ค๋ ๋๋ค์ด ์์ฐจ์ ์ผ๋ก join ๋ฉ์๋๋ฅผ ํธ์ถํ๊ฒ ๋์๋ค.
๊ทธ๋ฆฌ๊ณ memberRegister๋ฅผ ๋ ํฌ์งํ ๋ฆฌ์ ์ ์ฅํ ๋ save()๊ฐ ์๋ saveAndFlush() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ค.
save ๋ฉ์๋๋ฅผ ์ฌ์ฉํ๋ฉด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๋ฐ๋ก flush๋์ง ์์ synchronized๋ฅผ ์ด์ฉํ ํ ์คํธ๋ฅผ ํต๊ณผํ์ง ๋ชปํ๋ค. ์ด๋ @Transactional์ ๋์ ๋ฐฉ์ ๋๋ฌธ์ธ๋ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ๊ฐ์ด ์ ๋ ฅ๋๊ธฐ ์ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ๋ฉ์๋์ ์ ๊ทผํ ์ ์๋ค. ๋ฐ๋ผ์ saveAndFlush ๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ฐ๋ก flush๋๊ฒ ํจ์ผ๋ก์ ํ ์คํธ๋ฅผ ํต๊ณผํ ์ ์๋ค.
์ด์ฒ๋ผ ์ปค๋ฐ ์ ๊ฐ์ ํธ๋์ญ์ ์์์, ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋ ๋ณ๊ฒฝ์ฌํญ์ ๋์ค์ ์ฝ์ด์ผํด์ ์ฆ๊ฐ ๋ฐ์์ด ๋์ด์ผ ํ๋ ๊ฒฝ์ฐ, saveAndFlush๋ฅผ ์ฌ์ฉํ๋ค. (์ค๋ ๋ 1์ ์ํด ๋ฉค๋ฒ ํ ๋ช ์ด ๊ฐ์ ํ๋ฉด memberCount๊ฐ 1 ์ฆ๊ฐํ๋ค. ์ค๋ ๋ 2๋ ์ฆ๊ฐ๋ memberCount๋ฅผ ์ฝ์ด์ผ ํ๋ค.)