ホームページ Java &#&チュートリアル Javaマルチスレッド - ロックの詳細説明とサンプルコード

Javaマルチスレッド - ロックの詳細説明とサンプルコード

Jan 05, 2017 pm 03:30 PM

Java 5 以降、java.util.concurrent.locks パッケージにはいくつかのロック実装が含まれているため、独自のロックを実装する必要はありません。ただし、これらのロックの使用方法を理解する必要があります。

単純なロック

Java の synchronized ブロックから始めましょう:

public class Counter{
  private int count = 0;
 
  public int inc(){
    synchronized(this){
      return ++count;
    }
  }
}
ログイン後にコピー

inc() メソッドに synchronized(this) コード ブロックがあることがわかります。このコード ブロックは、同時に 1 つのスレッドだけが return ++count を実行できることを保証します。 synchronized 同期ブロック内のコードはより複雑になる可能性がありますが、スレッド同期の意味を表現するには ++count のような単純な操作で十分です。

次の Counter クラスは、同じ目的を達成するために同期の代わりに Lock を使用します:

public class Counter{
  private Lock lock = new Lock();
  private int count = 0;
 
  public int inc(){
    lock.lock();
    int newCount = ++count;
    lock.unlock();
    return newCount;
  }
}
ログイン後にコピー

lock() メソッドは Lock インスタンス オブジェクトをロックするため、オブジェクトの lock() メソッドを呼び出すすべてのスレッドは、ロックが完了するまでブロックされます。 Lock オブジェクトの lock() メソッドが呼び出されます。

Lock クラスの簡単な実装を次に示します。

public class Counter{
public class Lock{
  private boolean isLocked = false;
 
  public synchronized void lock()
    throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }
 
  public synchronized void unlock(){
    isLocked = false;
    notify();
  }
}
ログイン後にコピー

「スピン ロック」とも呼ばれる while(isLocked) ループに注目してください。 isLocked が true の場合、lock() を呼び出すスレッドは wait() 呼び出しの待機をブロックします。スレッドが notify() 呼び出しを受信せずに wait() から戻ること (誤ったウェイクアップとも呼ばれます) を防ぐために、スレッドは isLocked 条件を再チェックして、実行を継続しても安全かどうか、または実行を継続しても安全かどうかを判断します。再度待機する必要があり、スレッドが目覚めた後も安全に実行を継続できるとは考えられません。 isLocked が false の場合、現在のスレッドは while(isLocked) ループを終了し、isLocked を true に戻し、lock() メソッドを呼び出している他のスレッドが Lock インスタンスをロックできるようにします。

スレッドがクリティカルセクション(lock()とunlock()の間)のコードを完了すると、unlock()が呼び出されます。 lock() を実行すると、isLocked が false にリセットされ、lock() メソッドの wait() 関数の呼び出し後に待機状態になっていたスレッド (存在する場合) の 1 つが通知 (ウェイクアップ) されます。

ロックの再入可能

Java の同期ブロックは再入可能です。これは、Java スレッドがコード内の同期された同期ブロックに入り、その同期ブロックで使用される同期オブジェクトに対応するモニターのロックを取得した場合、このスレッドは同じモニター オブジェクトによって同期された別のブロックに入ることができることを意味します。 Javaコード。以下に例を示します。

public class Reentrant{
  public synchronized outer(){
    inner();
  }
 
  public synchronized inner(){
    //do something
  }
}
ログイン後にコピー

outer() と inner() の両方が synchronized として宣言されていることに注意してください。これは、Java の synchronized(this) ブロックに相当します。スレッドが external() を呼び出す場合、両方のメソッド (コードのブロック) が同じモニター オブジェクト (「this」) によって同期されるため、outer() 内で inner() を呼び出しても問題はありません。スレッドがすでにモニター オブジェクトのロックを所有している場合、スレッドはモニター オブジェクトによって同期されたすべてのコード ブロックにアクセスできます。これがリエントランシーです。スレッドは、すでに所有しているロックによって同期されているコードの任意のブロックに入ることができます。

前に示したロックの実装は再入可能ではありません。 Reentrantクラスを以下のように書き換えると、スレッドがouter()を呼び出すと、inner()メソッドのlock.lock()でブロックされてしまいます。

public class Reentrant2{
  Lock lock = new Lock();
 
  public outer(){
    lock.lock();
    inner();
    lock.unlock();
  }
 
  public synchronized inner(){
    lock.lock();
    //do something
    lock.unlock();
  }
}
ログイン後にコピー

outer() を呼び出すスレッドは、まず Lock インスタンスをロックし、次に inner() を呼び出し続けます。 inner() メソッドでは、スレッドは Lock インスタンスを再度ロックしようとしますが、Lock インスタンスは既に external() メソッドでロックされているため、アクションは失敗します (つまり、スレッドはブロックされます)。

2 つの lock() 呼び出しの間に lock() が呼び出されないと、lock() の実装を確認すると、その理由が明らかになることがわかります:

public class Lock{
  boolean isLocked = false;
 
  public synchronized void lock()
    throws InterruptedException{
    while(isLocked){
      wait();
    }
    isLocked = true;
  }
 
  ...
}
ログイン後にコピー

スレッドが許可されているかどうか。 lock() を終了する方法は、while ループ (スピン ロック) 内の条件によって決まります。現在の判定条件は、どのスレッドがロックしたかに関係なく、isLocked が false の場合にのみロック操作が許可されます。

この Lock クラスを再入可能にするには、それに小さな変更を加える必要があります:

public class Lock{
  boolean isLocked = false;
  Thread lockedBy = null;
  int lockedCount = 0;
 
  public synchronized void lock()
    throws InterruptedException{
    Thread callingThread =
      Thread.currentThread();
    while(isLocked && lockedBy != callingThread){
      wait();
    }
    isLocked = true;
    lockedCount++;
    lockedBy = callingThread;
 }
 
  public synchronized void unlock(){
    if(Thread.curentThread() ==
      this.lockedBy){
      lockedCount--;
 
      if(lockedCount == 0){
        isLocked = false;
        notify();
      }
    }
  }
 
  ...
}
ログイン後にコピー

while ループ (スピン ロック) が、Lock インスタンスをロックしたスレッドも考慮するようになったことに注意してください。現在のロック オブジェクトがロックされていない (isLocked = false)、または現在の呼び出しスレッドが既に Lock インスタンスをロックしている場合、while ループは実行されず、lock() を呼び出しているスレッドはメソッドを終了できます (翻訳注:現在のセマンティクスでは、「このメソッドの終了が許可される」ということは、wait() が呼び出されず、ブロッキングが発生しないことを意味します。

さらに、同じスレッドがロック オブジェクトを繰り返しロックした回数を記録する必要があります。それ以外の場合は、現在のロックが複数回ロックされている場合でも、1 回の unblock() 呼び出しでロック全体がロック解除されます。対応する lock() 呼び出しの数に unlock() 呼び出しが達する前にロックが解放されることは望ましくありません。

これで、この Lock クラスは再入可能になりました。

ロックの公平性

Java の同期ブロックは、ブロックに入ろうとするスレッドの順序を保証しません。したがって、複数のスレッドが同じ同期ブロックへのアクセスを求めて常に競合する場合、そのうちの 1 つまたは複数のスレッドがアクセスを取得できない危険性があります。つまり、アクセスは常に他のスレッドに割り当てられることになります。この状況はスレッドの飢餓と呼ばれます。この問題を回避するには、ロックが公平である必要があります。この記事で紹介されているロックは、同期されたブロックを使用して内部で実装されているため、公平性は保証されません。

在 finally 语句中调用 unlock()

如果用 Lock 来保护临界区,并且临界区有可能会抛出异常,那么在 finally 语句中调用 unlock()就显得非常重要了。这样可以保证这个锁对象可以被解锁以便其它线程能继续对其加锁。以下是一个示例:

lock.lock();
try{
  //do critical section code,
  //which may throw exception
} finally {
  lock.unlock();
}
ログイン後にコピー

这个简单的结构可以保证当临界区抛出异常时 Lock 对象可以被解锁。如果不是在 finally 语句中调用的 unlock(),当临界区抛出异常时,Lock 对象将永远停留在被锁住的状态,这会导致其它所有在该 Lock 对象上调用 lock()的线程一直阻塞。

以上就是关于 java 多线程锁的资料整理,后续继续补充相关资料,谢谢大家对本站的支持!


更多java 多线程-锁详解及示例代码相关文章请关注PHP中文网!


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

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

Video Face Swap

Video Face Swap

完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? 会社のセキュリティソフトウェアはアプリケーションの実行に失敗していますか?それをトラブルシューティングと解決する方法は? Apr 19, 2025 pm 04:51 PM

一部のアプリケーションが適切に機能しないようにする会社のセキュリティソフトウェアのトラブルシューティングとソリューション。多くの企業は、内部ネットワークセキュリティを確保するためにセキュリティソフトウェアを展開します。 ...

MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? MapsTructを使用したシステムドッキングのフィールドマッピングの問題を簡素化する方法は? Apr 19, 2025 pm 06:21 PM

システムドッキングでのフィールドマッピング処理は、システムドッキングを実行する際に難しい問題に遭遇することがよくあります。システムのインターフェイスフィールドを効果的にマッピングする方法A ...

エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? エンティティクラス変数名をエレガントに取得して、データベースクエリ条件を構築する方法は? Apr 19, 2025 pm 11:42 PM

データベース操作にMyBatis-Plusまたはその他のORMフレームワークを使用する場合、エンティティクラスの属性名に基づいてクエリ条件を構築する必要があることがよくあります。あなたが毎回手動で...

Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Intellijのアイデアは、ログを出力せずにSpring Bootプロジェクトのポート番号をどのように識別しますか? Apr 19, 2025 pm 11:45 PM

intellijideaultimatiateバージョンを使用してスプリングを開始します...

名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? 名前を数値に変換してソートを実装し、グループの一貫性を維持するにはどうすればよいですか? Apr 19, 2025 pm 11:30 PM

多くのアプリケーションシナリオでソートを実装するために名前を数値に変換するソリューションでは、ユーザーはグループ、特に1つでソートする必要がある場合があります...

Javaオブジェクトを配列に安全に変換する方法は? Javaオブジェクトを配列に安全に変換する方法は? Apr 19, 2025 pm 11:33 PM

Javaオブジェクトと配列の変換:リスクの詳細な議論と鋳造タイプ変換の正しい方法多くのJava初心者は、オブジェクトのアレイへの変換に遭遇します...

データベースクエリにTKMYBATISを使用するときに、エンティティクラスの変数名の構築クエリ条件をエレガントに取得する方法は? データベースクエリにTKMYBATISを使用するときに、エンティティクラスの変数名の構築クエリ条件をエレガントに取得する方法は? Apr 19, 2025 pm 09:51 PM

データベースクエリにTKMYBATISを使用する場合、クエリ条件を構築するためにエンティティクラスの変数名を優雅に取得する方法は一般的な問題です。この記事はピン留めします...

Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Redisキャッシュソリューションを使用して、製品ランキングリストの要件を効率的に実現する方法は? Apr 19, 2025 pm 11:36 PM

Redisキャッシュソリューションは、製品ランキングリストの要件をどのように実現しますか?開発プロセス中に、多くの場合、ランキングの要件に対処する必要があります。

See all articles