Javaのロック
Javaは、以下の分類に関する異なる特性に応じてロックを分類します。
ここでは、楽観と悲観的ロックに焦点を当て、Javaで実装し、対応します。同じデータの同時実行性について、悲観的ロックは、データの使用で自分自身を考慮し、各操作データは、他のスレッドのデータを変更しないようにすることを確実にするためにロックを追加しますので、前に、データを変更する他のスレッドがあるでしょう。Javaはロックを同期され、ロックロックが排他的ロックされています。
楽観的ロックは、更新データは、動作時にロックされていないので、データを変更する他のスレッドは存在しませんが、あなたは裁判官にデータを変更すると、変更されていない場合は、他のスレッドが、このデータを変更していないがあるたびに考えますそれは別のスレッドによって変更された場合、成功、再試行してくださいまたは失敗。Javaは、最も一般的に使用されるCASによってロックフリー並行プログラミングアルゴリズムを達成することです。
- 書き込みのためのペシミスティック・ロック最初のロックが書き込み動作の正確さを保証するためにあるため、一度、多くの小さなシーンをお読みください。
- 何のロック機能は楽観的ロックを有効にしないように、読み出し動作は一般的に、(データを変更していない)ロックする必要がないので、少しシーンを読み、書くためのオプティミスティック・ロックの性能を読んでは、大幅に(時間ロックを待機して減少)に改善されます。
ペシミスティック・ロック
Synchroniezdロック
ミューテックスは、同期、すなわち、悲観的ロックである唯一のスレッドが修正された方法またはコードの同期ブロックを入力することを可能にします。同期ロック、すなわち、スレッドが同じロックオブジェクトまたはクラスの時間を取得することができる、リエントラントです。
同期、アトミック、注文スレッド操作、可視性、複数のスレッドの下で安全な動作を確保することを保証するために、内蔵のロック同期を使用して変数。
ロックオブジェクトをロックするために、すなわち、3つの異なる方法を同期(改変された従来の方法で、オブジェクトが現在のクラスをロックすることである)、(非クラスオブジェクトロック電流)ブロックコードロック、ロッククラス(静的修飾方法、ロックは)現在のクラスです。もっと:同期ロック
以下はNスレッド0〜Mデジタル印刷サイクルで、ロックと同期しています。
public class SynchronizedTest implements Runnable {
// 定义一个对象用来保持锁
private static final Object LOCK = new Object();
// 当前线程
private int threadNum;
// 线程总数
private int threadSum;
// 当前数字,从0开始打印
private static int current = 0;
// 要打印的最大值
private int max;
public SynchronizedTest(int threadNum, int threadSum, int max) {
this.threadNum = threadNum;
this.threadSum = threadSum;
this.max = max;
}
@Override
public void run() {
// 实现N个线程循环打印数字
while (true) {
// 对代码块加锁,保证每次只有一个线程进入代码块
synchronized (LOCK) {
// 当前值 / 线程总数 = 当前线程
// 这里一定要用while,而不能用if。因为当线程被唤醒时,监视条件可能还没有满足(线程唤醒后是从wait后面开始执行)。
while (current % threadSum != threadNum) {
// 打印完了,跳出循环
if (current >= max) {
break;
}
// 不满足打印条件,则让出锁,进入等待队列
try {
LOCK.wait();
} catch (Exception e) {
e.printStackTrace();
}
}
// 这里还要做一次判断
if (current >= max) {
break;
}
System.out.println(Thread.currentThread().getName() + " 打印 " + current);
++current;
// 当前线程打印完了,通知所有等待的线程进入阻塞队列,然后一起去争抢锁
LOCK.notifyAll();
}
}
}
public static void main(String[] args) {
// 开启N个线程
int N = 3;
// 打印M个数字
int M = 15;
for (int i = 0; i < N; ++i) {
new Thread(new SynchronizedTest(i, N, M)).start();
}
}
}
复制代码
ロックロック
ロック悲観的ロックながら、また、ミューテックスロックです。これは、ロックを表示し、ロックのロック解除操作が手動で実装を必要と、ロックの同期解除は自動的に行われます。
ReentrantLockのは内部ロックロック公正かつ不当なロックを定義します。フェアロックの場合、内部のスレッドは、着信保持し、スレッドに最初確保するためにFIFOキューを維持し、最初に実行することができます。何人かの人々が餓死かもしれないつながる非公正ロック、スレッドがロックを解除する場合は、他のすべてのスレッドがロックをつかむことができ、ために、それは実行されません飽きないことがあります。しかし、時間のオーダーの絶対公平ロックを達成するために、頻繁なコンテキスト切り替えではなく、公正なロックを必要とする特定のコンテキストスイッチのオーバーヘッドが低減される低減されます。だから、ReentrantLockのデフォルト以外のフェアロックは、パフォーマンスを向上させるために使用しました。
ReentrantLockの視認性を以下(例えば、非フェアロック)原理を分析するために、達成するために状態を変更し、揮発性AQSを使用することによって達成されます。
ReentrantLockの最初の表示のロック、コールのロック方法。
final void lock() {
// 先尝试获取锁,也就是将state更新为1(这里用了CAS),如果获取成功,则将当前线程设置为独占模式同步的当前所有者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
// 如果获取失败,则进入acquire()方法
else
acquire(1);
}
复制代码
次のエントリの取得()メソッド:
public final void acquire(int arg) {
// 调用tryAcquire尝试获取锁
// 如果获取锁失败,则用当前线程创建一个独占结点加到等待队列的尾部,并继续尝试获取锁
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
复制代码
ここだけtryAcquire外観を入力します。
protected final boolean tryAcquire(int acquires) {
// 内部又调用了一个非公平的尝试获取锁方法
return nonfairTryAcquire(acquires);
}
复制代码
外観を下に入力します。
final boolean nonfairTryAcquire(int acquires) {
// 获取当前线程
final Thread current = Thread.currentThread();
// 重点!首先从主存中获取state(state是个volatile修饰的变量)
int c = getState();
// 如果state为0,说明没有获取过锁
if (c == 0) {
// 尝试获取锁
if (compareAndSetState(0, acquires)) {
// 将当前线程设置为独占模式当前所有者
setExclusiveOwnerThread(current);
return true;
}
}
// 如果state不为0,说明之前获取过锁
else if (current == getExclusiveOwnerThread()) {
// 将锁的数量叠加
int nextc = c + acquires;
if (nextc < 0) // 溢出(超过最大锁的数量)则抛出异常
throw new Error("Maximum lock count exceeded");
// 因为当前线程已经获取了锁,在这一步不会有其它线程来干扰,所以不需要用CAS来设置state
setState(nextc);
return true;
}
return false;
}
复制代码
上記の取得が失敗した場合、それはキューがロックを取得しようとし続けて待ちに追加されます、メジャーコードロックを取得することです。(このステップは、分析ではありません)
、ロックを解除する過程で、次のようになります:
public void unlock() {
// 通过内部类调用父类AbstractQueuedSynchronizer的release方法
sync.release(1);
}
复制代码
メソッドに次のリリース:
public final boolean release(int arg) {
// 调用tryRelease方法来尝试释放锁
if (tryRelease(arg)) {
Node h = head;
// 如果头节点不为空且等待状态非0
if (h != null && h.waitStatus != 0)
// 如果头节点的后继节点存在,则唤醒它
unparkSuccessor(h);
return true;
}
return false;
}
复制代码
ReentrantLockの内部同期クラスはメソッドtryReleaseを上書きします:
protected final boolean tryRelease(int releases) {
// 重点!也是首先获取state,并减去要释放的锁的数量
int c = getState() - releases;
// 如果当前线程不等于当前独占模式拥有者线程
if (Thread.currentThread() != getExclusiveOwnerThread())
// 抛出一个非法监视器状态异常
throw new IllegalMonitorStateException();
boolean free = false;
// 如果持有锁的数量为0
if (c == 0) {
// 设置锁为可释放
free = true;
// 把当前独占线程清空
setExclusiveOwnerThread(null);
}
// 设置state
setState(c);
return free;
}
复制代码
これらは、ロックコードを解放する鍵となります
時間が第1の入力状態メソッドを取得し、最後に状態端を設定するであろう場合、各ロックでロックを解除し、上記の分析が示すスルー。
状態は、すべてのスレッドから見えるように、各時間における揮発性変数がメインメモリにリフレッシュを強制するための非揮発性変数は、メインメモリに戻す更新されたときの状態変数は、揮発性によって修飾されているからです。
、確かに最初の呼び出しロックでロックされたコードでは(メインメモリをリフレッシュするためにもう一度も動作状態は、それが強制されます)(による揮発状態の運転に(書き込み前に読んで)、リフレッシュメインメモリを強制されます)、そして最後にコールロック解除、記載起こる-前ルール、書き込み揮発性変数は次読み取られることに表示されています。だから、同期コードの共有変数が表示されている保証します。
ここでReentrantLockのは、使用条件の変動条件をロックABCを印刷達成交互サイクルです。
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockTest {
// 定义一个显示锁
private static ReentrantLock lock = new ReentrantLock();
// 监控a的条件变量
private static Condition a = lock.newCondition();
// 监控b的条件变量
private static Condition b = lock.newCondition();
// 监控c的条件变量
private static Condition c = lock.newCondition();
// 控制要打印的值
private static int flag = 0;
public static void printA() {
for (int i = 0; i < 5; i++) {
// 显示加锁
lock.lock();
try {
try {
while (flag != 0) {
// 不满足监视条件则等待
a.await();
}
System.out.println(Thread.currentThread().getName() + "我是A");
flag = 1;
// 通知b线程去打印
b.signal();
} catch (Exception e) {
e.printStackTrace();
}
} finally {
// 释放锁
lock.unlock();
}
}
}
public static void printB() {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
try {
while (flag != 1) {
b.await();
}
System.out.println(Thread.currentThread().getName() + "我是B");
flag = 2;
c.signal();
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
public static void printC() {
for (int i = 0; i < 5; i++) {
lock.lock();
try {
try {
while (flag != 2) {
c.await();
}
System.out.println(Thread.currentThread().getName() + "我是C");
flag = 0;
a.signal();
} catch (Exception e) {
e.printStackTrace();
}
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
new Thread(() -> {
printA();
}).start();
new Thread(() -> {
printB();
}).start();
new Thread(() -> {
printC();
}).start();
}
}
复制代码
同期ロックとロックReentrantLockの
同じポイント
- リソースのロックのためにスレッド間のアクセスの同期を確保することができます。
- 彼らは、あるスレッドがより多くのリソースを繰り返しロックを持つことができているリエントラントロック、です。
- マルチスレッド不可分、秩序操作、可視操作外視界をロックする絶対的な保証はないが、外側ロックため(これが唯一、動作中の共有変数ロック視認性を保証することができるが保証され私たちは、それがメインメモリからデータを取得されています保証することはできません、ワーキングメモリは同期していてもよいです)
異なる点
- 同期を達成するために異なるメカニズム
- 同期Javaオブジェクトは、関連する監視モニターすることによって達成されます
- AQSてReentrantLockの、CASとその実現
- 可視性を達成するために異なるメカニズム
- 視認性を確保するためにJavaのメモリモデルによって同期
- AQS状態を介して視認性を確保するReentrantLockの(揮発性変性)
- 異なる監視条件
- 状態を監視するJavaオブジェクトで同期化
- 監視条件として条件(のawait、信号等を提供する)によってReentrantLockの
- さまざまな方法を使用します
- この方法の例は、同期(オブジェクトのロックされたインスタンス)を変更するために使用される、静的な方法(ロックされたクラスオブジェクト)、同期コードブロック(ロックされたオブジェクトは、指定されました)
- コールロックReentrantLockのロックを表示する必要があり、ロックを解除するのを最終的に必要性
- 異なる機能豊富なプログラム
- 単にロック同期
- ReentrantLockのロックを取得するタイミングを提供し、ロック状態条件(提供のawait、信号など)およびその他の機能のために、中断取得してもよいです。
- ロックのさまざまな種類
- 同期のみサポート料金フェアロック。
- ReentrantLockのサポートフェアロックとロック不公平が、非高効率フェアロック
最適化を同期する前に、その性能はReentrantLockのよりもはるかに悪化している、比較的重量級ですが、同期偏っロック、軽量ロック(スピンロック)、取り出しロック、ロック粗大化技術、両方のパフォーマンスの導入以来それはほとんど同じ位相です。
オプティミスティック・ロック
CASアルゴリズム
即ち、比較およびスワップ(比較およびスワップ)は、周知のロックフリーアルゴリズムです。スレッドが存在しない場合に変数を同期している複数のスレッド間で使用していないロック変数の同期がブロックされたときであるロックフリーのプログラミングは、それはまた、非ブロッキング同期(非ブロック同期)と呼ばれています。
CASアルゴリズムは、3つのオペランドが含まれます。
- (メモリ内の値)メモリ値Vを読み書きするために必要
- 値A(入力値)と比較されます
- 新しい値B(更新される値)が書き込まれます
Vの値が値Aに等しい場合にのみ及び、CASアトミック値VはBに更新された場合(比較及び置換がアトミック操作で、安全でない底部がオペレーティングシステムのアトミック性を保証するために)、VはAと等しくない場合、その後、障害またはもう一度お試しください。ロック操作への心配はありませんが、それは非常に効率的です。
しかし、それは3つの問題があります。
- 大きなループのオーバーヘッド。CASは、長い時間(同時書き込みより大きい)のために成功しない場合は、CPUリソースの無駄が生じ、長いスピンにつながります。
- アトミック操作は、変数のみを保証することができます。AtomicReferenceを提供するが、オブジェクト間の保証アトミック参照にJDK1.5クラスを開始するCAS動作における複数のオブジェクト内の変数が実行されてもよいです。
- ABAの問題。CAS最初のBの値、及びA.へ戻る場合 CASでは、値が変更されていないように見えるが、実際に条件を変更しました。最も典型的にはATMの問題からお金です:バランス100、私は、その後、ATMは二つのスレッドを開いたが、スレッドは一時的にハングアップ、50を取り出したスレッドが正常にバランスを更新する50である、と私の友人は私が50を回しました、ちょうど(成功することができCASに表示されますが、これは論理的ではない、emmm、50更新する、100の試みを操作し続けるこの場合には100に達したが、ちょうど人生にそのスレッドの背中からお金を取ります私の友人は)それは50が???起こっている私に転送されます。一般的なソリューションABAの問題は、この更新は、1A〜> 2B-は> 3Aので、CASは、彼らが同じではないと思うだろうということになり、バージョン番号の前に変数を追加することです。JDK1.5はAtomicStampedReference導入フラグを提供し始め、現在のフラグとフラグのcompareAndSetは、このクラス()メソッドの期待は、同じ成功を(このフラグは、各アップデートで更新されます)を更新するために必要とされます。
ここたCountDownLatch組み合わせAtomicStampedReferenceとABAの例を実装(問題はバージョン番号によって解決することができます)
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CasTest {
// 定义一个原子类型变量
private static AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 0);
// 定义一个线程计数器
private static CountDownLatch countDownLatch = new CountDownLatch(1);
public static void main(String[] args) {
new Thread(() -> {
// 打印当前线程的值
System.out.println("线程 " + Thread.currentThread().getName()
+ " value" + asr.getReference());
// 最开始的版本
int stamp = asr.getStamp();
try {
// 等待其它线程全部执行完毕(这里只需等待线程2运行结束)
countDownLatch.await();
} catch (Exception e) {
e.printStackTrace();
}
// 将1改为2,又改为1后,再次尝试将最开始的版本的1修改为2
// 操作结果应该是失败的,因为当前版本(0)与预期版本(2)不同
// 如果将取版本号的操作放在当前,操作结果肯定是成功的(因为这里修改的1不是最开始版本的1)
System.out.println(Thread.currentThread().getName()
+ " CAS操作结果 "
+ asr.compareAndSet(1, 2, stamp, stamp + 1));
}, "线程1").start();
new Thread(() -> {
// 把值修改为2
asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1);
System.out.println("线程 " + Thread.currentThread().getName()
+ " value" + asr.getReference());
// 把值修改为1
asr.compareAndSet(2, 1, asr.getStamp(), asr.getStamp() + 1);
System.out.println("线程 " + Thread.currentThread().getName()
+ " value" + asr.getReference());
// 当前任务执行完毕,等待线程数减1
countDownLatch.countDown();
}, "线程2").start();
}
}
复制代码