>Java >java지도 시간 >Java 동시 프로그래밍의 세 가지 요소에 대해 자세히 알아보세요.

Java 동시 프로그래밍의 세 가지 요소에 대해 자세히 알아보세요.

WBOY
WBOY앞으로
2022-04-22 11:54:482756검색

이 기사는 원자성, 가시성, 질서, 원인 및 정의 등 동시 프로그래밍의 세 가지 요소와 관련된 문제를 주로 소개하는 java에 대한 관련 지식을 제공합니다. 아래 내용을 살펴보시기 바랍니다. 모두에게 도움이 될 것입니다.

Java 동시 프로그래밍의 세 가지 요소에 대해 자세히 알아보세요.

추천 학습: "java 비디오 튜토리얼"

1 원자성

1.1 원자성의 정의

원자는 모두 실행되거나 중단되지 않는 하나 이상의 작업을 나타냅니다. 실행 중에 다른 작업에 의해 실행되거나 전혀 실행되지 않습니다.

1.2 원자성 문제의 원인

스레드 스위칭은 원자성 문제의 원인입니다. 스레드 스위칭은 CPU 활용도를 높이기 위한 것입니다.

count ++를 예로 들면 최소 3개의 CPU 명령이 필요합니다.

  • 명령 1: 먼저 변수 count를 메모리에서 CPU 레지스터로 로드해야 합니다.
  • 명령 2: 그런 다음 레지스터의 +1 작업
  • 지침 3: 마지막으로 결과를 메모리에 씁니다(캐싱 메커니즘으로 인해 메모리 대신 CPU 캐시가 기록됩니다).

count=0으로 가정합니다. 명령 1이 실행된 후 스레드 A가 스레드를 전환하고 아래 그림의 순서에 따라 스레드 A와 스레드 B가 실행되면 두 스레드 모두 count+= 작업을 수행했음을 알 수 있습니다. 1, 하지만 얻은 결과는 우리가 예상한 대로 2가 아니라 1입니다.

1.3 원자적 연산

멀티 스레드 환경에서 Java는 기본 데이터 유형의 변수 및 할당 작업이 원자적임을 보장합니다. (참고: 32비트 JDK 환경에서 64비트 데이터의 경우 읽기 long, double과 같은 원자 연산*이 아닙니다)

1.4 원자성 문제를 해결하는 방법

공유 변수에 대한 수정이 상호 배타적이라는 것을 보장할 수 있다면 그것이 단일 코어 CPU이든 멀티 코어 CPU, 이는 원자성을 보장합니다. 잠금은 동기화 및 잠금 사용과 같은 원자성 문제를 해결할 수 있습니다.

2 가시성

2.1 가시성 정의

가시성은 여러 스레드가 공유 변수를 작동할 때 한 스레드가 변수를 수정한 후 다른 스레드가 수정 결과를 즉시 볼 수 있음을 의미합니다.

2.2 가시성 문제의 원인

CPU 캐시와 메모리 간의 데이터 일관성이 가시성 문제의 원인입니다. CPU 캐시는 CPU의 효율성을 높이기 위한 것입니다.

2.3 가시성 문제 해결

가시성 문제의 원인은 CPU 캐시이므로 CPU 캐시를 비활성화할 수 있습니다.

  • 휘발성 필드는 CPU 캐시를 비활성화하고 가시성 문제를 해결할 수 있습니다.
  • 동기화 및 잠금 모두 가시성을 보장할 수 있습니다.

2.4 가시성 규칙이란 무엇입니까?

가시성 규칙은 Happens-Before 규칙입니다.

발생-규칙 전:

  • 간단히 말해서: 이전 작업의 결과는 후속 작업에 표시됩니다.
  • Happens-Before는 컴파일러의 최적화 동작을 제한합니다. 컴파일러는 최적화가 허용되지만 최적화 후에는 컴파일러가 Happens-Before 규칙을 준수해야 합니다.

2.5 발생 - 규칙 전

  • 프로그램 순서 규칙

스레드에서는 프로그램 순서에 따라 이전 작업이 발생합니다. - 후속 작업 전.

class Example {
  public void test() {
    int x = 42;   ①
    int y = 20;   ②
  }
 
}

① 일어난다 - ② 전.

  • 휘발성 변수 규칙

휘발성 변수에 대한 쓰기 작업은 이 휘발성 변수에 대한 후속 읽기 작업 전에 발생합니다.

  • 전이성 규칙

A가 B 전에 발생하고 B가 C 전에 발생하면 A가 C 전에 발생합니다.

class Example {
  int x = 0;
  volatile int y = 0;
  public void writer() {
    x = 42;      ①
    y = 1;       ②
  }
  public void reader() {
    if (y == 1) {  ③
      // 这里x会是多少呢?
    }
  }
}
  • ① 발생 - ② 전, 규칙 1-순차 규칙을 충족합니다.
  • ② Happens - Before ③, 규칙 2 충족 - 휘발성 변수 규칙.
  • ① 발생 - ③ 이전, 규칙 3 충족 - 전이 규칙. y == 1이면 x = 42입니다.
  • 모니터 잠금 규칙

잠금 해제가 발생하기 전에 발생하며 이후에 이 잠금이 잠깁니다.

管程是一种通用的同步原语,在 Java 中指的就是 synchronized,synchronized 是 Java 里对管程的实现。

synchronized (this) { //此处自动加锁
  // x是共享变量,初始值=10
  if (this.x < 12) {
    this.x = 12; 
  }  
} //此处自动解锁

假设 x 的初始值是 10,线程 A 执行完代码块后 x 的值会变成 12(执行完自动释放锁);

线程 B 进入代码块时,能够看到线程 A 对 x 的写操作,也就是线程 B 能够看到 x==12。

  • 线程 start() 规则

它是指主线程 A 启动子线程 B 后,子线程 B 能够看到主线程在启动子线程 B 前的操作。

  • 线程 join() 规则

它是指主线程 A 等待子线程 B 完成(主线程 A 通过调用子线程 B 的 join() 方法实现),当子线程 B 完成后(主线程 A 中 join() 方法返回),主线程能够看到子线程的操作。当然所谓的“看到”,指的是对共享变量的操作。

3 有序性

3.1 有序性的定义

有序性,即程序的执行顺序按照代码的先后顺序来执行。

3.2 有序性问题原因

编译器为了优化性能,有时候会改变程序中语句的先后顺序。

例如:“a=6;b=7;”编译器优化后可能变成“b=7;a=6;”,在这个例子中,编译器调整了语句的顺序,但是不影响程序的最终结果。

以双重检查代码为例:

public class Singleton {
  static Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {    ①
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();  ②
        }
    }
    return instance;
  }
}

上面的代码有问题,问题在 ② 操作上:经过优化后的执行路径是这样的:

  1. 分配一块内存 M;
  2. 将 M 的地址赋值给 instance 变量;
  3. 最后在内存 M 上初始化 Singleton 对象。

优化后会导致什么问题呢?我们假设线程 A 先执行 getInstance() 方法,当执行完 ① 时恰好发生了线程切换,切换到了线程 B 上;如果此时线程 B 也执行 getInstance() 方法,那么线程 B 在执行第一个判断时会发现 instance != null ,所以直接返回 instance,而此时的 instance 是没有初始化过的,如果我们这个时候访问 instance 的成员变量就可能触发空指针异常。

如何解决双重检查问题?变量用 volatile 来修饰,禁止指令重排序

public class Singleton {
  static volatile Singleton instance;
  static Singleton getInstance(){
    if (instance == null) {    ①
      synchronized(Singleton.class) {
        if (instance == null)
          instance = new Singleton();  ②
        }
    }
    return instance;
  }
}

推荐学习:《java视频教程

위 내용은 Java 동시 프로그래밍의 세 가지 요소에 대해 자세히 알아보세요.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 csdn.net에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제