자바 동기화 변수

Shubham Vora 2023년10월12일
  1. 동기화 키워드
  2. ReentrantLock 방법 사용
  3. 바이너리 또는 카운팅 세마포어 사용
  4. 원자 변수 사용
자바 동기화 변수

이 기사에서는 Java에서 변수를 동기화하거나 잠그는 방법에 대해 설명합니다.

다중 스레드 프로그래밍에서는 동기화 또는 잠금이 필수적입니다. 병렬로 실행되는 스레드는 예기치 않은 결과를 생성하는 동일한 변수 또는 다른 리소스에 액세스하려고 시도할 수 있습니다.

동기화 또는 잠금은 이러한 오류 사례를 방지하는 솔루션입니다.

동기화 키워드

동기화는 Java의 전통적인 방법입니다. 동기화된 블록은 synchronized 키워드를 사용하여 구현됩니다.

스레드가 synchronized 코드에 들어가면 다른 Java 스레드는 차단 상태가 됩니다. 현재 Java 스레드가 synchronized 블록을 종료한 후 다른 Java 스레드가 synchronized 블록에 액세스를 시도할 수 있습니다.

이 방법에는 단점이 있습니다. synchronized 블록은 스레드가 대기열에서 대기하고 현재 Java 스레드가 작업을 마친 후 변수에 액세스하는 것을 허용하지 않으므로 Java 스레드는 오랜 시간을 기다려야 합니다.

메서드 또는 블록과 함께 동기화 키워드 사용

아래 예에서는 MultiThreadList 클래스의 run() 메서드에서 MenuObj를 동기화합니다. synchronizedlistItem() 코드 블록을 정의할 수도 있습니다.

두 기능 모두 동일한 결과를 제공합니다.

동일한 프로그램은 방법의 동기화와 함께 다음과 같이 될 수 있습니다.

public synchronized void listItem(String item) {
  { /*Code to Synchronize*/
  }
}
synchronized (this) {
  System.out.println("\nMenu Item - \t" + item);
  try {
    Thread.sleep(1000);
  } catch (Exception e) {
    e.printStackTrace();
  }
  System.out.println("Listed - " + item);
}

여기에서 사용자는 synchronized 블록을 사용하여 전체 예제 코드를 볼 수 있습니다.

예제 코드:

import java.io.*;
import java.util.*;
// Menu class
class Menu {
  // List an item
  public void listItem(String item) {
    System.out.println("\nMenu Item - " + item);
    try {
      Thread.sleep(1000);
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("Listed - " + item);
  }
}
// Multi-thread menu listing
class MultiThreadList extends Thread {
  private String item;
  Menu MenuObj;
  /* Gets menu object and a string item*/
  MultiThreadList(String m, Menu obj) {
    item = m;
    MenuObj = obj;
  }
  public void run() {
    /* Only one Java thread can list an item at a time.*/
    synchronized (MenuObj) {
      // Menu object synchronized
      MenuObj.listItem(item);
    }
  }
}
// Main
class MainSync {
  public static void main(String args[]) {
    Menu listItem = new Menu();
    MultiThreadList M1 = new MultiThreadList("Rice", listItem);
    MultiThreadList M2 = new MultiThreadList("Icecream", listItem);

    // Start two threads
    M1.start();
    M2.start();

    // Wait for thread completion
    try {
      M1.join();
      M2.join();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

출력:

Menu Item - Rice
Listed - Rice
Menu Item - Icecream
Listed - Icecream

위의 출력에서 사용자는 한 스레드가 MenuObj에 액세스할 때 다른 스레드가 메뉴 항목 다음에 나열된 항목을 인쇄할 수 없기 때문에 이를 관찰할 수 있습니다. 여러 스레드가 동일한 블록에 동시에 액세스하려고 하면 메뉴 항목과 나열된 항목을 다른 순서로 인쇄해야 합니다.

ReentrantLock 방법 사용

Java의 ReentrantLock 클래스는 변수를 잠그는 유연한 방법입니다. ReentrantLock 클래스는 공통 리소스 또는 변수에 액세스할 때 동기화를 제공합니다.

lock()unlock() 메소드가 프로세스를 수행합니다. 한 번에 하나의 Java 스레드가 잠금을 가져옵니다. 이 시간 동안 다른 스레드는 차단 상태가 됩니다.

스레드는 잠금에 여러 번 들어갈 수 있습니다. 따라서 이름은 Re-entrant입니다. Java 스레드가 잠금을 획득하면 보유 횟수는 1이고 재진입 시 횟수가 합산됩니다.

잠금 보류 횟수는 unlock() 실행 후 1씩 감소합니다. ReentrantLock은 대기 시간을 기준으로 스레드를 제공합니다.

대기 시간이 긴 Java 스레드가 우선 순위를 갖습니다.

일부 예외의 경우 잠금을 해제하기 위해 lock()try 블록 전에 실행됩니다. unblock()finally 블록 내에서 실행됩니다.

ReentrantLock 기능

lock() 증분은 1씩 유지됩니다. 변수가 비어 있으면 Java 스레드에 잠금을 지정하십시오.
unlock() 홀드 카운트를 1씩 감소시킵니다. 보류 횟수가 0이 되면 잠금이 해제됩니다.
tryLock() 리소스가 사용 가능한 경우 이 함수는 true를 반환합니다. 그렇지 않으면 스레드가 종료됩니다.
lockInterruptibly() 하나의 Java 스레드가 잠금을 사용하면 다른 Java 스레드가 이 Java 스레드를 방해할 수 있습니다. 따라서 현재 Java 스레드는 즉시 반환해야 합니다.
getHoldCount() 리소스에 대한 잠금 수를 반환합니다.
isHeldByCurrentThread() 현재 Java 스레드가 잠금을 사용하는 경우 메서드는 true를 반환합니다.

ReentrantLock 사용

이 프로그램에서는 ReentrantLock에 대한 개체를 만듭니다. 실행 가능한 클래스 business가 실행되고 ReentrantLock에 잠금을 전달합니다.

결과를 관찰하기 위해 두 개의 풀을 사용할 수 있습니다. 프로그램은 lock()을 사용하여 잠금을 가져오고 unlock()을 사용하여 잠금을 해제합니다.

부울 finishedavailable은 잠금 가용성 및 작업 완료를 추적합니다.

두 개의 스레드가 실행 중입니다. 두 번째 상점은 자물쇠를 얻지 못하고 대기열에서 기다립니다.

Shop 1은 외부 잠금 장치를 먼저 가져오고 내부 잠금 장치를 그 다음에 가져옵니다. 현재 Shop 2가 기다리고 있습니다.

Shop 1의 잠금 보류 횟수는 2회입니다. Shop 1은 내부 잠금을 해제한 후 외부 잠금을 해제한 후 잠금 보류 횟수가 감소하고 Shop 1이 문을 닫습니다.

Shop 2는 대기열을 통해 자동으로 잠금을 설정합니다. 상점 2에 대해 프로세스가 반복됩니다.

예제 코드:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
class business implements Runnable {
  String name;
  ReentrantLock re;
  public business(ReentrantLock rl, String n) {
    re = rl;
    name = n;
  }
  public void run() {
    boolean finished = false;
    while (!finished) {
      // Get Outer Lock
      boolean isLockAvailable = re.tryLock();
      // If the lock is available
      if (isLockAvailable) {
        try {
          Date d = new Date();
          SimpleDateFormat ft = new SimpleDateFormat("hh:mm:ss");
          System.out.println("Shop  - " + name + " got outside lock at " + ft.format(d));

          Thread.sleep(1500);
          // Get Inner Lock
          re.lock();
          try {
            d = new Date();
            ft = new SimpleDateFormat("hh:mm:ss");
            System.out.println("Shop - " + name + " got inside lock at " + ft.format(d));

            System.out.println("Lock Hold Count - " + re.getHoldCount());
            Thread.sleep(1500);
          } catch (InterruptedException e) {
            e.printStackTrace();
          } finally {
            // Inner lock release
            System.out.println("Shop - " + name + " releasing inside lock");

            re.unlock();
          }
          System.out.println("Lock Hold Count - " + re.getHoldCount());
          System.out.println("Shop - " + name + " closed");
          finished = true;
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          // Outer lock release
          System.out.println("Shop - " + name + " releasing outside lock");
          re.unlock();
          System.out.println("Lock Hold Count - " + re.getHoldCount());
        }
      } else {
        System.out.println("Shop - " + name + " waiting for lock");
        try {
          Thread.sleep(1000);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }
  }
}
public class sample {
  static final int POOL_MAX = 2;
  public static void main(String[] args) {
    ReentrantLock rel = new ReentrantLock();
    ExecutorService pool = Executors.newFixedThreadPool(POOL_MAX);
    Runnable p1 = new business(rel, "Shop 1");
    Runnable p2 = new business(rel, "Shop 2");
    System.out.println("Running Pool 1");
    pool.execute(p1);
    System.out.println("Running Pool 2");
    pool.execute(p2);
    pool.shutdown();
  }
}

출력:

Running Pool 1
Running Pool 2
Shop - Shop 2 waiting for the lock
Shop  - Shop 1 got the outside lock at 11:05:47
Shop - Shop 2 waiting for the lock
Shop - Shop 1 got the inside 'lock' at 11:05:48
Lock Hold Count - 2
Shop - Shop 2 waiting for the lock
Shop - Shop 2 waiting for the lock
Shop - Shop 1 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 1 closed
Shop - Shop 1 releasing the outside lock
Lock Hold Count - 0
Shop  - Shop 2 got the outside lock at 11:05:51
Shop - Shop 2 got the inside 'lock' at 11:05:52
Lock Hold Count - 2
Shop - Shop 2 releasing the inside lock
Lock Hold Count - 1
Shop - Shop 2 closed
Shop - Shop 2 releasing the outside lock
Lock Hold Count - 0

바이너리 또는 카운팅 세마포어 사용

Java의 세마포어는 리소스에 함께 액세스하는 스레드 수를 결정합니다. Binary Semaphore는 다중 스레드 프로그래밍에서 리소스에 액세스할 수 있는 패스 또는 허가를 제공합니다.

Java 스레드는 잠금 허가를 사용할 수 있을 때까지 기다려야 합니다. acquire() 메서드는 패스를 제공하고 release() 함수는 잠금 패스를 해제합니다.

acquire() 함수는 잠금 허용이 사용 가능해질 때까지 스레드를 차단합니다.

세마포어에서 사용할 수 있는 두 가지 상태는 허용 가능허가 불가능입니다.

이 예에서 Semaphore 객체는 commonResource() 함수에 액세스합니다. 프로그램에서 두 개의 스레드가 실행 중입니다.

acquire() 메서드는 스레드에 허가를 제공하고 release()는 가용성에 따라 잠금 허가를 해제합니다.

스레드 0이 패스를 받고 바쁜 공간에 들어갑니다. 그런 다음 허가를 해제하면 여유 공간으로 나옵니다.

다음으로 스레드 1은 잠금 허가를 획득하고 임계 영역에 들어가 허가 해제 후 여유 공간에 들어옵니다.

예제 코드:

import java.util.concurrent.Semaphore;
public class SemaphoreCounter {
  Semaphore binary = new Semaphore(1);
  public static void main(String args[]) {
    final SemaphoreCounter semObj = new SemaphoreCounter();
    new Thread() {
      @Override
      public void run() {
        semObj.commonResource();
      }
    }.start();
    new Thread() {
      @Override
      public void run() {
        semObj.commonResource();
      }
    }.start();
  }
  private void commonResource() {
    try {
      binary.acquire();
      // mutual sharing resource
      System.out.println(Thread.currentThread().getName() + " busy space");
    } catch (InterruptedException ie) {
      ie.printStackTrace();
    } finally {
      binary.release();
      System.out.println(Thread.currentThread().getName() + " free space");
    }
  }
}

출력:

Thread-0 busy space
Thread-0 free space
Thread-1 busy space
Thread-1 free space

원자 변수 사용

원자 변수는 내장 함수를 사용하여 Java 다중 스레드 프로그래밍에서 동기화를 제공합니다.

프로그램은 타이머 제한과 스레드 크기를 설정합니다. executorService는 스레드 한계까지 반복하여 타이머를 제출하고 서비스를 종료합니다.

Atomic 변수의 timer 객체는 타이머를 증가시킵니다. 전체 프로세스는 동기화된 방식으로 발생합니다.

각 Java 스레드는 중단 없이 고유한 개수를 가져옵니다.

예제 코드:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

class Timer implements Runnable {
  private static AtomicInteger timer;
  private static final int timerLimit = 10;
  private static final int totThreads = 5;
  public static void main(String[] args) {
    timer = new AtomicInteger(0);
    ExecutorService executorService = Executors.newFixedThreadPool(totThreads);
    for (int i = 0; i < totThreads; i++) {
      executorService.submit(new Timer());
    }
    executorService.shutdown();
  }
  @Override
  public void run() {
    while (timer.get() < timerLimit) {
      increaseTimer();
    }
  }
  private void increaseTimer() {
    System.out.println(Thread.currentThread().getName() + " : " + timer.getAndIncrement());
  }
}

출력:

pool-1-thread-2 : 4
pool-1-thread-2 : 5
pool-1-thread-4 : 1
pool-1-thread-4 : 7
pool-1-thread-4 : 8
pool-1-thread-4 : 9
pool-1-thread-3 : 3
pool-1-thread-5 : 2
pool-1-thread-1 : 0
pool-1-thread-2 : 6

이 기사에서는 다중 스레드 프로그래밍에서 Java의 리소스를 동기화하는 네 가지 방법을 설명했습니다. 동기화 방법은 전통적인 방법입니다.

유연성을 제공하기 위해 ReentrantLock 방법을 사용합니다. ReentrantLock에는 단점도 있습니다. 프로그래머는 lock()unlock()을 작성할 때 try-catch-finally 블록을 추적해야 합니다.

Binary Semaphore는 ReentrantLock과 유사하지만 ReentrantLock에는 기본 수준 동기화 및 고정된 잠금 메커니즘만 있습니다. Binary Semaphore는 높은 수준의 동기화, 사용자 지정 잠금 및 교착 상태 방지를 제공합니다.

원자 변수는 단순한 데이터 읽기-쓰기 활동을 위한 동기화를 달성하는 방법이기도 합니다.

작가: Shubham Vora
Shubham Vora avatar Shubham Vora avatar

Shubham is a software developer interested in learning and writing about various technologies. He loves to help people by sharing vast knowledge about modern technologies via different platforms such as the DelftStack.com website.

LinkedIn GitHub

관련 문장 - Java Variable