Java java지도 시간 Java 및 CLH 대기열 원칙의 스레드 안전 구현에 대한 간략한 소개(코드 예)

Java 및 CLH 대기열 원칙의 스레드 안전 구현에 대한 간략한 소개(코드 예)

Sep 17, 2018 pm 03:23 PM
멀티스레딩 경쟁 상대

이 기사는 Java 및 CLH 대기열 원칙의 스레드 안전 구현에 대한 간략한 소개(코드 예제)를 제공합니다. 이는 특정 참조 가치가 있으므로 도움이 될 수 있습니다.

동기화 차단

Java에서는 여러 스레드의 공유 데이터에 대한 동시 액세스 문제를 해결하기 위해 상호 배타적인 동기화를 달성하기 위해 동기화 키워드를 자주 사용합니다. synchronzied 키워드가 컴파일된 후 두 개의 바이트코드 명령어인 monitorenter 및 monitorexit가 synchized에 포함된 동기화 코드 블록 앞뒤에 추가됩니다. 동기화 키워드를 사용하려면 잠금 및 잠금 해제를 위한 개체를 지정해야 합니다. 예를 들면 다음과 같습니다.

public class Main {

    private static final Object LOCK = new Object();
    
    public static void fun1() {
        synchronized (LOCK) {
            // do something
        }
    }
    
    public static void fun2() {
        synchronized (LOCK) {
            // do something
        }
    }
}
로그인 후 복사

객체가 명시적으로 지정되지 않은 경우 동기화된 수정이 인스턴스 메서드인지 정적 메서드인지 여부에 따라 객체 인스턴스를 사용할지 클래스의 클래스 인스턴스를 객체로 사용할지 결정됩니다. 예:

public class SynchronizedTest {
    public synchronized void doSomething() {
        //采用实例对象作为锁对象
    }
}
로그인 후 복사
public class SynchronizedTest {
    public static synchronized void doSomething() {
        //采用SynchronizedTest.class 实例作为锁对象
    }
}
로그인 후 복사

동기화 기반으로 구현된 차단 뮤텍스로 인해 작동 스레드를 차단하는 것 외에도 운영 체제 수준에서 기본 스레드를 깨우거나 차단해야 하며 사용자 모드에서 커널로 전환해야 합니다. 이 상태 전환에 소요되는 시간은 사용자 코드의 실행 시간보다 길 수 있으므로 Java 언어에서는 동기화를 "무거운 잠금"이라고 자주 말합니다.

비차단 동기화

낙관적 잠금과 비관적 잠금

동기화 키워드를 사용한 동기화의 주요 문제점은 스레드 차단 및 깨우기로 인한 성능 소모입니다. 동기화 차단은 경쟁 가능성이 있는 한 잠금을 수행해야 한다고 믿는 비관적인 동시성 전략입니다.
그러나 동기화 전략에는 또 다른 낙관적 전략이 있습니다. 낙관적 동시성 전략은 데이터 작업을 진행하는 데 다른 스레드도 해당 데이터에서 작업한 것이 발견되지 않으면 작업이 성공한 것으로 간주됩니다. 다른 스레드도 데이터를 조작하는 경우 일반적으로 성공할 때까지 계속해서 재시도합니다. 이 낙관적 잠금 전략은 차단 스레드가 필요하지 않으며 비차단 동기화 수단입니다.

CAS

낙관적 동시성 전략은 주로 두 가지 중요한 단계로 구성됩니다. 하나는 데이터를 작동하는 것이고, 다른 하나는 충돌을 감지하는 것, 즉 다른 스레드도 데이터에 대해 작동하는지 여부를 감지하는 것입니다. 여기서 데이터 작업 및 충돌 감지에는 원자성이 필요합니다. 그렇지 않으면 i++와 유사한 문제가 쉽게 발생합니다.
CAS는 비교 및 ​​교환을 의미합니다. 현재 대부분의 CPU는 기본적으로 CAS 원자성 명령을 지원합니다. 예를 들어 IA64 및 x86의 명령 세트에는 CAS 기능을 완료하기 위한 cmpxchg와 같은 명령이 있습니다. .
CAS 명령에는 일반적으로 값의 메모리 주소, 예상되는 이전 값 및 새 값이라는 세 가지 매개변수가 필요합니다. CAS 명령어가 실행될 때 메모리 주소의 값이 예상되는 이전 값과 일치하면 프로세서는 메모리 주소의 값을 새 값으로 업데이트하고, 그렇지 않으면 업데이트되지 않습니다. 이 작업은 CPU 내에서 원자성이 보장됩니다.
Java에는 CAS 관련 API가 많이 있습니다. 가장 일반적인 API에는 AtomicInteger, 와 같은 <code>java.util.concurrent 패키지 아래에 다양한 원자 클래스가 포함됩니다. >AtomicReference등. java.util.concurrent 包下的各种原子类,例如AtomicIntegerAtomicReference等等。
这些类都支持 CAS 操作,其内部实际上也依赖于 sun.misc.Unsafe 这个类里的 compareAndSwapInt() 和 compareAndSwapLong() 方法。
CAS 并非是完美无缺的,尽管它能保证原子性,但它存在一个著名的 ABA 问题。一个变量初次读取的时候值为 A,再一次读取的时候也为 A,那么我们是否能说明这个变量在两次读取中间没有发生过变化?不能。在这期间,变量可能由 A 变为 B,再由 B 变为 A,第二次读取的时候看到的是 A,但实际上这个变量发生了变化。一般的代码逻辑不会在意这个 ABA 问题,因为根据代码逻辑它不会影响并发的安全性,但如果在意的话,可能考虑采用阻塞同步的方式而不是 CAS。实际上 JDK 本身也对这个 ABA 问题解决方案,提供了 AtomicStampedReference이러한 클래스는 모두 CAS 작업을 지원하며 내부적으로는 실제로 sun.misc.Unsafe 클래스의 CompareAndSwapInt() 및 CompareAndSwapLong() 메서드에 의존합니다.

CAS는 원자성을 보장하지만 그 유명한

ABA 문제를 안고 있습니다. 변수의 값은 처음 읽을 때 A이고, 다시 읽을 때에도 A입니다. 이 변수는 두 번의 읽기 사이에 변경되지 않았다고 설명할 수 있습니까? 할 수 없습니다. 이 기간 동안 변수는 A에서 B로, 그리고 B에서 A로 바뀔 수 있습니다. 두 번째 읽을 때는 A로 보이지만 실제로는 변수가 변경되었습니다. 일반 코드 로직에서는 이 ABA 문제를 신경 쓰지 않습니다. 코드 로직에 따르면 동시성 보안에 영향을 주지 않기 때문입니다. 하지만 관심이 있는 경우 CAS 대신 차단 동기화를 사용하는 것을 고려할 수 있습니다. 실제로 JDK 자체도 ABA 문제를 해결하기 위해 변수에 버전을 추가하는 AtomicStampedReference 클래스를 제공하여 이 ABA 문제에 대한 솔루션을 제공합니다.

스핀 잠금
동기화로 표시되는 차단 동기화. 차단된 스레드가 스레드 작업을 재개하기 때문입니다. 이를 위해서는 운영 체제 수준에서 사용자 모드와 커널 모드 간 전환이 필요하며 이는 시스템 성능에 큰 영향을 미칩니다. 큰. 스핀 잠금의 전략은 스레드가 잠금을 획득할 때 다른 스레드가 이미 잠금을 차지하고 있음을 발견하면 즉시 CPU의 실행 시간 조각을 포기하지 않고 "의미 없는" 루프에 들어가는 것입니다. 스레드 잠금이 포기되었는지 확인하십시오.

그러나 스핀 잠금은

중요 섹션

이 상대적으로 작은 상황에 적합합니다. 잠금을 너무 오랫동안 유지하면 스핀 작업 자체가 시스템 성능을 낭비하게 됩니다. 🎜🎜다음은 간단한 스핀 잠금 구현입니다. 🎜
import java.util.concurrent.atomic.AtomicReference;
public class SpinLock {
   private AtomicReference<Thread> owner = new AtomicReference<Thread>();
   public void lock() {
       Thread currentThread = Thread.currentThread();
        // 如果锁未被占用,则设置当前线程为锁的拥有者
       while (!owner.compareAndSet(null, currentThread)) {}
   }

   public void unlock() {
       Thread currentThread = Thread.currentThread();
        // 只有锁的拥有者才能释放锁
       owner.compareAndSet(currentThread, null);
   }
}
로그인 후 복사

上述的代码中, owner 变量保存获得了锁的线程。这里的自旋锁有一些缺点,第一个是没有保证公平性,等待获取锁的线程之间,无法按先后顺序分别获得锁;另一个,由于多个线程会去操作同一个变量 owner,在 CPU 的系统中,存在着各个 CPU 之间的缓存数据需要同步,保证一致性,这会带来性能问题。

公平的自旋

为了解决公平性问题,可以让每个锁拥有一个服务号,表示正在服务的线程,而每个线程尝试获取锁之前需要先获取一个排队号,然后不断轮询当前锁的服务号是否是自己的服务号,如果是,则表示获得了锁,否则就继续轮询。下面是一个简单的实现:

import java.util.concurrent.atomic.AtomicInteger;

public class TicketLock {
   private AtomicInteger serviceNum = new AtomicInteger(); // 服务号
   private AtomicInteger ticketNum = new AtomicInteger(); // 排队号

   public int lock() {
       // 首先原子性地获得一个排队号
       int myTicketNum = ticketNum.getAndIncrement();
       // 只要当前服务号不是自己的就不断轮询
       while (serviceNum.get() != myTicketNum) {
       }
       return myTicketNum;
    }

    public void unlock(int myTicket) {
        // 只有当前线程拥有者才能释放锁
        int next = myTicket + 1;
        serviceNum.compareAndSet(myTicket, next);
    }
}
로그인 후 복사

虽然解决了公平性的问题,但依然存在前面说的多 CPU 缓存的同步问题,因为每个线程占用的 CPU 都在同时读写同一个变量 serviceNum,这会导致繁重的系统总线流量和内存操作次数,从而降低了系统整体的性能。

MCS 自旋锁

MCS 的名称来自其发明人的名字:John Mellor-Crummey和Michael Scott。
MCS 的实现是基于链表的,每个申请锁的线程都是链表上的一个节点,这些线程会一直轮询自己的本地变量,来知道它自己是否获得了锁。已经获得了锁的线程在释放锁的时候,负责通知其它线程,这样 CPU 之间缓存的同步操作就减少了很多,仅在线程通知另外一个线程的时候发生,降低了系统总线和内存的开销。实现如下所示:

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class MCSLock {
    public static class MCSNode {
        volatile MCSNode next;
        volatile boolean isWaiting = true; // 默认是在等待锁
    }
    volatile MCSNode queue;// 指向最后一个申请锁的MCSNode
    private static final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater
            .newUpdater(MCSLock.class, MCSNode.class, "queue");

    public void lock(MCSNode currentThread) {
        MCSNode predecessor = UPDATER.getAndSet(this, currentThread);// step 1
        if (predecessor != null) {
            predecessor.next = currentThread;// step 2
            while (currentThread.isWaiting) {// step 3
            }
        } else { // 只有一个线程在使用锁,没有前驱来通知它,所以得自己标记自己已获得锁
            currentThread.isWaiting = false;
        }
    }

    public void unlock(MCSNode currentThread) {
        if (currentThread.isWaiting) {// 锁拥有者进行释放锁才有意义
            return;
        }

        if (currentThread.next == null) {// 检查是否有人排在自己后面
            if (UPDATER.compareAndSet(this, currentThread, null)) {// step 4
                // compareAndSet返回true表示确实没有人排在自己后面
                return;
            } else {
                // 突然有人排在自己后面了,可能还不知道是谁,下面是等待后续者
                // 这里之所以要忙等是因为:step 1执行完后,step 2可能还没执行完
                while (currentThread.next == null) { // step 5
                }
            }
        }
        currentThread.next.isWaiting = false;
        currentThread.next = null;// for GC
    }
}
로그인 후 복사

MCS 的能够保证较高的效率,降低不必要的性能消耗,并且它是公平的自旋锁。

CLH 自旋锁

CLH 锁与 MCS 锁的原理大致相同,都是各个线程轮询各自关注的变量,来避免多个线程对同一个变量的轮询,从而从 CPU 缓存一致性的角度上减少了系统的消耗。
CLH 锁的名字也与他们的发明人的名字相关:Craig,Landin and Hagersten。
CLH 锁与 MCS 锁最大的不同是,MCS 轮询的是当前队列节点的变量,而 CLH 轮询的是当前节点的前驱节点的变量,来判断前一个线程是否释放了锁。
实现如下所示:

import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
public class CLHLock {
    public static class CLHNode {
        private volatile boolean isWaiting = true; // 默认是在等待锁
    }
    private volatile CLHNode tail ;
    private static final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater
            . newUpdater(CLHLock.class, CLHNode .class , "tail" );
    public void lock(CLHNode currentThread) {
        CLHNode preNode = UPDATER.getAndSet( this, currentThread);
        if(preNode != null) {//已有线程占用了锁,进入自旋
            while(preNode.isWaiting ) {
            }
        }
    }

    public void unlock(CLHNode currentThread) {
        // 如果队列里只有当前线程,则释放对当前线程的引用(for GC)。
        if (!UPDATER .compareAndSet(this, currentThread, null)) {
            // 还有后续线程
            currentThread.isWaiting = false ;// 改变状态,让后续线程结束自旋
        }
    }
}
로그인 후 복사

从上面可以看到,MCS 和 CLH 相比,CLH 的代码比 MCS 要少得多;CLH是在前驱节点的属性上自旋,而MCS是在本地属性变量上自旋;CLH的队列是隐式的,通过轮询关注上一个节点的某个变量,隐式地形成了链式的关系,但CLHNode并不实际持有下一个节点,MCS的队列是物理存在的,而 CLH 的队列是逻辑上存在的;此外,CLH 锁释放时只需要改变自己的属性,MCS 锁释放则需要改变后继节点的属性。

CLH 队列是 J.U.C 中 AQS 框架实现的核心原理。

위 내용은 Java 및 CLH 대기열 원칙의 스레드 안전 구현에 대한 간략한 소개(코드 예)의 상세 내용입니다. 자세한 내용은 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. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 채팅 명령 및 사용 방법
1 몇 달 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

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

SublimeText3 중국어 버전

SublimeText3 중국어 버전

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

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

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

C++ 함수 예외 및 멀티스레딩: 동시 환경의 오류 처리 C++ 함수 예외 및 멀티스레딩: 동시 환경의 오류 처리 May 04, 2024 pm 04:42 PM

C++의 함수 예외 처리는 다중 스레드 환경에서 스레드 안전성과 데이터 무결성을 보장하는 데 특히 중요합니다. try-catch 문을 사용하면 특정 유형의 예외가 발생할 때 이를 포착하고 처리하여 프로그램 충돌이나 데이터 손상을 방지할 수 있습니다.

PHP에서 멀티스레딩을 구현하는 방법은 무엇입니까? PHP에서 멀티스레딩을 구현하는 방법은 무엇입니까? May 06, 2024 pm 09:54 PM

PHP 멀티스레딩은 독립적으로 실행되는 스레드를 생성하여 하나의 프로세스에서 여러 작업을 동시에 실행하는 것을 의미합니다. PHP에서 Pthreads 확장을 사용하여 멀티스레딩 동작을 시뮬레이션할 수 있습니다. 설치 후 Thread 클래스를 사용하여 스레드를 생성하고 시작할 수 있습니다. 예를 들어, 대량의 데이터를 처리할 경우 데이터를 여러 블록으로 나누어 해당 개수의 스레드를 생성해 동시 처리함으로써 효율성을 높일 수 있다.

Golang API 디자인에 동시성 및 코루틴 적용 Golang API 디자인에 동시성 및 코루틴 적용 May 07, 2024 pm 06:51 PM

동시성과 코루틴은 GoAPI 설계에서 다음을 위해 사용됩니다. 고성능 처리: 성능 향상을 위해 여러 요청을 동시에 처리합니다. 비동기 처리: 코루틴을 사용하여 작업(예: 이메일 보내기)을 비동기적으로 처리하고 메인 스레드를 해제합니다. 스트림 처리: 코루틴을 사용하여 데이터 스트림(예: 데이터베이스 읽기)을 효율적으로 처리합니다.

C++의 멀티스레딩에서 공유 리소스를 처리하는 방법은 무엇입니까? C++의 멀티스레딩에서 공유 리소스를 처리하는 방법은 무엇입니까? Jun 03, 2024 am 10:28 AM

뮤텍스는 C++에서 다중 스레드 공유 리소스를 처리하는 데 사용됩니다. std::mutex를 통해 뮤텍스를 만듭니다. mtx.lock()을 사용하여 뮤텍스를 획득하고 공유 리소스에 대한 독점 액세스를 제공합니다. 뮤텍스를 해제하려면 mtx.unlock()을 사용하세요.

C++에서 멀티스레드 프로그램을 테스트하기 위한 과제와 전략 C++에서 멀티스레드 프로그램을 테스트하기 위한 과제와 전략 May 31, 2024 pm 06:34 PM

다중 스레드 프로그램 테스트는 반복 불가능성, 동시성 오류, 교착 상태 및 가시성 부족과 같은 문제에 직면합니다. 전략은 다음과 같습니다. 단위 테스트: 스레드 동작을 확인하기 위해 각 스레드에 대한 단위 테스트를 작성합니다. 멀티스레드 시뮬레이션: 시뮬레이션 프레임워크를 사용하여 스레드 일정을 제어하여 프로그램을 테스트합니다. 데이터 경합 감지: 도구를 사용하여 valgrind와 같은 잠재적인 데이터 경합을 찾습니다. 디버깅: 디버거(예: gdb)를 사용하여 런타임 프로그램 상태를 검사하고 데이터 경합의 원인을 찾습니다.

멀티스레드 환경에서 C++ 메모리 관리의 과제와 대책은? 멀티스레드 환경에서 C++ 메모리 관리의 과제와 대책은? Jun 05, 2024 pm 01:08 PM

다중 스레드 환경에서 C++ 메모리 관리는 데이터 경합, 교착 상태 및 메모리 누수와 같은 문제에 직면합니다. 대책에는 다음이 포함됩니다. 1. 뮤텍스 및 원자 변수와 같은 동기화 메커니즘을 사용합니다. 2. 잠금 없는 데이터 구조를 사용합니다. 3. 스마트 포인터를 사용합니다. 4. (선택 사항) 가비지 수집을 구현합니다.

단위 테스트 Go 동시 기능 가이드 단위 테스트 Go 동시 기능 가이드 May 03, 2024 am 10:54 AM

단위 테스트 동시 기능은 동시 환경에서 올바른 동작을 보장하는 데 도움이 되므로 매우 중요합니다. 동시 기능을 테스트할 때는 상호 배제, 동기화, 격리와 같은 기본 원칙을 고려해야 합니다. 동시 기능은 경쟁 조건을 시뮬레이션하고, 테스트하고, 결과를 확인하여 단위 테스트할 수 있습니다.

C++ 기술의 예외 처리: 다중 스레드 환경에서 예외를 올바르게 처리하는 방법은 무엇입니까? C++ 기술의 예외 처리: 다중 스레드 환경에서 예외를 올바르게 처리하는 방법은 무엇입니까? May 09, 2024 pm 12:36 PM

다중 스레드 C++에서 예외 처리는 적시성, 스레드 안전성 및 명확성이라는 원칙을 따릅니다. 실제로는 뮤텍스 또는 원자 변수를 사용하여 예외 처리 코드의 스레드 안전성을 보장할 수 있습니다. 또한 다중 스레드 환경에서 안전하고 효율적으로 실행되도록 예외 처리 코드의 재진입, 성능 및 테스트를 고려하십시오.

See all articles