前回の記事では、JAVAで可視性と順序がどのように解決されるかを説明しました。
原子問題を解決するには?
アトミック性の原因は、スレッドの切り替えです。スレッドが共有変数の読み取りと書き込みを行っても、完了していません。別のスレッドに切り替えて、共有変数の読み取りと書き込みを行ってください。たとえば、次の古典的なカウント++の問題です。
これにより、スレッドの切り替えが許可されず、オペレーティングシステムのスレッドの切り替えがCPU割り込みに依存している限り、この問題を解決できます。CPUの割り込みを禁止すると、スレッドの切り替えを禁止できます。
シングルコアCPUで割り込みを無効にすると、スレッド切り替えを無効にしてアトミック性を確保できますが、それらのほとんどはマルチコアCPUです。同時に、2つのスレッドが2つのCPUで同時に実行される可能性があるため、割り込みの禁止は機能しません。 。
実際、一度に実行するスレッドが1つだけであることを確認するだけでよく、それをmutex、つまりロックと呼びます。
上の図に示すように、スレッドが保護されたリソースを読み書きしたい場合、他のスレッドがロックを取得できず、保護されたリソースにアクセスできないように、ロックLRを取得する必要があります。スレッドがロックを解除してロックオブジェクトを解放すると、他のスレッドはロックを取得して、保護されたリソースを読み書きできます。リソース。
ここではロックとリソースについて説明していますが、ロックとリソースの定量的な関係について検討しましたか?
ロックとリソースの関係は1です。N(Nリソースは関連していません)。つまり、ロックは複数のリソースをロックできますが、リソースは複数のロックで制御できません。そうでない場合、異なるCPUが同時に異なるロックを取得できます。リソースへのアクセス。ここで、ロックは実際にはトークンとして理解でき、リソースはトークンを取得した後でのみアクセスできます。
関連するリソースが2つある場合は、両方のリソースをカバーするロックが必要です。
転送の場合と同様に、
class Account {
private int balance;
// 转账
synchronized void transfer(
Account target, int amt){
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
アカウントが他の人にお金を送金したい場合、他の人に送金するために、このオブジェクトである同期のロックを取得する必要があります。問題はないように見えますが、問題はこのロックが自分の残高にアクセスできますが、他の人にはアクセスできませんアカウント残高。
たとえば、アカウントA、B、Cの残高はそれぞれ200です。CPU1のスレッド1がアカウントAを実行し、独自のロックを取得して100をアカウントBに転送します(アカウントAの残高= 200-100 = 100書き込み、アカウントBのblance = 200 + 100 = 300書き込み)、同時に、CPU2のスレッド2がアカウントBを実行し、独自のロックを取得して100をアカウントCに転送することもできます(バランス= 200-100 =アカウントBの100書き込み、アカウントCのバランス) = 200 + 100 = 300書き込み)。
スレッド1が最初に書き込み、スレッド2が後で書き込む場合、アカウントA = 100、アカウントB = 300が最初に、B = 100が上書き、B = 100、アカウントC = 300、100少なくなります。
スレッド2が最初に書き込み、スレッド1が後で書き込む場合、結果はアカウントA = 100、アカウントB = 100が最初で、次にB = 300上書き、B = 300、アカウントC = 300、さらに100です。
したがって、ロックを適切に使用する方法は、2つのリソースを同時にカバーするロックが必要であるため、これらの2つのリソースにアクセスするには、より大きなロックを取得する必要があります。
たとえば
// 转账
void transfer(Account target, int amt){
// 此处检查所有对象共享的锁
synchronized(lock) {//这里锁的是class对象,程序运行时,jvm中只有一份
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
しかし、上記ではAccount.classをロックとして使用しているため、すべてのアカウント転送操作が同期されます。
実際、このアカウントと転送先オブジェクトをロックとして使用でき、2つのメソッドのみを同時に転送できます。
class Account {
private int balance;
// 转账
void transfer(Account target, int amt){
// 锁定转出账户
synchronized(this) {
// 锁定转入账户
synchronized(target) {
if (this.balance > amt) {
this.balance -= amt;
target.balance += amt;
}
}
}
}
}
実際、ここでもデッドロックの問題が発生することがわかりました。1つのスレッドがこれを取得し、別のスレッドがターゲットを取得し、相手のロックを一斉に待機すると、デッドロックが発生します。
デッドロック問題を解決し、
- 実際、ReentranLockのtryLockを使用して、ロックを取得しようとすることができます。ロックが取得されない場合、ブロッキング状態にはなりませんが、直接戻り、ロックを解放するか、タイムアウトをサポートし、一定時間内にロックを取得し、ロックを取得できず、戻り、解放できません。ロック。この場合、ライブロックの問題に注意してください。
- 実際、2つのロックオブジェクトを同時に取得することもできます(2つのロックを取得するか、どちらも取得しません)。
// 转账
void transfer(Account target, int amt){
// 一次性申请转出账户和转入账户,直到成功
while(!actr.apply(this, target))
;
try{
// 锁定转出账户
synchronized(this){
// 锁定转入账户
synchronized(target){
if (this.balance > amt){
this.balance -= amt;
target.balance += amt;
}
2つのロックオブジェクトを同時に取得できない場合は、whileループを使用して待機するため、CPUを大量に消費します。
実際、同期待機()、通知()、通知すべて()を使用して、ウェイクアップ待機メカニズムを実装できます。
class Allocator {
private List<Object> als;
// 一次性申请所有资源
synchronized void apply(
Object from, Object to){
// 经典写法
while(als.contains(from) ||
als.contains(to)){
try{
wait();
}catch(Exception e){
}
}
als.add(from);
als.add(to);
}
// 归还资源
synchronized void free(
Object from, Object to){
als.remove(from);
als.remove(to);
notifyAll();
}
}
notifyall()は可能な限り使用してください。なぜならば、notifyallはすべてを起動して1つを選択し、notifyはランダムに1つだけ起動するからです。したがって、特定のスレッドが起こされない可能性があることを通知してください。
注:wait()メソッドとnotify()メソッドは同期メソッドに含める必要があり、wait()メソッドをwhileループに含める必要があります。スレッドがロックエントリメソッドを取得し、他のスレッドがそのメソッドに入ろうとすると、そのメソッドは待機キュー1に格納されます。ロックが取得されると、エントリメソッドが満たされないため、待機し、待機キュー2に入ります。通知が起動すると、通知が起動します。スレッドがキュー2で起こされるのを待ち、行wait()から実行を継続するため、whileループなしで条件が再決定されない場合、期間中に条件が満たされていない可能性がありますが、実行されます!
これが終わって、あなたは非常に上記のsynchroned、またはその興味を持って理解していない場合は、あなたが見ることができるチューブモデルが同期原則と同期の実現を。