웹사이트 검색

Java 잠금 예 - ReentrantLock


Java Lock 예제 자습서에 오신 것을 환영합니다. 일반적으로 다중 스레드 환경에서 작업할 때 스레드 안전을 위해 동기화를 사용합니다.

자바 잠금

  1. Lock: This is the base interface for Lock API. It provides all the features of synchronized keyword with additional ways to create different Conditions for locking, providing timeout for thread to wait for lock. Some of the important methods are lock() to acquire the lock, unlock() to release the lock, tryLock() to wait for lock for a certain period of time, newCondition() to create the Condition etc.

  2. Condition: Condition objects are similar to Object wait-notify model with additional feature to create different sets of wait. A Condition object is always created by Lock object. Some of the important methods are await() that is similar to wait() and signal(), signalAll() that is similar to notify() and notifyAll() methods.

  3. ReadWriteLock: It contains a pair of associated locks, one for read-only operations and another one for writing. The read lock may be held simultaneously by multiple reader threads as long as there are no writer threads. The write lock is exclusive.

  4. ReentrantLock: This is the most widely used implementation class of Lock interface. This class implements the Lock interface in similar way as synchronized keyword. Apart from Lock interface implementation, ReentrantLock contains some utility methods to get the thread holding the lock, threads waiting to acquire the lock etc. synchronized block are reentrant in nature i.e if a thread has lock on the monitor object and if another synchronized block requires to have the lock on the same monitor object then thread can enter that code block. I think this is the reason for the class name to be ReentrantLock. Let’s understand this feature with a simple example.

    public class Test{
    
    public synchronized foo(){
        //do something
        bar();
      }
    
      public synchronized bar(){
        //do some more
      }
    }
    

    If a thread enters foo(), it has the lock on Test object, so when it tries to execute bar() method, the thread is allowed to execute bar() method since it’s already holding the lock on the Test object i.e same as synchronized(this).

Java 잠금 예 - Java의 ReentrantLock

이제 동기화된 키워드를 Java Lock API로 대체하는 간단한 예를 살펴보겠습니다. 스레드로부터 안전하기를 원하는 일부 작업과 스레드 안전성이 필요하지 않은 일부 메서드가 포함된 Resource 클래스가 있다고 가정해 보겠습니다.

package com.journaldev.threads.lock;

public class Resource {

	public void doSomething(){
		//do some operation, DB read, write etc
	}
	
	public void doLogging(){
		//logging, no need for thread safety
	}
}

이제 Resource 메서드를 사용할 Runnable 클래스가 있다고 가정해 보겠습니다.

package com.journaldev.threads.lock;

public class SynchronizedLockExample implements Runnable{

	private Resource resource;
	
	public SynchronizedLockExample(Resource r){
		this.resource = r;
	}
	
	@Override
	public void run() {
		synchronized (resource) {
			resource.doSomething();
		}
		resource.doLogging();
	}
}

리소스 개체에 대한 잠금을 획득하기 위해 동기화된 블록을 사용하고 있습니다. 우리는 클래스에서 더미 개체를 만들고 잠금 목적으로 사용할 수 있습니다. 이제 java Lock API를 사용하고 동기화된 키워드를 사용하지 않고 위의 프로그램을 다시 작성하는 방법을 살펴보겠습니다. 우리는 자바에서 ReentrantLock을 사용할 것입니다.

package com.journaldev.threads.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConcurrencyLockExample implements Runnable{

	private Resource resource;
	private Lock lock;
	
	public ConcurrencyLockExample(Resource r){
		this.resource = r;
		this.lock = new ReentrantLock();
	}
	
	@Override
	public void run() {
		try {
			if(lock.tryLock(10, TimeUnit.SECONDS)){
			resource.doSomething();
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			//release lock
			lock.unlock();
		}
		resource.doLogging();
	}

}

보다시피, 나는 내 스레드가 정해진 시간 동안만 기다리도록 하기 위해 tryLock() 메서드를 사용하고 있습니다. 개체에 대한 잠금을 얻지 못하면 그냥 로깅하고 종료합니다. 주목해야 할 또 다른 중요한 점은 try-finally 블록을 사용하여 doSomething() 메서드 호출에서 예외가 발생하더라도 잠금이 해제되었는지 확인하는 것입니다.

Java 잠금 대 동기화됨

위의 세부 사항 및 프로그램을 기반으로 Java Lock과 동기화의 차이점은 다음과 같습니다.

  1. 자바 잠금 API는 스레드가 잠금을 무한정 대기하게 될 수 있는 동기화와 달리 잠금에 대한 더 많은 가시성과 옵션을 제공합니다. tryLock()을 사용하여 스레드가 특정 시간 동안만 기다리도록 할 수 있습니다.
  2. 동기화 코드는 훨씬 더 깨끗하고 유지하기 쉽습니다. 반면 Lock을 사용하면 lock()과 unlock() 메서드 호출 사이에 일부 예외가 발생하더라도 Lock이 해제되도록 강제로 try-finally 블록이 있어야 합니다.
  3. 동기화 블록 또는 메소드는 하나의 메소드만 다룰 수 있는 반면 Lock API를 사용하여 한 메소드에서 잠금을 획득하고 다른 메소드에서 해제할 수 있습니다.
  4. synchronized 키워드는 공정성을 제공하지 않는 반면 ReentrantLock 객체를 생성하는 동안 공정성을 true로 설정하여 가장 긴 대기 스레드가 먼저 잠금을 얻도록 할 수 있습니다.
  5. Lock에 대해 다른 조건을 만들 수 있으며 다른 스레드는 다른 조건에 대해 await()할 수 있습니다.

여기까지가 Java Lock 예제, Java의 ReentrantLock 및 동기화된 키워드를 사용한 비교 분석에 대한 것입니다.