목차
머리말
LockSupport 구현 원리
직접 LockSupport 구현
구현 원칙
LockSupport 프로토콜 규정을 직접 구현
Tools
具体实现
完整代码
JVM实现一瞥
Java java지도 시간 필기 Java LockSupport 구현 방법

필기 Java LockSupport 구현 방법

May 07, 2023 am 08:25 AM
java locksupport

    머리말

    ReentrantLock의 내부 구현이나 기타 도구 등 JDK에서 제공하는 다양한 동시성 도구 중에서 자주 사용하는 도구가 있는데, 이 도구가 LockSupport입니다. LockSupport는 스레드 차단을 위한 가장 기본적인 기능을 제공하므로 스레드를 차단하거나 깨울 수 있으므로 동시 시나리오에서 자주 사용됩니다.

    LockSupport 구현 원리

    LockSupport 구현 원리를 이해하기 전에 먼저 사례를 통해 LockSupport의 기능을 이해해 보도록 하겠습니다!

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
     
    public class Demo {
     
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          System.out.println("park 之前");
          LockSupport.park(); // park 函数可以将调用这个方法的线程挂起
          System.out.println("park 之后");
        });
        thread.start();
        TimeUnit.SECONDS.sleep(5);
        System.out.println("主线程休息了 5s");
        System.out.println("主线程 unpark thread");
        LockSupport.unpark(thread); // 主线程将线程 thread 唤醒 唤醒之后线程 thread 才可以继续执行
      }
    }
    로그인 후 복사

    위 코드의 출력은 다음과 같습니다.

    park Before
    the main threadrest for 5s
    main thread unpark thread
    park after

    얼핏 보면 위의 LockSupport의 park 및 unpark 구현 기능과 wait 및 신호 구현 기능은 동일한 것처럼 보이지만 실제로는 다릅니다. 다음 코드를 살펴보겠습니다.

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.LockSupport;
     
    public class Demo02 {
      public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
          try {
            TimeUnit.SECONDS.sleep(5);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
          System.out.println("park 之前");
          LockSupport.park(); // 线程 thread 后进行 park 操作 
          System.out.println("park 之后");
        });
        thread.start();
        System.out.println("主线程 unpark thread");
        LockSupport.unpark(thread); // 先进行 unpark 操作
     
      }
    }
    로그인 후 복사

    위 코드의 출력은 다음과 같습니다.

    main thread unpark thread
    park before
    park

    위 코드에서 메인 스레드가 먼저 unpark 작업을 수행한 다음 스레드가 park 작업을 수행하면 프로그램도 정상적으로 실행될 수 있습니다. 그러나 신호 호출이 대기 호출 이전에 발생하면 프로그램은 실행되지 않습니다. 예를 들어 다음 코드는

    import java.util.concurrent.TimeUnit;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class Demo03 {
     
      private static final ReentrantLock lock = new ReentrantLock();
      private static final Condition condition = lock.newCondition();
     
      public static void thread() throws InterruptedException {
        lock.lock();
     
        try {
          TimeUnit.SECONDS.sleep(5);
          condition.await();
          System.out.println("等待完成");
        }finally {
          lock.unlock();
        }
      }
     
      public static void mainThread() {
        lock.lock();
        try {
          System.out.println("发送信号");
          condition.signal();
        }finally {
          lock.unlock();
          System.out.println("主线程解锁完成");
        }
      }
     
      public static void main(String[] args) {
        Thread thread = new Thread(() -> {
          try {
            thread();
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        });
        thread.start();
     
        mainThread();
      }
    }
    로그인 후 복사

    위 코드의 출력은 다음과 같습니다.

    신호 보내기
    메인 스레드 잠금 해제가 완료되었습니다.

    위 코드에서 "완료 대기 중"은 출력되지 않습니다. 이는 신호 함수가 대기 전에 호출되기 때문입니다. 신호 함수는 그 전에 실행된 대기 함수에만 영향을 미치고 대기에는 영향을 미치지 않습니다. 그 뒤에 호출되는 함수는 영향을 미칩니다.

    그렇다면 이 효과의 원인은 무엇일까요?

    실제로 JVM은 LockSupport를 구현할 때 각 스레드에 대해 카운터 변수 _counter를 유지합니다. 이 변수는 "라이센스 수"를 나타냅니다. 동시에 라이센스가 있는 경우에만 실행할 수 있습니다. 최대 라이센스 수는 1개만 가능합니다. 파크를 한 번 호출하면 라이센스 수가 하나씩 감소됩니다. unpark가 한 번 호출되면 카운터가 1씩 증가하지만 카운터 값은 1을 초과할 수 없습니다.

    스레드가 park를 호출하면 라이센스를 기다려야 합니다. 라이센스를 얻은 후에만 스레드를 계속 실행할 수 있습니다. 또는 park 전에 라이센스를 얻은 경우에는 차단할 필요가 없으며 직접 실행될 수 있습니다. 실행.

    직접 LockSupport 구현

    구현 원칙

    이전 기사에서 주요 내부 구현은 라이센스를 통해 구현됩니다.

    • 각 스레드가 얻을 수 있는 라이센스 최대 수 1입니다.

    • unpark 메소드가 호출되면 스레드는 라이센스를 얻을 수 있습니다. 라이센스 수의 상한은 1입니다. 이미 라이센스가 있는 경우 라이센스를 누적할 수 없습니다.

    • park 메서드를 호출할 때 park 메서드를 호출하는 스레드에 라이선스가 없으면 다른 스레드가 unpark 메서드를 호출하고 스레드에 라이선스를 발급할 때까지 스레드를 일시 중단해야 스레드를 계속 구현할 수 있습니다. . 그러나 스레드에 이미 라이센스가 있는 경우 스레드는 차단되지 않고 직접 실행할 수 있습니다.

    LockSupport 프로토콜 규정을 직접 구현

    Parker의 자체 구현에서는 라이센스 수가 0보다 크거나 같을 때 스레드에 대한 라이센스 수를 기록하는 카운터를 각 스레드에 제공할 수도 있습니다. , 스레드가 실행될 수 있습니다. 그렇지 않으면 스레드가 차단되어야 합니다. 프로토콜의 특정 규칙은 다음과 같습니다:

    • 초기 스레드에 대한 라이센스 수는 0입니다.

    • park를 호출할 때 카운터 값이 1이고 카운터 값이 0이 되면 스레드는 계속 실행될 수 있습니다.

    • park를 호출할 때 카운터 값이 0이면 스레드는 계속 실행할 수 없으며 스레드는 일시 중단되어야 하며 카운터 값은 -1로 설정됩니다.

    • unpark를 호출할 때 unpark된 스레드의 카운터 값이 0이면 카운터 값을 1로 변경해야 합니다.

    • unpark를 호출할 때 unpark된 스레드의 카운터 값이 1이면 카운터의 최대값이 1이므로 카운터 값을 변경할 필요가 없습니다.

    • unpark를 호출할 때 카운터 값이 -1이면 스레드가 일시 중지되었음을 의미하므로 스레드를 깨우고 카운터 값을 0으로 설정해야 합니다.

    Tools

    스레드 차단 및 깨우기가 포함되므로 재진입 잠금 ReentrantLock 및 조건 변수 Condition을 사용할 수 있으므로 이 두 도구의 사용에 익숙해야 합니다.

    ReentrantLock은 주로 잠금 및 잠금 해제에 사용되며 중요한 영역을 보호하는 데 사용됩니다.

    Condition.awat 메소드는 스레드를 차단하는 데 사용됩니다.

    Condition.signal 메소드는 스레드를 깨우는 데 사용됩니다.

    因为我们在unpark方法当中需要传入具体的线程,将这个线程发放许可证,同时唤醒这个线程,因为是需要针对特定的线程进行唤醒,而condition唤醒的线程是不确定的,因此我们需要为每一个线程维护一个计数器和条件变量,这样每个条件变量只与一个线程相关,唤醒的肯定就是一个特定的线程。我们可以使用HashMap进行实现,键为线程,值为计数器或者条件变量。

    具体实现

    因此综合上面的分析我们的类变量如下:

    private final ReentrantLock lock; // 用于保护临界去
    private final HashMap<Thread, Integer> permits; // 许可证的数量
    private final HashMap<Thread, Condition> conditions; // 用于唤醒和阻塞线程的条件变量
    로그인 후 복사

    构造函数主要对变量进行赋值:

    public Parker() {
      lock = new ReentrantLock();
      permits = new HashMap<>();
      conditions = new HashMap<>();
    }
    로그인 후 복사

    park方法

    public void park() {
      Thread t = Thread.currentThread(); // 首先得到当前正在执行的线程
      if (conditions.get(t) == null) { // 如果还没有线程对应的condition的话就进行创建
        conditions.put(t, lock.newCondition());
      }
      lock.lock();
      try {
        // 如果许可证变量还没有创建 或者许可证等于0 说明没有许可证了 线程需要被挂起
        if (permits.get(t) == null || permits.get(t) == 0) {
          permits.put(t, -1); // 同时许可证的数目应该设置为-1
          conditions.get(t).await();
        }else if (permits.get(t) > 0) {
          permits.put(t, 0); // 如果许可证的数目大于0 也就是为1 说明线程已经有了许可证因此可以直接被放行 但是需要消耗一个许可证
        }
      } catch (InterruptedException e) {
        e.printStackTrace();
      } finally {
        lock.unlock();
      }
    }
    로그인 후 복사

    unpark方法

    public void unpark(Thread thread) {
      Thread t = thread; // 给线程 thread 发放一个许可证
      lock.lock();
      try {
        if (permits.get(t) == null) // 如果还没有创建许可证变量 说明线程当前的许可证数量等于初始数量也就是0 因此方法许可证之后 许可证的数量为 1
          permits.put(t, 1);
        else if (permits.get(t) == -1) { // 如果许可证数量为-1,则说明肯定线程 thread 调用了park方法,而且线程 thread已经被挂起了 因此在 unpark 函数当中不急需要将许可证数量这是为0 同时还需要将线程唤醒
          permits.put(t, 0);
          conditions.get(t).signal();
        }else if (permits.get(t) == 0) { // 如果许可证数量为0 说明线程正在执行 因此许可证数量加一
          permits.put(t, 1);
        } // 除此之外就是许可证为1的情况了 在这种情况下是不需要进行操作的 因为许可证最大的数量就是1
      }finally {
        lock.unlock();
      }
    }
    로그인 후 복사

    完整代码

    import java.util.HashMap;
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
     
    public class Parker {
     
      private final ReentrantLock lock;
      private final HashMap<Thread, Integer> permits;
      private final HashMap<Thread, Condition> conditions;
     
      public Parker() {
        lock = new ReentrantLock();
        permits = new HashMap<>();
        conditions = new HashMap<>();
      }
     
      public void park() {
        Thread t = Thread.currentThread();
        if (conditions.get(t) == null) {
          conditions.put(t, lock.newCondition());
        }
        lock.lock();
        try {
          if (permits.get(t) == null || permits.get(t) == 0) {
            permits.put(t, -1);
            conditions.get(t).await();
          }else if (permits.get(t) > 0) {
            permits.put(t, 0);
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        } finally {
          lock.unlock();
        }
      }
     
      public void unpark(Thread thread) {
        Thread t = thread;
        lock.lock();
        try {
          if (permits.get(t) == null)
            permits.put(t, 1);
          else if (permits.get(t) == -1) {
            permits.put(t, 0);
            conditions.get(t).signal();
          }else if (permits.get(t) == 0) {
            permits.put(t, 1);
          }
        }finally {
          lock.unlock();
        }
      }
    }
    로그인 후 복사

    JVM实现一瞥

    其实在JVM底层对于park和unpark的实现也是基于锁和条件变量的,只不过是用更加底层的操作系统和libc(linux操作系统)提供的API进行实现的。虽然API不一样,但是原理是相仿的,思想也相似。

    比如下面的就是JVM实现的unpark方法:

    void Parker::unpark() {
      int s, status;
      // 进行加锁操作 相当于 可重入锁的 lock.lock()
      status = pthread_mutex_lock(_mutex);
      assert (status == 0, "invariant");
      s = _counter;
      _counter = 1;
      if (s < 1) {
        // 如果许可证小于 1 进行下面的操作
        if (WorkAroundNPTLTimedWaitHang) {
          // 这行代码相当于 condition.signal() 唤醒线程
          status = pthread_cond_signal (_cond);
          assert (status == 0, "invariant");
          // 解锁操作 相当于可重入锁的 lock.unlock()
          status = pthread_mutex_unlock(_mutex);
          assert (status == 0, "invariant");
        } else {
          status = pthread_mutex_unlock(_mutex);
          assert (status == 0, "invariant");
          status = pthread_cond_signal (_cond);
          assert (status == 0, "invariant");
        }
      } else {
        // 如果有许可证 也就是 s == 1 那么不许要将线程挂起
        // 解锁操作 相当于可重入锁的 lock.unlock()
        pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
      }
    }
    로그인 후 복사

    JVM实现的park方法,如果没有许可证也是会将线程挂起的:

    필기 Java LockSupport 구현 방법

    위 내용은 필기 Java LockSupport 구현 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.

    핫 AI 도구

    Undresser.AI Undress

    Undresser.AI Undress

    사실적인 누드 사진을 만들기 위한 AI 기반 앱

    AI Clothes Remover

    AI Clothes Remover

    사진에서 옷을 제거하는 온라인 AI 도구입니다.

    Undress AI Tool

    Undress AI Tool

    무료로 이미지를 벗다

    Clothoff.io

    Clothoff.io

    AI 옷 제거제

    AI Hentai Generator

    AI Hentai Generator

    AI Hentai를 무료로 생성하십시오.

    인기 기사

    R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
    3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. 최고의 그래픽 설정
    3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
    R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
    3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
    WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
    3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

    뜨거운 도구

    메모장++7.3.1

    메모장++7.3.1

    사용하기 쉬운 무료 코드 편집기

    SublimeText3 중국어 버전

    SublimeText3 중국어 버전

    중국어 버전, 사용하기 매우 쉽습니다.

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    드림위버 CS6

    드림위버 CS6

    시각적 웹 개발 도구

    SublimeText3 Mac 버전

    SublimeText3 Mac 버전

    신 수준의 코드 편집 소프트웨어(SublimeText3)

    자바의 제곱근 자바의 제곱근 Aug 30, 2024 pm 04:26 PM

    자바의 제곱근 안내 여기서는 예제와 코드 구현을 통해 Java에서 Square Root가 어떻게 작동하는지 설명합니다.

    자바의 완전수 자바의 완전수 Aug 30, 2024 pm 04:28 PM

    Java의 완전수 가이드. 여기서는 정의, Java에서 완전 숫자를 확인하는 방법, 코드 구현 예제에 대해 논의합니다.

    자바의 암스트롱 번호 자바의 암스트롱 번호 Aug 30, 2024 pm 04:26 PM

    자바의 암스트롱 번호 안내 여기에서는 일부 코드와 함께 Java의 Armstrong 번호에 대한 소개를 논의합니다.

    Java의 난수 생성기 Java의 난수 생성기 Aug 30, 2024 pm 04:27 PM

    Java의 난수 생성기 안내. 여기서는 예제를 통해 Java의 함수와 예제를 통해 두 가지 다른 생성기에 대해 설명합니다.

    자바의 웨카 자바의 웨카 Aug 30, 2024 pm 04:28 PM

    Java의 Weka 가이드. 여기에서는 소개, weka java 사용 방법, 플랫폼 유형 및 장점을 예제와 함께 설명합니다.

    Java의 스미스 번호 Java의 스미스 번호 Aug 30, 2024 pm 04:28 PM

    Java의 Smith Number 가이드. 여기서는 정의, Java에서 스미스 번호를 확인하는 방법에 대해 논의합니다. 코드 구현의 예.

    Java Spring 인터뷰 질문 Java Spring 인터뷰 질문 Aug 30, 2024 pm 04:29 PM

    이 기사에서는 가장 많이 묻는 Java Spring 면접 질문과 자세한 답변을 보관했습니다. 그래야 면접에 합격할 수 있습니다.

    Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Java 8 Stream foreach에서 나누거나 돌아 오시겠습니까? Feb 07, 2025 pm 12:09 PM

    Java 8은 스트림 API를 소개하여 데이터 컬렉션을 처리하는 강력하고 표현적인 방법을 제공합니다. 그러나 스트림을 사용할 때 일반적인 질문은 다음과 같은 것입니다. 기존 루프는 조기 중단 또는 반환을 허용하지만 스트림의 Foreach 메소드는이 방법을 직접 지원하지 않습니다. 이 기사는 이유를 설명하고 스트림 처리 시스템에서 조기 종료를 구현하기위한 대체 방법을 탐색합니다. 추가 읽기 : Java Stream API 개선 스트림 foreach를 이해하십시오 Foreach 메소드는 스트림의 각 요소에서 하나의 작업을 수행하는 터미널 작동입니다. 디자인 의도입니다

    See all articles