찾다
Javajava지도 시간필기 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으로 문의하시기 바랍니다. 삭제
    带你搞懂Java结构化数据处理开源库SPL带你搞懂Java结构化数据处理开源库SPLMay 24, 2022 pm 01:34 PM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于结构化数据处理开源库SPL的相关问题,下面就一起来看一下java下理想的结构化数据处理类库,希望对大家有帮助。

    Java集合框架之PriorityQueue优先级队列Java集合框架之PriorityQueue优先级队列Jun 09, 2022 am 11:47 AM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于PriorityQueue优先级队列的相关知识,Java集合框架中提供了PriorityQueue和PriorityBlockingQueue两种类型的优先级队列,PriorityQueue是线程不安全的,PriorityBlockingQueue是线程安全的,下面一起来看一下,希望对大家有帮助。

    完全掌握Java锁(图文解析)完全掌握Java锁(图文解析)Jun 14, 2022 am 11:47 AM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于java锁的相关问题,包括了独占锁、悲观锁、乐观锁、共享锁等等内容,下面一起来看一下,希望对大家有帮助。

    一起聊聊Java多线程之线程安全问题一起聊聊Java多线程之线程安全问题Apr 21, 2022 pm 06:17 PM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于多线程的相关问题,包括了线程安装、线程加锁与线程不安全的原因、线程安全的标准类等等内容,希望对大家有帮助。

    详细解析Java的this和super关键字详细解析Java的this和super关键字Apr 30, 2022 am 09:00 AM

    本篇文章给大家带来了关于Java的相关知识,其中主要介绍了关于关键字中this和super的相关问题,以及他们的一些区别,下面一起来看一下,希望对大家有帮助。

    Java基础归纳之枚举Java基础归纳之枚举May 26, 2022 am 11:50 AM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于枚举的相关问题,包括了枚举的基本操作、集合类对枚举的支持等等内容,下面一起来看一下,希望对大家有帮助。

    java中封装是什么java中封装是什么May 16, 2019 pm 06:08 PM

    封装是一种信息隐藏技术,是指一种将抽象性函式接口的实现细节部分包装、隐藏起来的方法;封装可以被认为是一个保护屏障,防止指定类的代码和数据被外部类定义的代码随机访问。封装可以通过关键字private,protected和public实现。

    归纳整理JAVA装饰器模式(实例详解)归纳整理JAVA装饰器模式(实例详解)May 05, 2022 pm 06:48 PM

    本篇文章给大家带来了关于java的相关知识,其中主要介绍了关于设计模式的相关问题,主要将装饰器模式的相关内容,指在不改变现有对象结构的情况下,动态地给该对象增加一些职责的模式,希望对大家有帮助。

    See all articles

    핫 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를 무료로 생성하십시오.

    뜨거운 도구

    스튜디오 13.0.1 보내기

    스튜디오 13.0.1 보내기

    강력한 PHP 통합 개발 환경

    메모장++7.3.1

    메모장++7.3.1

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

    SecList

    SecList

    SecLists는 최고의 보안 테스터의 동반자입니다. 보안 평가 시 자주 사용되는 다양한 유형의 목록을 한 곳에 모아 놓은 것입니다. SecLists는 보안 테스터에게 필요할 수 있는 모든 목록을 편리하게 제공하여 보안 테스트를 더욱 효율적이고 생산적으로 만드는 데 도움이 됩니다. 목록 유형에는 사용자 이름, 비밀번호, URL, 퍼징 페이로드, 민감한 데이터 패턴, 웹 셸 등이 포함됩니다. 테스터는 이 저장소를 새로운 테스트 시스템으로 간단히 가져올 수 있으며 필요한 모든 유형의 목록에 액세스할 수 있습니다.

    ZendStudio 13.5.1 맥

    ZendStudio 13.5.1 맥

    강력한 PHP 통합 개발 환경

    에디트플러스 중국어 크랙 버전

    에디트플러스 중국어 크랙 버전

    작은 크기, 구문 강조, 코드 프롬프트 기능을 지원하지 않음