
class Counter {
private int c = 0;
public void increment() {
c++;
}
public void decrement() {
c--;
}
public int value() {
return c;
}
}
@Test
void counterTest() throws InterruptedException {
Counter counter = new Counter();
int threadCount = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
counter.increment();
counter.decrement();
latch.countDown();
});
}
latch.await();
executorService.shutdown();
int result = counter.value();
System.out.println("Final counter value: " + result);
}

이처럼 여러 스레드가 동일한 자원에 동시에 접근해 사용했을 때 발생할 수 있는 동시성 문제를 막는 방법 중 하나로 synchronized키워드가 있습니다.
public class SynchronizedCounter {
private int c = 0;
public synchronized void increment() {
c++;
}
public synchronized void decrement() {
c--;
}
public synchronized int value() {
return c;
}
}
@Test
void counterTest2() throws InterruptedException {
// Counter -> SynchronizedCounter로 변경
SynchronizedCounter counter = new SynchronizedCounter();
int threadCount = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
counter.increment();
counter.decrement();
latch.countDown();
});
}
latch.await();
executorService.shutdown();
int result = counter.value();
System.out.println("Final counter value: " + result);
}

메서드 앞에 선언하여 사용하거나(synchronized method), 또는 synchronized 구문을 만들어 사용할 수 있습니다(synchronized statement).public synchronized void increment() {}메서드와 동일합니다. public void increment() {
synchronized(this) {
c++;
}
}public synchronized void increment()를 실행하고 있다면, 다른 스레드는 public synchronized void increment()는 물론이고 public synchronized void decrement()메서드도 실행할 수 없습니다. 이는 같은 객체 안에 있는 synchronized메서드는 모두 동일한 락(this)을 사용한다는 의미입니다.synchronized statement는 공유락(Intrinsic Lock)을 제공할 객체를 반드시 선언해야 합니다.
사용 형태(예시)
public void method() {
// ...
synchronized(공유락을 제공할 객체) {
// do something
}
// ...
}
두 메서드가 사용하는 자원이 다르다면 다음과 같이 synchronized statement를 활용할 수 있습니다
public class MsLunch {
private long c1 = 0;
private long c2 = 0;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void inc1() {
synchronized(lock1) {
c1++;
}
}
public void inc2() {
synchronized(lock2) {
c2++;
}
}
}
static synchronized는 인스턴스가 아닌 클래스 단위로 Lock이 발생합니다.
public class StaticSynchronizedCounter {
private static int c = 0;
public static synchronized void increment() {
c++;
}
public static synchronized void decrement() {
c--;
}
public static synchronized int value() {
return c;
}
}
@Test
void counterTest3() throws InterruptedException {
int threadCount = 1000;
ExecutorService executorService = Executors.newFixedThreadPool(10);
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
executorService.submit(() -> {
StaticSynchronizedCounter.increment();
StaticSynchronizedCounter.decrement();
latch.countDown();
});
}
latch.await();
executorService.shutdown();
int result = StaticSynchronizedCounter.value();
System.out.println("Final counter value: " + result);
}
static synchronized는 일반적인 synchronized와 Lock을 공유하지 않습니다.
자신이 이미 소유하고 있는 Lock은 다시 획득할 수 있습니다. 이는 synchronized블록이 중첩됐을 때 자신이 획득한 Lock때문에 자기 자신이 다음으로 진입하지 못하는 경우를 방지합니다.public void inc1() {
synchronized(this) {
// do Something
synchornized(this) {
// do Something2
}
}
} 동작이 중단 없이 완전히 실행되거나, 아예 실행되지 않는 것을 의미합니다. 중간에 멈출 수 없으며 all or nothing의 성격을 지닙니다. 또한 해당 동작이 완료되기 전까지는 side-effects가 외부에 보이지 않습니다.가시성만을 보장하며, synchronized와 달리 복합 연산에 대한 원자성은 보장하지 않습니다. 따라서 volatile로 선언한 변수를 단순 flag로 사용하는 건 좋지만, 이 값을 count++처럼 연산하는 건 좋지 않습니다.