Synchronized
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class MultiThreadTest {
private static Logger logger = LoggerFactory.getLogger(MultiThreadTest.class);
public static void main(String[] args) {
Target target = new Target();
new Thread(() -> target.criticalSection()).start();
new Thread(() -> target.criticalSection()).start();
Target2 target2 = new Target2();
new Thread(() -> target2.criticalSection()).start();
new Thread(() -> target2.criticalSection()).start();
}
static class Target {
public synchronized void criticalSection() {
logger.info("Enter Critical Section");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Leave Critical Section");
}
}
static class Target2 {
public void criticalSection() {
synchronized (this) {
logger.info("Enter Scoped Critical Section");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
logger.info("Leave Scoped Critical Section");
}
}
}
}
18:19:07.776 [Thread-0] INFO MultiThreadTest - Enter Critical Section
18:19:07.776 [Thread-2] INFO MultiThreadTest - Enter Scoped Critical Section
18:19:10.780 [Thread-0] INFO MultiThreadTest - Leave Critical Section
18:19:10.780 [Thread-2] INFO MultiThreadTest - Leave Scoped Critical Section
18:19:10.780 [Thread-1] INFO MultiThreadTest - Enter Critical Section
18:19:10.780 [Thread-3] INFO MultiThreadTest - Enter Scoped Critical Section
18:19:13.784 [Thread-3] INFO MultiThreadTest - Leave Scoped Critical Section
18:19:13.784 [Thread-1] INFO MultiThreadTest - Leave Critical Section
ReentrantLock
- 함수
- lock() : 호출한 스레드가 해당 객체를 Lock. 만약 이미 Lock이 걸려있으면 무한정 기다린다.
- boolean tryLock() : 호출한 스레드가 해당 객체를 Lock 시도하고 그 결과를 리턴한다. 만약 다른 Thread가 Lock했다면 호출 즉시 false를 리턴한다.
- boolean trylock(long timeout, TimeUnit timeUnit) : 일정 시간동안 Lock을 얻기위해 기다린다.
- getHoldCount() : 해당 스레드가 가지고 있는 lock 갯수 리턴
- lockInterruptibly() :
- isHeldByCurrentThread() : 현재 스레드가 Lock을 소유하고 있는지 여부 리턴.
- unlock() : Lock을 반환
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
private static Logger logger = LoggerFactory.getLogger(ReentrantLockTest.class);
public static void main(String[] args) {
ReentrantLock lock = new ReentrantLock();
Program program = new Program(lock);
new Thread(program).start();
new Thread(program).start();
}
static class Program implements Runnable {
private ReentrantLock lock;
public Program(ReentrantLock lock) {
this.lock = lock;
}
@Override
public void run() {
lockTest();
}
private void lockTest() {
try {
lock.lock();
logger.info("Acquired Lock. lockCount={}", lock.getHoldCount());
Thread.sleep(3000);
} catch (Exception e) {
//
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
logger.info("Lock is released. lockCount={}", lock.getHoldCount());
}
}
}
private void tryLockTest() {
try {
if (lock.tryLock()) {
logger.info("Acquired Lock. lockCount={}", lock.getHoldCount());
Thread.sleep(3000);
} else {
logger.info("failed to get lock");
}
} catch (Exception e) {
//
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
logger.info("Lock is released. lockCount={}", lock.getHoldCount());
}
}
}
}
}
- tryLockTest 수행 결과
18:52:27.169 [Thread-1] INFO ReentrantLockTest - failed to get lock
18:52:27.169 [Thread-0] INFO ReentrantLockTest - Acquired Lock. lockCount=1
18:52:30.173 [Thread-0] INFO ReentrantLockTest - Lock is released. lockCount=0
수행 결과 Thread1은 Lock을 획득하지 못하자 바로 종료됐다. 그리고 Thread0은 Lock을 획득해 Thread.sleep()까지 호출되고 종료됐다.
- lockTest 수행결과
18:51:38.951 [Thread-0] INFO ReentrantLockTest - Acquired Lock. lockCount=1
18:51:41.959 [Thread-0] INFO ReentrantLockTest - Lock is released. lockCount=0
18:51:41.959 [Thread-1] INFO ReentrantLockTest - Acquired Lock. lockCount=1
18:51:44.960 [Thread-1] INFO ReentrantLockTest - Lock is released. lockCount=0
먼저 실행된 Thread0이 Lock을 획득. 뒤에 실행된 Thread1은 Lock을 획득하기위해 대기.
- Lock 중복 획득
하나의 스레드가 Lock을 중복해서 얻을 수 있다. 위에 예제중 lockTest() 함수를 아래와 같이 고쳐서 lock을 두번 획득하도록 했다.
private void lockTest() {
try {
lock.lock();
lock.lock();
logger.info("Acquired Lock. lockCount={}", lock.getHoldCount());
Thread.sleep(3000);
} catch (Exception e) {
//
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
logger.info("Lock is released. lockCount={}", lock.getHoldCount());
}
}
}
Lock을 반납 하는 코드가 한번밖에 없기 때문에 2번 획득, 1번 반납 하기 때문에 결국 첫번째 수행한 스레드가 아직 Lock을 가지고 있다. 때문에 후에 실행된 스레드는 무한정 대기하기하게 된다.
- lockInterruptably()
Lock을 획득하기 위해 대기하다가 Interrupt가 발생하면 대기를 중단한다.
public static void main(String[] args) throws InterruptedException {
ReentrantLock lock = new ReentrantLock();
Program program = new Program(lock);
Thread thread = new Thread(program);
thread.start();
Thread thread2 = new Thread(program);
thread2.start();
Thread.sleep(2000);
thread2.interrupt();
}
private void lockTest() {
try {
lock.lockInterruptibly();
if (lock.isHeldByCurrentThread()) {
lock.lock();
}
logger.info("Acquired Lock. lockCount={}", lock.getHoldCount());
Thread.sleep(3000);
} catch (Exception e) {
logger.error("Interrupted", e);
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
logger.info("Lock is released. lockCount={}", lock.getHoldCount());
}
}
}
강조한 구문처럼 lockInterruptibly()를 호출했다. 그리고 두번째 스레드 시작 후 2초후에 Interrupt를 발생시키면 InterruptedException 발생하면서 두번째 스레드가 종료된다. 만약 lock()을 호출하는 경우 Interrupt를 발생시키더라도 계속 대기상태로 유지된다. Interrupt를 발생시키고 스레드 덤프를 남겨보면 Thread-1이 계속 WAITING 상태로 남아있다.
"Thread-1" #11 prio=5 os_prio=31 tid=0x00007f82310c2000 nid=0x5103 waiting on condition [0x0000700009941000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x0000000795e85aa8> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:870)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1199)
at java.util.concurrent.locks.ReentrantLock$NonfairSync.lock(ReentrantLock.java:209)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:285)
at ReentrantLockTest$Program.lockTest(ReentrantLockTest.java:38)
at ReentrantLockTest$Program.run(ReentrantLockTest.java:33)
at java.lang.Thread.run(Thread.java:748)
- Synchronized vs ReentrantLock
- ReentrantLock은 Lock에 대한 더 많은 컨트롤을 지원한다. 다른 스레드가 이미 모니터를 가지고 있는 경우 일정시간 동안만 기다리거나, 아예 기다리지 않고 interrupt될 수 있다.
- ReentractLock은 lockInterruptably() 함수를 통해 WAITING 상태의 스레드를 interrupt할 수 있다. 하지만 synchronized로 인해 WAITING 상태인 스레드는 interrupt가 불가능하다.
- ReentrantLock은 Fairness가 가능하다. Fairness란 하나의 스레드가 자원을 독점하는것이 아닌 여러 스레드와 공유하는 개념.
댓글