[同時実行性]インタビュアー:SynchronizedはJavaで提供されていますが、なぜLockを提供する必要があるのですか?

前に書く

同期キーワードはJavaで提供され、1つのスレッドのみが同期コードブロックにアクセスできるようにします。synchronizedキーワードが提供されたのに、なぜLockインターフェースがまだJava SDKパッケージで提供されているのですか?これは車輪を再発明し、それを不要にしますか?今日は、この問題について一緒に議論します。

車輪を再発明しますか?

同期されたコードブロックに1つのスレッドのみがアクセスできるようにするために、synchronizedキーワードがJVMに提供されているので、なぜLockインターフェースを提供するのですか?これは車輪の再発明ですか?なぜJavaデザイナーはこれをしたのですか?質問で軽視しましょう。

ロックインターフェイスを提供する理由

多くの友人は、Java 1.5バージョンでは同期パフォーマンスがロックほど良くないと聞いたことがあるかもしれませんが、Java 1.6バージョン以降、同期により多くの最適化が行われ、パフォーマンスが大幅に向上しました。同期キーワードのパフォーマンスが改善されたので、なぜロックを使用するのですか?

ここに画像の説明を挿入

より深いレベルで考える場合、考えるのは難しくありません。同期ロックを使用して積極的にロックを解放することはできず、デッドロックの問題が発生します。

デッドロックの問題

デッドロックが発生する場合、以下の4つの条件が必要であり、4つのうちの1つが必須です。

ここに画像の説明を挿入

  • 相互に排他的な条件

特定のリソースは、一定の期間に1つのスレッドのみによって占有されます。この時点で他のスレッドがリソースを要求した場合、要求元のスレッドは待機することしかできません。

  • 譲れない状態

スレッドによって取得されたリソースは、使用される前に他のスレッドによって強制的に奪うことはできません。つまり、リソースを取得したスレッドによってのみ解放できます(アクティブな解放のみ)。

  • リクエストとホールドの条件

スレッドは少なくとも1つのリソースを予約しましたが、新しいリソース要求を行い、そのリソースはすでに他のスレッドによって占有されています。要求しているスレッドはこの時点でブロックされますが、取得したリソースを保持し続けます。

  • ループ待機状態

デッドロックが発生すると、プロセス待機キュー{P1、P2、...、Pn}が存在する必要があります。ここで、P1はP2が占有するリソースを待機し、P2はP3が占有するリソースを待機します...、PnはP1が占有するリソースを待機し、プロセス待機ループを形成します道路では、ループ内の各プロセスによって占有されているリソースは、別のプロセスによって同時に適用されます。つまり、次のプロセスによって占有されているリソースは、前のプロセスによって影響を受けます。

同期の制限

同期キーワードを使用してプログラムでデッドロックが発生した場合、同期の鍵は、「不可侵」のデッドロック状態を破壊できないことです。これは、同期がリソースに適用されるときに、アプリケーションに障害が発生すると、スレッドが直接ブロック状態になり、スレッドがブロック状態になり、何もできず、すでにスレッドが占有していたリソースを解放できないためです。

ただし、ほとんどのシナリオでは、「不可侵性」の状態が解消されることを期待しています。つまり、「不可侵」の状態では、あるリソースを占有しているスレッドがさらに他のリソースに適用されると、適用に失敗した場合、占有しているリソースを積極的に解放できるため、不可侵の状態が破壊されます。

同期の問題を解決するためにロックを自分で再設計する場合、どのように設計する必要がありますか?

問題を解く

同期の制限を理解した後で、同期ロックを自分で実装する場合、どのように設計すればよいでしょうか?つまり、ロックを設計するときに、同期の制限をどのように解決すればよいのでしょうか。ここで、この問題は3つの側面から考えられると思います。
ここに画像の説明を挿入

(1)割り込みに応答できる。同期の問題は、ロックAを保持した後、ロックBを取得する試みが失敗した場合、スレッドはブロッキング状態になり、デッドロックが発生すると、ブロックされたスレッドをウェイクアップすることはできません。しかし、ブロックされたスレッドが割り込み信号に応答できる場合、つまり、ブロックされたスレッドに割り込み信号を送信する場合、それをウェイクアップでき、一度保持されたロックAを解放する機会があります。これは不可侵の状態を破壊します。

(2)タイムアウトをサポートします。スレッドが一定期間内にロックを取得せず、ブロッキング状態に入るのではなくエラーを返す場合、このスレッドは一度保持したロックを解放する機会もあります。これは不可分の状態を破壊することもできます。

(3)ブロックせずにロックを取得します。ロックを取得する試みが失敗し、ブロッキング状態にならないが、直接戻る場合、このスレッドは、保持されたロックを解放する機会もあります。これは不可分の状態を破壊することもできます。

Lockインターフェースに反映されるこれらは、以下に示すように、Lockインターフェースによって提供される3つのメソッドです。

// 支持中断的API
void lockInterruptibly() throws InterruptedException;
// 支持超时的API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 支持非阻塞获取锁的API
boolean tryLock();
  • lockInterruptibly()

中断をサポートします。

  • tryLock()メソッド

tryLock()メソッドには戻り値があります。これは、ロックの取得に使用されることを意味します。取得が成功した場合は、trueを返します。取得が失敗した場合(つまり、ロックが他のスレッドによって取得された場合)は、falseを返します。つまり、このメソッドは何でもかまいません。すぐに戻ります。ロックを取得できない場合、そこに永遠にいることはできません。

  • tryLock(long time、TimeUnit unit)メソッド

tryLock(long time、TimeUnit unit)メソッドはtryLock()メソッドに似ていますが、このメソッドは、ロックが利用できない場合に一定の時間待機し、制限時間内にロックが利用できない場合は返されるという点が異なります偽。最初または待機期間中にロックが取得された場合、trueを返します。

つまり、デッドロックの問題の場合、ロックによって不可分の状態が破壊される可能性があります。たとえば、次のプログラムコードはデッドロックの不可解な状態を破壊します。

public class TansferAccount{
    
    
    private Lock thisLock = new ReentrantLock();
    private Lock targetLock = new ReentrantLock();
    //账户的余额
    private Integer balance;
    //转账操作
    public void transfer(TansferAccount target, Integer transferMoney){
    
    
        boolean isThisLock = thisLock.tryLock();
        if(isThisLock){
    
    
            try{
    
    
                boolean isTargetLock = targetLock.tryLock();
                if(isTargetLock){
    
    
                    try{
    
    
                         if(this.balance >= transferMoney){
    
    
                            this.balance -= transferMoney;
                            target.balance += transferMoney;
                        }   
                    }finally{
    
    
                        targetLock.unlock
                    }
                }
            }finally{
    
    
                thisLock.unlock();
            }
        }
    }
}

例外として、ロックの下にReentrantLockがあり、ReentrantLockはフェアロックとアンフェアロックをサポートしています。

ReentrantLockを使用する場合、ReentrantLockには2つのコンストラクターがあります。1つはパラメーターなしのコンストラクターで、もう1つは公平なパラメーターを持つコンストラクターです。fairパラメータは、ロックの公平性戦略を表します。trueが渡された場合は、公平なロックを作成する必要があることを意味し、そうでない場合は、不当なロックを作成する必要があることを意味します。次のコードスニペットに示すように。

//无参构造函数: 默认非公平锁
public ReentrantLock() {
    
    
	sync = new NonfairSync();
} 
//根据公平策略参数创建锁
public ReentrantLock(boolean fair){
    
    
	sync = fair ? new FairSync() : new NonfairSync();
}

ロックの実装は、基本的にエントリ待機キューに対応しています。スレッドがロックを取得しない場合、スレッドは待機キューに入ります。スレッドがロックを解放すると、待機キューから待機スレッドをウェイクアップする必要があります。公平なロックの場合、ウェイクアップ戦略は、長時間待機しているユーザーを目覚めさせることです。これは公平です。不公平なロックの場合、この公平性の保証は提供されず、待機時間が短いスレッドが最初に起こされる場合があります。Lockはフェアロックをサポートしますが、synchronizedはフェアロックをサポートしません。

最後に、Lockを使用してロックする場合は、たとえば次のコードスニペットに示すように、finally {}コードブロックでロックを解放する必要があることに注意してください。

try{
    
    
    lock.lock();
}finally{
    
    
    lock.unlock();
}

注:同期およびロックのその他の詳細な説明については、自分で参照してください。

さて、今日はそれについて話しましょう!より多くの人が一緒に見て、一緒に学び、一緒に進歩することができるように、それを好きで、それを見て転送している誰かにそれを与えることを忘れないでください!

おすすめ

転載: blog.csdn.net/l1028386804/article/details/108613319