synchronized 키워드 3가지 테스트 케이스

synchronized 키워드를 사용하는 전형적인 3가지 케이스에 대해서 테스트 코드를 작성해 보았다.

첫 번째는 메소드에 synchronized 키워드를 추가하여 메소드 lock을 걸어주는 방법

두 번째는 synchronized(객체)와 같이 객체의 레퍼런스 변수를 이용하여 lock을 걸어주는 방법

세 번째는 synchronized(this)와 같이 메소드 구현부에 자기 자신의 객체에 대한 lock을 걸어주는 방법

테스트는 Junit을 이용하여 구현하였고, Junit으로 Multithread test 코드 작성 시 Program Argument 영역에 아래 옵션을 추가해 줘야 한다.

실행할 class 선택 후 오른쪽 마우스 클릭 > Run As > Run Configurations…

Argument 탭으로 이동하여 Program arguments에 다음 옵션 추가

-keepalive

객체는 단 한 개의 쓰레드 lock을 취할 수 있다. 

즉, synchronized 키워드 블락에 thread 접근 시 다른 여러 쓰레드들은 synchronized 키워드 블락에 접근할 수 없다.

간단하게 예를 들면 아래와 같이 3개의 메소드가 있을 때 1번 쓰레드가 test3() 메소드 블락에 진입하면 다른 쓰레드들은 test2(), test3() 블락에 접근하지 못하고 thread wait set에 들어가게 된다.

1번 쓰레드가 test3() 메소드를 빠져나오게 되면 wait set에 있는 쓰레드 중 한 개가 test2() 또는 test3() 메소드 블락에 접근 한다.

synchronized 키워드가 추가되어 있지 않은 test1()은 thread lock과 상관없이 언제든지 접근 가능하다.

test1() {}

synchronized test2() {}

synchronized test3() {}

첫 번째 테스트 코드

public class SyncTest {
private static SyncTarget target;
@BeforeClass
public static void init() {
target = new SyncTarget();
System.out.println("init");
}
@Test
public void threadLockTest1() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
target.test1();
};
}.start();
}
}
@Test
public void threadLockTest2() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
target.test2();
};
}.start();
}
}
@Test
public void threadLockTest3() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
target.test3();
};
}.start();
}
}
public static class SyncTarget {
public void test1() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test1(), threadName=" + Thread.currentThread().getName());
}
public synchronized void test2() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2(), threadName=" + Thread.currentThread().getName());
}
public synchronized void test3() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test3(), threadName=" + Thread.currentThread().getName());
}
}
}

두 번째 테스트 코드

public class SyncTest2 {
private static SyncTarget target;
@BeforeClass
public static void init() {
target = new SyncTarget();
System.out.println("inint");
}
@Test
public void threadLockTest2() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
synchronized (target) {
target.test2();
}
};
}.start();
}
}
@Test
public void threadLockTest3() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
synchronized (target) {
target.test3();
}
};
}.start();
}
}
public static class SyncTarget {
public void test2() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2(), threadName=" + Thread.currentThread().getName());
}
public void test3() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test3(), threadName=" + Thread.currentThread().getName());
}
}
}

세 번째 테스트 코드

public class SyncTest3 {
private static SyncTarget target;
@BeforeClass
public static void init() {
target = new SyncTarget();
System.out.println("inint");
}
@Test
public void threadLockTest2() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
target.test2();
};
}.start();
}
}
@Test
public void threadLockTest3() {
for (int i = 0; i < 5; i++) {
new Thread() {
@Override
public void run() {
target.test3();
};
}.start();
}
}
public static class SyncTarget {
public void test2() {
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test2(), threadName=" + Thread.currentThread().getName());
}
}
public void test3() {
synchronized (this) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test3(), threadName=" + Thread.currentThread().getName());
}
}
}
}