>  기사  >  Java  >  Java 스레드(2) - 스레드 동기화에 대한 자세한 설명

Java 스레드(2) - 스레드 동기화에 대한 자세한 설명

黄舟
黄舟원래의
2017-03-01 11:31:191212검색


코드 실행 속도를 높이기 위해 멀티스레딩 방식을 채택합니다. 병렬 실행은 코드를 더 효율적으로 만들어 주지만, 그에 따른 문제는 프로그램에서 동시에 실행되는 스레드가 많다는 것입니다. 동시에 개체를 수정하면 손상될 가능성이 높습니다. 이러한 스레드를 관리하려면 동기화 메커니즘이 필요합니다.

(1) 경쟁조건

운영체제에 인상깊었던 그림이 있었던 것으로 기억합니다. 위에 그려진 것은 프로세스 블록입니다. 여러 스레드가 이러한 프로세스로 나누어져 있습니다. 이 스레드는 모두 통합된 방식으로 프로세스의 리소스를 가리킵니다. Java에서도 마찬가지입니다. 리소스는 각 스레드가 독립적인 리소스를 갖는 대신 스레드 간에 공유됩니다. 이러한 공유 상황에서는 여러 스레드가 동시에 리소스에 액세스할 가능성이 매우 높습니다. 이러한 현상을 경합 상태라고 합니다.

뱅킹 시스템에서 각 스레드는 계좌를 관리하며 이러한 스레드는 이체 작업을 수행할 수 있습니다.
스레드는 작업을 수행할 때 먼저 계좌 잔고를 레지스터에 저장합니다. 두 번째 단계에서는 이체할 금액만큼 레지스터의 숫자를 줄입니다. 그 결과를 다시 레지스터에 씁니다. 균형.
문제는 이 스레드가 1단계와 2단계를 완료하면 다른 스레드가 깨어나 첫 번째 스레드의 계좌 잔고 값을 수정하지만, 이때 첫 번째 스레드는 이에 대해 알지 못한다는 점이다. 첫 번째 스레드는 두 번째 스레드의 실행이 완료될 때까지 기다린 후 세 번째 단계, 즉 결과를 다시 잔액에 기록하는 단계를 계속합니다. 이때 두 번째 스레드의 연산을 플러시하게 되므로 전체 시스템의 총 금액은 반드시 오류로 전송됩니다.
이는 Java 경쟁 조건이 발생할 때 바람직하지 않은 상황입니다.

(2) ReentrantLock 클래스

위의 예는 작업이 원자적 작업이 아닌 경우 중단되는 것은 그런 일은 반드시 일어날 것입니다. 때로는 확률이 매우 작더라도 배제할 수는 없습니다. 우리는 코드를 운영 체제처럼 원자적 작업으로 전환할 수 없습니다. 우리가 할 수 있는 일은 보안을 보장하기 위해 코드를 잠그는 것뿐입니다. 동시 프로그램에서 데이터에 액세스하려면 먼저 코드에 잠금을 설정해야 합니다. 잠금을 사용하는 동안 코드에 포함된 리소스는 우리가 액세스할 때까지 다른 스레드에서 액세스할 수 없는 것처럼 보입니다. 이 자물쇠를 열어라.

Java에서는 동기화된 키워드와 ReentrantLock 클래스 모두 이 잠금 기능을 가지고 있습니다. 여기서는 먼저 ReentrantLcok의 기능에 대해 논의해 보겠습니다.

1. ReentrantLock 생성자

이 클래스에는 두 개의 생성자가 제공되는데 하나는 기본 생성자이며 말할 것도 없고 다른 하나는 공정성 생성자입니다. 전략을 위해. 이 공정한 전략은 우선 일반 잠금보다 훨씬 느리고, 두 번째로 어떤 경우에는 실제로 공정하지 않습니다. 그리고 특별한 이유 없이 공정한 전략이 정말로 필요하다면 이 전략을 연구하지 않으려고 노력하세요.

2. 획득 및 해제

안녕

드디어 잠금 해제를 꼭 기억하세요! ! 이전에 확인되지 않은 오류로 인해 스레드가 종료될 수 있다고 말씀드렸습니다. 설명할 수 없는 종료로 인해 프로그램이 최종적으로 실행되지 않으면 잠금이 해제되지 않습니다. 이 원칙은 일상적인 프레임워크에서 .close() 패키지를 사용할 때와 동일합니다. 닫기에 대해 말하면 잠금을 사용할 때 잠금이 닫히지 않기 때문에 "리소스가 포함된 try 문"을 사용할 수 없다는 점을 언급할 가치가 있습니다. 리소스가 포함된 try 문이 무엇인지 모른다면 제가 말하지 않은 것처럼 하세요.

3. 잠금은 재진입 가능

재귀 또는 순환 프로그램에서 잠금을 사용하려면 자유롭게 사용하세요. ReentrantLock 잠금은 lock()이 호출될 때마다 호출되는 횟수를 기록하기 위해 개수를 유지합니다. 잠금 해제를 통해 잠금을 해제해야 합니다.

(3) 조건부 개체

일반적으로 스레드는 임계 영역을 잠그고 들어간 후 문제를 발견합니다. 필요한 리소스는 다른 개체에 사용되거나 수행됩니다. 실행 조건을 충족하지 못하는 경우, 잠금을 획득했지만 유용한 작업을 수행할 수 없는 스레드를 관리하려면 조건 개체를 사용해야 합니다.

rree

1. "너 자신을 가두었구나"

위는 아주 간단한 조건부 판단이지만, 동시성 프로그램에서는 이렇게 쓸 수 없습니다. 문제는 이 스레드가 판단을 내린 직후에 다른 스레드가 깨어나고 다른 스레드가 작업 후 b보다 작은 값을 만드는 경우(if 문의 조건이 더 이상 올바르지 않음)입니다.

那么这个时候我们可能想到,我们把整个if语句直接放在锁里面,确保自己的代码不会被打断。但是这样又存在一个问题,如果if判断是false,那么if中的语句不会被执行。但是如果我们需要去执行if中的语句,甚至我们要一直等待if判断变的正确之后去执行if中的语句,这时,我们突然发现,if语句再也不会变得正确了,因为我们的锁把这个线程锁死,其他的线程没办法访问临界区并修改a和b的值让if判断变得正确,这真的是非常尴尬,我们自己的锁把我们自己困住了,我们出不去,别人进不来。

2.Condition类

为了解决这种情况,我们用ReentrantLock类中的newCondition方法来获取一个条件对象。

Condition cd = myLock.newCondition();

获取了Condition对象之后,我们就应该来研究这个对象有什么方法和作用了。先不急于看API,我们回到主题发现现在亟待解决的就是if条件判断的问题,我们如何才能:在已经上锁的情况下,发现if判断错误时,给其他线程机会并自己一直等着if判断变回正确

Condition类就是为了解决这个难题而生的,有了Condition类之后,我们在if语句下面直接跟上await方法,这个方法表示这个线程被阻塞,并放弃了锁,等其他的线程来操作。

注意在这里我们用的名词是阻塞,我们之前也说过阻塞和等待有很大不同:等待获得锁时,一旦锁有了空闲,他可以自动的去获得锁,而阻塞获得锁时,即使有空闲的锁,也要等待线程调度器允许他去持有锁的时候才能获得锁。

其他的线程在顺利执行if语句内容之后,要去调用signalAll方法,这个方法将会重新去激活所有的因为这个条件被阻塞的线程,让这些线程重新获得机会,这些线程被允许从被阻塞的地方继续进行。此时,线程应该再次测试该条件,如果还是不能满足条件,需要再次重复上述操作。

ReentrantLock myLock = new ReentrantLock();
//创建锁对象myLock.lock();
//给下面的临界区上锁
Condition cd = myLock.newCondition();
//创建一个Condition对象,这个cd对象表示条件对象while(!(a>b))
    cd.await();
    //上面的while循环和await方法调用是标准写法
    //如果不能满足if的条件,那么他将进入阻塞状态,放弃锁,等待别人去激活它a.set(b-1);
    //一直等到从while循环出来,满足了判断的条件,我们执行自己的功能cd.signalAll();
    //最后一定不能忘记调用signalAll方法去激活其他的被阻塞的线程
    //如果所有的线程都在等待其他线程signalAll,则进入死锁

非常不妙的,如果所有的线程都在等待其他线程signalAll,则进入死锁的状态。死锁状态是指所有的线程需要的资源都被其他的线程形成环状结构而导致谁都不能执行的情况。最后调用signalAll方法激活其他因为cd而阻塞的“兄弟”是必须的,方便你我他,减少死锁的发生。

3.Condition对象和锁总结

总结来说,Condition对象和锁有这样几个特点。

  1. 锁可以用来保护代码片段,任何时刻只能有一个线程进入被保护的区域

  2. 锁可以管理试图进入临界区的线程

  3. 锁可以拥有一个或多个条件对象

  4. 每个条件对象管理那些因为前面所描述的原因而不能被执行但已经进入被保护代码段的线程

(四)synchronized关键字

我们上面介绍的ReentrantLock和Condition对象是一种用来保护代码片段的方法,在java中还有另外一种机制:通过使用关键字synchronized来修饰方法,从而给方法添加一个内部锁。从版本开始,java的每一个对象都有一个内部锁,每个内部锁会保护那些被synchronized修饰的方法。也就是说,如果想调用这个方法,首先要获得内部的对象锁。

1.synchronized与ReentrantLock比较

我们先拿出上面的代码:

public void function(){
    ReentrantLock myLock = new ReentrantLock();
    myLock.lock();

    Condition cd = myLock.newCondition();    while(!(a>b))
        cd.await();

    a.set(b-1);

    cd.signalAll();
}

如果我们用synchronized来实现这段代码,将会变成下面的样子:

public synchronized void function(){
    while(!(a>b))
        wait();

    a.set(b-1);

    notifyAll();
}

需要我们注意的是,在使用synchronized关键词时,无需再去用ReentrantLock和Condition对象,我们用wait方法替换了await方法,notifyAll方法替换了signalAll方法。这样写确实比之前的简单了很多。

2.静态方法的synchronized

将静态方法声明为synchronized也是合法的。如果调用这种方法,将会获取相关的类对象的内部锁。比如我们调用Test类中的静态方法,这时,Test.class对象的锁将被锁住。

3.内部锁和条件的局限性

内部锁虽然简便,但是他存在着很多限制:

  1. 不能中断一个正在试图获得锁的线程

  2. 试图获得锁时不能设定超时

  3. 因为不能通过Condition来实例化条件。每个锁仅有单一的条件,可能是不够的

在代码中应该使用这两种锁中的哪一种呢?Lock和Condition对象还是同步方法?在core java一书中有一些建议:

  1. ReentrantLock이나 동기화 키워드를 모두 사용하지 않는 것이 가장 좋습니다. 대부분의 경우 java.util.concurrent 패키지를 사용할 수 있습니다

  2. Synchronized가 코드 요구사항을 충족하는 경우 If까지 먼저

  3. 사용하십시오. 특히 ReentrantLcok이 필요하신 분은 꼭 사용하세요

코드 실행 속도를 높이기 위해 멀티스레딩 방식을 채택하고 있습니다. 병렬 실행은 코드를 더 효율적으로 만들어 주지만, 그에 따른 문제는 프로그램에서 동시에 실행되는 스레드가 많다는 것입니다. 동시에 개체를 수정하면 손상될 가능성이 높습니다. 이러한 스레드를 관리하려면 동기화 메커니즘이 필요합니다.

위 내용은 Java Thread (2)의 내용입니다. - Thread 동기화에 대한 자세한 설명은 PHP 중국어 홈페이지(www.php.cn)를 참고해주세요!


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