들어가기 전

멀티 스레드 환경에서 동시에 접근하면 데이터 무결성이 깨질 수 있습니다.

이러한 현상을 방지하기 위해 synchronized와 ReentrantLock을 사용해서 임계 영역에 하나의 자원만 접근하게 하여 데이터 무결성을 지킬 수 있습니다.

이번 포스팅에서는 synchronized와 ReentrantLock가 무엇인지 알아보고 어떻게 데이터 무결성이 지켜지는지 알아보겠습니다.

 

 

synchronized와 ReentrantLock에 대해 알아보기 전에 임계 영역이 무엇인지 알아보겠습니다.

 

임계 영역이란?

임계영역이란 프로세스 간에 공유자원을 접근하는 데 있어서 문제가 발생하지 않도록 한 번에 하나의 프로세스만 이용하게끔 보장해줘야 하는 영역입니다.

 

 

public class CriticalSection {
    private int count = 0;

    public void increase() {
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 시작");
        // 임계 영역 시작
        for (int i = 0; i < 100000; i++) {
            count += 1;
        }
        //임계영역 끝
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 끝");
    }

    public int getCount() {
        return count;
    }
}

 

public class BlogTask implements Runnable {

    private CriticalSection criticalSection;

    public BlogTask(CriticalSection criticalSection) {
        this.criticalSection = criticalSection;
    }

    @Override
    public void run() {
        criticalSection.increase();
    }
}

 

public class CriticalSectionTest {

    @DisplayName("두 개의 스레드가 임계영역에 동시에 접근하면 데이터 무결성이 깨진다.")
    @Test
    void breaksIntegrityOnRaceCondition() throws InterruptedException {
        //given
        CriticalSection criticalSection = new CriticalSection();
        BlogTask blogTask = new BlogTask(criticalSection);
        Thread thread1 = new Thread(blogTask, "thread1");
        Thread thread2 = new Thread(blogTask, "thread2");

        //when
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();

        //then
        Assertions.assertNotEquals(criticalSection.getCount(), 200000);
    }
}

 

 

 

 

위 로직에서 두 개의 스레드(thread1, thread2)가 count값을 증가시키고 있습니다.

테스트 코드 실행을 했을 때 기대한 값은 "200000"이었지만 실제 기대한 값을 얻지 못하는 현상을 볼 수 있습니다.

이유는 두 개의 스레드가 임계 영역에 동시에 접근하였기 때문에 데이터 무결성이 깨진 것을 확인할 수 있습니다.

 

이유를 자세히 알고 싶으신 분은 아래 포스팅을 참고하시는 것을 추천드리겠습니다.

 

https://hoestory.tistory.com/83

 

[Java] 동시성 이슈 개념과 발생하는 동작 과정

들어가기 전이번 포스팅에서는 동시성 이슈에 대해 알아보겠습니다. 동시성 이슈를 알아보기 전에 싱글 스레드와 멀티 스레드 환경에 대해서 알아보겠습니다. 싱글스레드와 멀티스레드 싱글

hoestory.tistory.com

 

참고

 

increase 메서드에서 count를 증가시키는 로직(빨간 박스)이 임계영역입니다.

 

 

지금까지 임계영역이 무엇이고 멀티 스레드 환경에서 임계 영역에 동시에 접근하면 발생하는 문제에 대해서 알아보았습니다.
이제 임계 영역에 여러 스레드가 동시에 접근하여 데이터의 무결성이 깨지는 것을 방지하는 방법인 synchronized와 ReentrantLock에 대해서 알아보겠습니다.

 

 

synchronized

임계 영역 공유 자원에 접근한 스레드를 제외하고 다른 스레드가 임계 영역에 접근하는 것을 막는 동기화 방법입니다.

각 객체는 모니터 락(고유 락)을 소유하고 있습니다.

그래서 synchronized를 사용할 때는 객체가 가지고 있는 모니터 락과 함께 사용이 됩니다.

 

synchronized 사용할 수 있는 방법은 아래와 같이 두 가지가 있습니다.

  • 메서드
  • 메서드 구현부의 synchronized 블록 사용

 

 

synchronized 사용 전 로그

 

 

사용하기 전에는 여러 개의 스레드가 동시에 연산을 시작을 하고 늦게 시작한 연산이 먼저 끝나는 것을 확인할 수 있습니다.

그리고 결괏값이 기대한 결괏값이 아닌 것을 확인할 수 있습니다.

 

 

synchronized  : 메서드 사용

 

public class CriticalSection {
    private int count = 0;

    public synchronized void increase() {
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 시작");
        for (int i = 0; i < 100000; i++) {
            count += 1;
        }
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 끝");
    }

    public int getCount() {
        return count;
    }
}

 

 

사용하기 전 로그와 비교를 해보면 사용했을대는 한 개의 스레드가 연산을 시작하고 끝나야 다음 스레드가 실행되는 것을 확인할 수 있습니다.

그리고 기대한 연산 결괏값이 나온 것을 확인할 수 있습니다.

 

synchronized  : 메서드 구현부 synchronized 블록 사용

 

public class CriticalSection {
    private int count = 0;

    public void increase() {
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 시작");
        synchronized(this) {
            for (int i = 0; i < 100000; i++) {
                count += 1;
            }
        }
        System.out.println("스레드명 : " + Thread.currentThread().getName() +  " 연산 끝");
    }

    public int getCount() {
        return count;
    }
}

 

  • synchronized에 this를 사용한 이유 : 현재 객체(this)에 모니터 락(monitor lock)을 걸어 동시에 여러 스레드가 임계 영역에 접근하지 못하도록 방지하기 위해 사용합니다.

 

 

 

메서드에 synchronized를 사용하면 메서드에 접근하는 순간 객체에 락이 걸려 한 번에 하나의 스레드만 실행할 수 있습니다.

반면 synchronized 블록임계 영역에만 적용하면 메서드 자체에는 여러 스레드가 동시에 접근할 수 있으며, 임계 영역에는 하나의 스레드만 접근이 가능하여 기대한 연산 결괏값을 얻을 수 있습니다.

 

 

synchronized를 사용해서 여러 스레드가 임계 영역에 동시에 접근하는 것을 막아줌으로써 데이터 무결성을 지켜주는 것을 확인하였습니다

임계 영역에 하나의 스레드만 접근하여 데이터 무결성을 지켜주기 위한 동기화 기법으로 synchronized을 사용하면 좋겠지만 synchronized에는 치명적인 단점이 있습니다.

 

치명적인 synchronized  단점

synchronized는 모니터 락 기반으로 동작하기 때문에  락을 소유하고 있는 스레드의 작업이 끝날 때까지 다른 스레드는 대기하고 있어야 합니다.

그리고 대기 중인 스레드의 상태값은 BLOCKED값이어서 인터럽트로 대기 중인 스레드를 깨워줘야 하는데 synchronized를 사용하면 인터럽트가 적용되지 않습니다.

즉 락이 해제되지 않아 락을 오래 소유하고 있는 스레드가 있다면 다른 스레드가 무기한 대기를 할 수 있습니다.

 

 

지금까지 synchronized에 대해서 알아보았습니다.
이제 synchronized에서 설명한 치명적인 단점해결해 줄 수 있는 LockSupport, ReentrantLock에 대해서 알아보겠습니다.

 

 

 

LockSupport

LockSupport는 synchronized에서 발생할 수 있는 무한정 대기를 해결해 줄 수 있는 기능을 제공합니다.

 

LockSupport에서 제공하는 메서드

  • park()
  • park(long nanos)
  • unpark(Thread thread)

 

park()

LockSupport에서 지원해 주는 park 메서드는 Runnable인 스레드의 상태를 Waiting으로 변경해 주는 역할을 합니다.

 

public class LockSupportEx {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        LockSupportTask lockSupportTask = new LockSupportTask();
        Thread lockSupportThread = new Thread(lockSupportTask, "lockSupportThread");
        lockSupportThread.start();
        Thread.sleep(100);
        System.out.printf("스레드 명 : %s, park 한 뒤의 스레드 상태값 : %s%n", lockSupportThread.getName(),
            lockSupportThread.getState());
    }
}
class LockSupportTask implements  Runnable {
    @Override
    public void run() {
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "의 상태값 : " + Thread.currentThread().getState());
    }
}
  • Thread.sleep(100)을 해준 이유는 lockSupportThread 스레드가 park를 할 수 있는 시간을 만들어주기 위해서 사용하였습니다.

 

위 코드를 실행시키면 아래와 같은 결과를 확인할 수 있습니다.

 

 

lockSupportThread가 Runnable 상태에서 park 되어 WAITING 상태로 변경이 됩니다.

lockSupportThread의 상태가 WAITING이어서  프로그램은 종료되지 않습니다.

이것들을 해결하는 방법은 TIME_WAITING 상태로 만들거나 WAITING 상태에서 빠져나오게 하면 됩니다.

TIME_WAITING 상태로 만드는 방법parkNanos메서드의 nano초 단위의 시간을 설정해 주면 됩니다.

WAITING 상태에서 빠져나오는 방법unpark 메서드에 현재 WAITING 되어있는 스레드를 매개변수로 전달해 주면 됩니다. 

 

 

parkNanos(long nanos)

park 메서드와 다르게 지정한 nanos(나노) 초만큼 실행 중인 스레드를 TIME_WAITING 상태로 변경하였다가 RUNNABLE 상태로 변경되는 메서드입니다.

 

public class LockSupportEx {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        LockSupportTask lockSupportTask = new LockSupportTask();
        Thread lockSupportThread = new Thread(lockSupportTask, "lockSupportThread");
        lockSupportThread.start();
        System.out.printf("스레드 명 : %s, park 전 스레드 상태값 : %s%n", lockSupportThread.getName(),lockSupportThread.getState());
        Thread.sleep(100);

        System.out.printf("스레드 명 : %s, park 한 뒤의 스레드 상태값 : %s%n", lockSupportThread.getName(),
            lockSupportThread.getState());
    }
}
class LockSupportTask implements  Runnable {
    @Override
    public void run() {
        System.out.println("parkNanos beforeTime = " + LocalDateTime.now());
        LockSupport.parkNanos(1000_000000L);
        System.out.println("parkNanos afterTime " + LocalDateTime.now());
        System.out.println(Thread.currentThread().getName() + "의 상태값 : " + Thread.currentThread().getState());
    }
}
  • parkNanos(1000_000000L) : 1초 동안 WAITING을 합니다. cf) Thread.sleep(1000)

 

아래 결과를 보면 parkNanos로 인해 1초 동안 TIME_WAITING 상태에 있다가 1초 뒤에 RUNNABLE 상태로 변경되는 것을 확인할 수 있습니다.

 

 

그리고 park 메서드를 사용했을 때와 달리 프로그램이 종료된 것을 확인할 수 있습니다.

 

 

unpark(Thread thread)

WAITNG 상태에 빠져 있는 스레드를 매개변수로 전달받아 RUNNABLE 상태값으로 만들어줍니다.

 

public class LockSupportEx {

    private Lock lock = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        LockSupportTask lockSupportTask = new LockSupportTask();
        Thread lockSupportThread = new Thread(lockSupportTask, "lockSupportThread");
        lockSupportThread.start();
        System.out.printf("스레드 명 : %s, park 전 스레드 상태값 : %s%n", lockSupportThread.getName(),lockSupportThread.getState());
        Thread.sleep(1000);
        LockSupport.unpark(lockSupportThread);
        System.out.printf("스레드 명 : %s, unpark 한 뒤의 스레드 상태값 : %s%n", lockSupportThread.getName(),
            lockSupportThread.getState());
    }
}
class LockSupportTask implements  Runnable {
    @Override
    public void run() {
        LockSupport.park();
        System.out.println(Thread.currentThread().getName() + "의 상태값 : " + Thread.currentThread().getState());
    }
}

 

 

위 코드를 실행시키면 unpark 메서드를 통해 WAITING 되어 있는 스레드를 깨워서 RUNNABLE 상태로 변경해 주는 것을 아래 결과를 통해 확인할 수 있습니다.

 

 

parkNanos와 동일하게 프로그램이 종료되는 것을 확인할 수 있습니다.

 

 

지금까지 synchronized의 단점을 극복할 수 있는 LockSupport에 대해서 알아보았습니다.
이제 LockSupport 기반으로 동작하지만 더 많은 기능을 제공하는 ReentrantLock에 대해서 알아보겠습니다.

 

 

ReentrantLock

ReentrantLock은 위에서 설명한 synchronized 메서드 또는 블록과 기본적인 동작과 의미는 같지만 명시적으로 락을 제어할 수 있으며 확장된 기능을 제공하는 동기화 기법입니다.

그리고 AQS(AbstractQueuedSynchronizer)를 상속받은 Sync 추상클래스를 활용하여 공정, 비공정모드를 제공합니다.

기본값은 비공정 락 방식으로 동작합니다.

 

공정모드(Fair)

ReentrantLock의 인스턴스를 생성할 때 생성자에 true를 전달해 주면 공정모드로 수행이 됩니다.

공정모드는 먼저 대기한 스레드가 먼저 락을 획득할 수 있게 해 줍니다.

이럴 경우 모든 스레드가 락을 획득할 수 있는 기회를 언젠간 얻을 수 있기 때문에 스레드의 기아 현상 방지할 수 있다는 장점은 있지만 스레드가 락을 획득하는 속도가 느려질 수 있습니다.

 

public class ReentrantLockEx {

    public static void main(String[] args) {
        ReentrantLockTask reentrantLockTask = new ReentrantLockTask();
        Thread thread = new Thread(reentrantLockTask, "reenTrantLockTaskFair");
        thread.start();
    }

}

class ReentrantLockTask implements Runnable {
    private final ReentrantLock lock = new ReentrantLock(true);

    @Override
    public void run() {
        System.out.printf("현재 스레드 명 : %s, 공정/비공정 상태 : %b", Thread.currentThread().getName(), lock.isFair());
    }
}

 

위처럼 인스턴스를 생성할 때 true를 전달하면 공정 모드가 되어 아래와 같이 isFair() 메서드가 true를 반환하는 것을 확인할 수 있습니다.

 

 

비공정모드(NonFair)

기본 ReentrantLock을 생성할 때 비공정 모드로 생성이 됩니다.

스레드가 락을 얻을 수 있는 기회가 생긴다면 순서 상관없이 락을 획득합니다.

먼저 대기하고 있던 스레드가 아닌 나중에 대기하고 있던 스레드가 락을 획득할 수 있어 기아 현상이 발생할 수 있는 반면 락을 획득하는 속도가 빠릅니다.

 

public class ReentrantLockEx {

    public static void main(String[] args) {
        ReentrantLockTask reentrantLockTask = new ReentrantLockTask();
        Thread thread = new Thread(reentrantLockTask, "reenTrantLockTaskNonFair");
        thread.start();
    }

}

class ReentrantLockTask implements Runnable {
    private final ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        System.out.printf("현재 스레드 명 : %s, 공정/비공정 상태 : %b", Thread.currentThread().getName(), lock.isFair());
    }
}

 

위처럼 인스턴스를 생성할 때 아무것도 전달하지 않으면 비공정 모드가 되어 아래와 같이 isFair() 메서드가 false를 반환하는 것을 확인할 수 있습니다.

 

 

 

ReentrantLock에서 제공하는 메서드

  • void lock()
  • void lockInterruptibly()
  • void unlock()
  • boolean tryLock()
  • booelan tryLock(long timeout, TimeUnit unit)

 

lock()

임계영역에 접근하지 못하도록 락을 거는 기능입니다.

synchronized처럼 다른 스레드에 인터럽트를 하더라도 락을 그대로 유지합니다.

그래서 작업이 끝날 때까지 다른 스레드들은 락을 획득하지 못한 채 대기상태에 머무르게 됩니다.

 

 

public class ReentrantLockEx {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTask reentrantLockTask = new ReentrantLockTask();
        Thread thread = new Thread(reentrantLockTask, "reenTrantLock");
        thread.start();
        thread.interrupt();
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s", thread.getName(), thread.getState());
    }

}

class ReentrantLockTask implements Runnable {
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        lock.lock();
        for (long i = 0; i < 100000000000000L; i++) {
            
        }
        System.out.printf("현재 스레드 명 : %s, 상태 : %s\n", Thread.currentThread().getName(), Thread.currentThread().getState());
    }
}

 

 

main 스레드에서 인터럽트를 호출하였는데 아무 반응 없는 상태로 작업을 이어나가고 있는 결과를 확인할 수 있습니다.

 

lockInterruptibly()

 

lock() 메서드와 동일한 기능이지만 다른 스레드에서 인터럽트를 하면 락 걸린 스레드를 빠져나오게 할 수 있습니다.

 

public class ReentrantLockEx {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTask reentrantLockTask = new ReentrantLockTask();
        Thread thread = new Thread(reentrantLockTask, "reenTrantLock");
        thread.start();
        thread.interrupt();
        Thread.sleep(100);
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread.getName(), thread.getState());
    }

}

class ReentrantLockTask implements Runnable {
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            lock.lockInterruptibly();
            for (long i = 0; i < 100000000000000L; i++) {

            }
        } catch (InterruptedException e) {
            System.out.println("인터럽트 발생");
        }


        System.out.printf("현재 스레드 명 : %s, 상태 : %s\n", Thread.currentThread().getName(), Thread.currentThread().getState());
    }
}

 

main 스레드에서 인터럽트를 호출하면 현재 락 걸려있는 스레드가 빠져나오는 것을 확인할 수 있습니다.

 

 

 

void unlock()

 

현재 스레드가 획득하고 있는 락을 해제하는 기능입니다. 현재 락을 가지고 있는 스레드가 unlock 메서드를 통해 락이 해제가 된다면 대기 중인 스레드가 락을 획득할 수 있습니다.

lock 메서드를 통해 락을 획득하였다면 unlock을 통해서 필수적으로 락을 해제해줘야 합니다.

그렇지 않을 경우 대기 중인 상태가 락을 획득하지 못하고 대기상태에 머무를 수 있습니다.

 

public class ReentrantLockEx {

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockTask reentrantLockTask = new ReentrantLockTask();
        Thread thread = new Thread(reentrantLockTask, "reenTrantLock");
        thread.start();
        Thread.sleep(100);
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread.getName(), thread.getState());
    }

}

class ReentrantLockTask implements Runnable {
    private final Lock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            lock.lock();
            for (long i = 0; i < 100000000000L; i++) {
            }
        }finally {
            lock.unlock();
        }


        System.out.printf("현재 스레드 명 : %s, 상태 : %s\n", Thread.currentThread().getName(), Thread.currentThread().getState());
    }
}

 

 

*참고

만약 락을 획득하지 못한 상태로 unlock 메서드를 호출하여 락 해제 시도를 하게 되면 아래와 같은 오류가 발생할 수 있습니다.

 

 

tryLock()

현재 락을 획득한 스레드가 존재여부를 파악합니다.

락을 획득한 스레드가 존재하면 false를 반환하고 획득한 스레드가 없다면 true를 반환하여 락을 획득하고 임계영역에 접근합니다.

 

public class BlogEx {
    private long hit;
    private final Lock lock = new ReentrantLock();

    public void increaseHit() {
        String threadName = Thread.currentThread().getName();
        System.out.println("락 접근 시도하는 스레드 명 : " + threadName);
        if(lock.tryLock()) {
            System.out.println("락 획득한 스레드 명 : " + threadName);
            lock.lock();
            for (long i = 0; i < 1000000000000L; i++) {
                hit += 1;
            }
        }else {
            System.out.println("락 실패한 스레드 명 : " + threadName);
        }
    }
    public long getHit() {
        return hit;
    }
}

 

  • tryLock 메서드를 통해 현재 락을 획득한 스레드가 존재하는지 확인합니다.
  • 현재 락을 획득한 스레드가 없을 경우 임계 영역에 접근합니다.
  • 락을 획득한 스레드가 존재하면 대기하지 않고 작업 종료하여 TERMINATED 상태값을 가집니다.
public class ReentrantLockEx {

    public static void main(String[] args) throws InterruptedException {
        BlogEx blogEx = new BlogEx();
        ReentrantLockTask reentrantLockTask1 = new ReentrantLockTask(blogEx);
        ReentrantLockTask reentrantLockTask2 = new ReentrantLockTask(blogEx);
        Thread thread1 = new Thread(reentrantLockTask1, "reenTrantLock1");
        Thread thread2 = new Thread(reentrantLockTask2, "reenTrantLock2");
        thread1.start();
        thread2.start();
        Thread.sleep(100);
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread1.getName(), thread1.getState());
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread2.getName(), thread2.getState());
        thread2.join();
        thread1.join();
        System.out.println("결과 : " + blogEx.getHit());
    }

}

class ReentrantLockTask implements Runnable {

    private BlogEx blogEx;

    public ReentrantLockTask(BlogEx blogEx) {
        this.blogEx = blogEx;
    }

    @Override
    public void run() {
        blogEx.increaseHit();
    }
}

 

아래 결과를 확인해 보면 reenTrantLock2 락을 획득하여 reenTrantLock1은 락 획득에 실패하여 종료된 것을 확인할 수 있습니다.

 

 

 

tryLock(long timeout, TimeUnit unit)

tryLock메서드와 동일한 기능을 제공하지만 tryLock 메서드와 다르게 지정한 시간만큼 대기하고 있다가 작업을 끝냅니다.

 

 

 

public class BlogEx {
    private long hit;
    private final Lock lock = new ReentrantLock();

    public void increaseHit() {
        String threadName = Thread.currentThread().getName();
        System.out.printf("락 접근 시도하는 스레드 명 :%s , 시도 시간 : %s" ,threadName, LocalDateTime.now());

        System.out.println("================================");
        try {
            if(lock.tryLock(2, TimeUnit.SECONDS)) {
                System.out.println("락 획득한 스레드 명 : " + threadName);
                System.out.println("================================");
                lock.lock();
                for (long i = 0; i < 1000000000L; i++) {
                    hit += 1;
                }
            }else {
                System.out.printf("락 실패한 스레드 명 : %s, 락 획득 실패한 시간 : %s \n" , threadName,
                    LocalDateTime.now());

                System.out.println("================================");

            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            lock.unlock();
        }
    }
    public long getHit() {
        return hit;
    }
}

 

public class ReentrantLockEx {

    public static void main(String[] args) throws InterruptedException {
        BlogEx blogEx = new BlogEx();
        ReentrantLockTask reentrantLockTask1 = new ReentrantLockTask(blogEx);
        ReentrantLockTask reentrantLockTask2 = new ReentrantLockTask(blogEx);
        Thread thread1 = new Thread(reentrantLockTask1, "reenTrantLock1");
        Thread thread2 = new Thread(reentrantLockTask2, "reenTrantLock2");
        thread1.start();
        thread2.start();
        Thread.sleep(100);
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread1.getName(), thread1.getState());
        System.out.printf("작업 끝난 현재 스레드 명 : %s, 상태 : %s\n", thread2.getName(), thread2.getState());
        thread2.join();
        thread1.join();
        System.out.println("결과 : " + blogEx.getHit());
    }

}

class ReentrantLockTask implements Runnable {

    private BlogEx blogEx;

    public ReentrantLockTask(BlogEx blogEx) {
        this.blogEx = blogEx;
    }

    @Override
    public void run() {
        blogEx.increaseHit();
    }
}

 

 

위 코드를 실행시키면 아래와 같은 결과를 확인할 수 있습니다.

tryLock(2, TimeUnit.SECONDS) 메서드를 통해서 락 획득을 위해 2초 정도 대기하였다가 2초 동안 락을 획득하지 못하여 락 획득을 포기하고 작업을 끝내는 것을 확인할 수 있습니다.