JAVAマルチスレッドの3番目の部分(1)明示的なロックと同期

コンカレントノートポータル:
1.0コンカレントプログラミング-マインドマップ
2.0コンカレントプログラミング-スレッドの安全性の基礎
3.0コンカレントプログラミング-基本的な構築モジュール
4.0コンカレントプログラミング-タスクの実行-将来
5.0コンカレントプログラミング-マルチスレッドのパフォーマンスとスケーラビリティ
6.0コンカレントプログラミング-明示的なロックと同期された
7.0同時プログラミング
-AbstractQueuedSynchronizer8.0同時プログラミング-アトミック変数と非ブロッキング同期メカニズム

明示的なロック

Java 5より前は、共有オブジェクトへのアクセスを調整するときに使用できるメカニズムはsynchronizedsumだけvolatileです。Java5が追加されましたReentrantLockReentrantLockこれは、組み込みのロックを置き換える方法ではありませんが、組み込みのロックメカニズムが適用できない場合のオプションの高度な機能として使用できます。

ロック与ReentrantLock

Lockは、無条件、ポーリング可能、時間指定、および割り込み可能な取得操作を提供します。すべてのロックおよびロック解除方法は明示的です。

Lockの実装では、内部ロックと同じメモリ可視性セマンティクスを提供する必要がありますが、ロックセマンティクス、スケジューリングアルゴリズム、順序保証、およびパフォーマンス特性の点で異なる場合があります。

package java.util.concurrent.locks;
/**
 * @see ReentrantLock
 * @see Condition
 * @see ReadWriteLock
 *
 * @since 1.5
 * @author Doug Lea
 */
public interface Lock {
    
    

    void lock();

    void lockInterruptibly() throws InterruptedException;

    boolean tryLock();

    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

    void unlock();

    Condition newCondition();
}

ReentrantLockLockインターフェイスを実装し、synchronized可視メモリとミューテックスの同じ行を提供します。またsynchronized、同じように、ロックセマンティクスReentrantLockも提供します可重入(可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。)

なぜメモリロックに非常に似た新しいロックメカニズムを作成するのですか?

ほとんどの場合、組み込みのロックは適切に機能しますが、機能にはいくつかの制限があります。たとえば、ロックの取得を待機しているスレッドを中断したり、ロックを要求している間は無期限に待機したりすることはできません。
組み込みロックは、ロックを取得したコードブロックで解放する必要があります。これにより、コーディング作業が簡素化され、例外処理操作との良好な相互作用が実現しますが、非ブロック構造のロックルールを実装することはできません。これらが使用synchronizedの理由ですが、場合によっては、より柔軟なロックメカニズムにより、通常、より優れた活性またはパフォーマンスが提供されます。

Lockを明示的に呼び出すには、最終的にロックを解除する必要があります。最終的にロックを解除することは難しくありませんが、忘れてしまう可能性があります。

ポーリングロックとタイムロック

時限およびポーリング可能なロック取得モードは、tryLockメソッドによって実装さます。無条件のロック取得モードと比較して、より完全なエラー回復メカニズムがあります。

割り込み可能なロック取得操作

lockInterruptibly このメソッドは、ロックを取得している間、割り込みへの応答を維持できます。

コードはログの下にあります!

lock()メソッドは次のように出力します。スレッド1はロックを取得できないと、ロックの解放を待機し、割り込みに応答しません。スレッド0がロックを解放すると、スレッド1は割り込みへの応答を再開します。

Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock
Thread-1:already get lock
Thread-1:Interrupt
Thread-1: release unlock

lockInterruptibly()メソッドは次のように出力します。スレッド1は、ロックを取得できなかった後、時間内に割り込みに応答できます。

Thread-0:start get lock
Thread-0:already get lock
Thread-1:start get lock
Thread-1:Interrupt
Thread-1: unlock failed
Thread-1: failed desc:null
Thread-0:working num 0
Thread-0:working num 1
Thread-0:working num 2
Thread-0:working num 3
Thread-0:working num 4
Thread-0:working num 5
Thread-0: release unlock

サンプルコードlockInterruptibly実行するlock、違いが明確になります。


    public static void main(String[] args) throws InterruptedException {
    
    
        LockTest lockTest = new LockTest();
        Thread t0 = new Thread(new Runnable(){
    
    
            @Override
            public void run() {
    
    
                lockTest.doWork();
            }
        });
        Thread t1 = new Thread(new Runnable(){
    
    
            @Override
            public void run() {
    
    
                lockTest.doWork();
            }
        });
        // 启动线程t1
        t0.start();
        Thread.sleep(10);
        // 启动线程t2
        t1.start();
        Thread.sleep(100);
        // 线程t1没有得到锁,中断t1的等待
        t1.interrupt();
    }

class LockTest {
    
    
    private Lock lock = new ReentrantLock();
    public void doWork() {
    
    
        String name = Thread.currentThread().getName();
        try {
    
    
            System.out.println(name + ":start get lock");
            //lock.lock();
            lock.lockInterruptibly();
            System.out.println(name + ":already get lock");
            for (int i = 0; i < 6; i++) {
    
    
                Thread.sleep(1000);
                System.out.println(name + ":working num "+ i);
            }
        } catch (InterruptedException e) {
    
    
            System.out.println(name + ":Interrupt");
        }finally{
    
    
            try {
    
    
                lock.unlock();
                System.out.println(name + ": release unlock");
            } catch (Exception e) {
    
    
                System.out.println(name + ": unlock failed");
                System.out.println(name + ": failed desc:" + e.getMessage());
            }

        }
    }
}

パフォーマンスに関する考慮事項

Java 5を追加するとReentrantLock、組み込みのロックよりも優れた競争力のあるパフォーマンスを提供できます。Java 6は、改良されたアルゴリズムを使用して組み込みロックを管理するため、組み込みロックのReentrantLockスループットはほぼ同じであり、2つのスケーラビリティは基本的に同じです。

公平性

ReentrantLockコンストラクターには2つの公平性の選択肢があります。不公平なロック(デフォルト)または公平なロックを作成します。

  • 公正なロック:スレッドは、要求を行った順序でロックを取得します。
  • 不公平なロック:スレッドが要求を行っている間にロックの状態が使用可能になると、スレッドは待機中のすべてのスレッドをスキップしてロックを取得します。

フェアロックでは、別のスレッドがロックを保持している場合、または別のスレッドがキュー内のロックを待機している場合、新しく要求されたスレッドがキューに入れられます。不公平なロックでは、ロックがスレッドによって保持されている場合にのみ、新しく要求されたスレッドがキューに入れられます。

すべてのロックを公平にしたくないのはなぜですか?
ロック操作を実行する場合、スレッドの一時停止と再開にフェアロックを使用すると、パフォーマンスが大幅に低下する可能性があります。また、実際の状況では、統計的な公平性の保証(ブロックされたスレッドが最終的にロックを取得できることを保証するため)で通常は十分であり、オーバーヘッドははるかに小さくなります。ビジネスの正確性を確保するためにフェアキューイングアルゴリズムに依存しているものもありますが、これらのアルゴリズムは一般的ではありません。ほとんどの場合、不公平なロックのパフォーマンスは、公平なロックのパフォーマンスよりも高くなります。

synchronizedReentrantLockの間で選択

ReentrantLockロックとメモリで提供されるセマンティクスは、組み込みロックと同じです。さらに、タイミングロック待機、割り込み可能ロック待機、公平性、および非ブロック構造ロックなど、他のいくつかの機能も提供します。

明示的なロックと比較して、組み込みのロックには依然として大きな利点があります。組み込みのロックは開発者にとってなじみがあり、シンプルでコンパクトです。ReentrantLock危険性は同期メカニズムよりも高く、finallyブロック内で呼び出すのを忘れ場合unlock()は、実際に時限爆弾が仕掛けられています。

内蔵ロックがニーズを満たせない場合は、ReentrantLock高度なツールとして使用できます。いくつかの高度な機能が必要な場合に使用する必要がありますReentrantLock。これらの機能には、時限、ポーリング可能、および割り込み可能な取得操作、フェアキュー、および非ブロック構造ロックが含まれます。それ以外の場合は、最初に使用してくださいsynchronized

読み取り/書き込みロック

ReentrantLock標準のミューテックスロックが実装されていReentrantLockます。毎回最大で1つのスレッドが保持されますただし、データの整合性を維持するために、相互除外は通常、同時実行を制限する非常に強力なロックルールです。相互排除は保守的なロック戦略です。競合は回避できますが写/写写/读競合も回避できます读/读したがって、读/读状況のロック要件が緩和されると、プログラムのパフォーマンスが向上します。この場合、次の读-写锁ようになります。リソースは、複数の読み取り操作でアクセスすることも、1回の書き込み操作でアクセスすることもできますが、2つを同時に実行することはできません。

public interface ReadWriteLock {
    
    
    Lock readLock();
    Lock writeLock();
}

读-写锁ロック戦略、複数の読み出し動作を同時に許可されているが、一方のみの書き込み動作は、一度に許可されます。

ReentrantReadWriteLock両方のタイプのロックに重要なロックセマンティクスを提供します。そしてReentrantLock同様に、
ReentrantReadWriteLock建設はまた、公正つの非ロック(デフォルト)または公正なロックであることを選択することができるとき。

  • フェアロックでは、待機時間が最も長いスレッドが最初にロックを取得します。このロックがリーダースレッドによって保持され、別のスレッドが書き込みロックを要求した場合、ライタースレッドが不足してライターロックを解放するまで、他のリーダースレッドは読み取りロックを取得できません。
  • 不公平なロックでは、スレッドがアクセス許可を取得する順序は不確実です。ライタースレッドをリーダースレッドにダウングレードすることは可能ですが、リーダースレッドからライタースレッドにアップグレードすることはできません(これによりデッドロックが発生します)。

読み取り/書き込みロックコードの例:

public class ReadWriteMap<K,V>{
    
    
    private final Map<K,V> map;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock read = lock.readLock();
    private final Lock write = lock.writeLock();
    public ReadWriteMap(Map<K,V> map){
    
    
        this.map = map;
    }
    public V put(K key,V value){
    
    
        write.lock();
        try {
    
    
            return map.put(key, value);
        }finally{
    
    
            write.unlock();
        }
    }
    public V get(Object key){
    
    
        read.lock();
        try {
    
    
            return map.get(key);
        }finally{
    
    
            read.unlock();
        }
    }
}

組み込みのロックと比較して、明示的なロックはいくつかの拡張機能を提供し、より高い柔軟性を備えています。柔軟性があり、キューラインをより適切に制御できます。ただし、ReentrantLock完全に交換することはできませんsynchronizedsynchronized需要を満たすことができない場合にのみ使用してください。

读-写锁複数のリーダースレッドが保護されたオブジェクトに同時にアクセスできるようにします。読み取り操作が支配的なデータ構造にアクセスすると、プログラムのスケーラビリティを向上させることができます。

おすすめ

転載: blog.csdn.net/lijie2664989/article/details/105739247