ホームページ  >  記事  >  Java  >  Javaスレッド(2) - スレッド同期の詳細説明

Javaスレッド(2) - スレッド同期の詳細説明

黄舟
黄舟オリジナル
2017-03-01 11:31:191213ブラウズ


コードの実行速度を高速化するために、マルチスレッド方式を採用しています。並列実行によりコードの効率は向上しますが、プログラム内で多数のスレッドが同時に実行されると、破損が発生する可能性が高くなります。これらのスレッドを管理するには、同期メカニズムが必要です。

(1) 競技条件

オペレーティングシステムに非常に感銘を受けた絵があるのを覚えています。上に描かれているのはプロセスのブロックです。これらのスレッドはすべて、統一された方法でプロセスのリソースを指します。 Java でも同じことが当てはまります。リソースは、各スレッドが独立したリソースを持つのではなく、スレッド間で共有されます。この種の共有状況では、複数のスレッドが同時にリソースにアクセスしている可能性が高く、この現象は 競合状態 と呼ばれます。

銀行システムでは、各スレッドが口座を管理し、これらのスレッドが送金操作を実行することがあります。
スレッドが操作を実行すると、最初に口座残高がレジスタに格納され、2 番目のステップで、レジスタ内の数値が転送される金額だけ減算され、結果がレジスタに書き込まれます。バランス。
問題は、このスレッドがステップ 1 と 2 を完了すると、別のスレッドが起動して最初のスレッドの口座残高値を変更しますが、最初のスレッドはこの時点ではそれを認識していないことです。最初のスレッドは 2 番目のスレッドの実行が完了するのを待った後、結果を残高に書き戻す 3 番目のステップに進みます。このとき、2番目のスレッドの操作をフラッシュするため、システム全体の合計金額は確実にエラーとして送信されます。
Java の競合状態が発生すると、これは望ましくない状況になります。

(2) ReentrantLock クラス

上記の例は、操作が アトミック操作 でない場合、たとえ確率が実際に非常に小さい場合でも、割り込みが確実に発生することを示しています。除外する。オペレーティング システムのようにコードをアトミ​​ックな操作に変えることはできません。できることは、コードをロックしてセキュリティを確保することです。並行プログラムでは、データにアクセスしたい場合、まずコードにロックを設定します。ロックを使用している間、コードに含まれるリソースは「ロック」されているように見えます。この鍵を開けてください。

JavaではsynchronizedキーワードとReentrantLockクラスの両方にこのロック機能があります。ここではまず ReentrantLcok の機能について説明します。

1. ReentrantLock コンストラクター

このクラスでは 2 つのコンストラクターが提供され、1 つは何も言うことのないデフォルトのコンストラクターであり、もう 1 つは公正な戦略を持つコンストラクターです。この公平な戦略は、第一に、通常のロックよりもはるかに遅く、第二に、場合によっては実際には公平ではありません。そして、特別な理由もなく本当に公正な戦略が必要な場合は、その戦略を研究しないようにしてください。

2. 取得と解放

ReentrantLock myLock = new ReentrantLock();
//创建对象
myLock.lock();
//获取锁try{...}
finally{
myLock.unlock();
//释放锁
}

最後に必ずロックを解除してください。 !チェックされていないエラーはスレッドの終了につながる可能性があると前に述べました。原因不明の終了により、最終的にリリースが行われない場合、プログラムの下向きの実行が停止されます。ロックは決して解放されません。この原理は、日常のフレームワークで Baohou.close() を使用する場合と同じです。 close について言えば、ロックを使用する場合、ロックは close で閉じられないため、「リソースを含む try ステートメント」を使用できないことに注意してください。リソースを伴う try ステートメントが何なのかわからない場合は、私がそれを言わなかったことにしてください。

3. ロックは再入可能です

再帰的プログラムまたは循環プログラムでロックを使用したい場合は、自由に使用してください。 ReentrantLock は再入可能であり、各ロック呼び出しを解放するために Unlock を使用する必要があるたびに、呼び出しの数を記録します。

(3) 条件付きオブジェクト

通常、スレッドは、ロックされてクリティカルセクションに入った後に問題を発見します。このとき、必要なリソースが他のオブジェクトで使用されているか、スレッドの実行条件を満たしていません。ロックは取得したが有用な作業を実行できないスレッドを管理するには、condition オブジェクト を使用する必要があります。

if(a>b){    a.set(b-1);}

1. 「あなたは自分自身を閉じ込めています」

上記は非常に単純な条件判断ですが、同時実行プログラムではこのように書くことはできません。問題は、このスレッドが判断を行った直後に別のスレッドが起動され、操作後に別のスレッドが a 未満を出した場合 (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 や synchronized キーワードは使用しないことをお勧めします。多くの場合、java.util.concurrent パッケージを使用できます

  2. synchronized がコードのニーズを満たしている場合は、最初にそれを使用してください

  3. 特に ReentrantLcok が必要になるまでは、高速化するためにそれを使用してください

コードの実行速度を向上させるために、マルチスレッドのアプローチを採用しています。並列実行によりコードの効率は向上しますが、プログラム内で多数のスレッドが同時に実行されると、破損が発生する可能性が高くなります。これらのスレッドを管理するには、同期メカニズムが必要です。

上記は Java スレッド (2) - スレッド同期の詳細な説明の内容です。さらに関連する内容については、PHP 中国語 Web サイト (www.php.cn) に注目してください。


声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。