目次
I. 概要
Lock は Synchronized に似たスレッド同期メカニズムですが、Lock は Synchronized よりも柔軟です。Lock は、ReentrantLock (再入可能ロック)、ReentrantReadWriteLock (読み取り/書き込みロック) の 2 つの実装クラスを持つインターフェイスです。
2、ロックと同期の違い
- lock はインターフェースであり、synchronized は Java のキーワードです
- 同期では、ユーザーが手動でロックを解放する必要はなく、例外が発生するかスレッドが終了すると自動的にロックが解放されます。ロックではユーザーが手動でロックを解放する必要があります。ロックが積極的に解放されないと、デッドロックが発生する可能性があります。
- lock を使用すると、ロックを待機しているスレッドが割り込みに応答できるようになります。synchronized を使用すると、待機中のスレッドは永遠に待機し、割り込みに応答できなくなります。
3. ロックAPI
4、ReentrantLock(リエントラントロック)、ReentrantReadWriteLock(読み書きロック)
1. ReentrantLock(リエントラントロック)
リエントラント ロックは再帰ロックとも呼ばれます。これは、同じスレッドの外側の関数がロックを取得した後、内側の再帰関数にはロックを取得するためのコードがまだ残っていますが、影響を受けないことを意味します。
public class ReentrantDemo implements Runnable {
Lock lock = new ReentrantLock();
@Override
public void run() {
set();
}
public void set() {
try {
lock.lock();
System.out.println("set 方法");
get();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();// 必须在finally中释放
}
}
public void get() {
try {
lock.lock();
System.out.println("get 方法");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantDemo reentrantDemo = new ReentrantDemo();
new Thread(reentrantDemo).start();
}
テスト結果: 同じスレッドが最初に set メソッドでロックを取得し、次に get メソッドを呼び出し、get メソッドで同じロックを繰り返し取得します。どちらのメソッドも正常に実行されます
2. ReentrantReadWriteLock (読み取り/書き込みロック)
読み取り/書き込みロックは、それぞれ読み取りロックまたは書き込みロックを取得できます。つまり、データの読み取りと書き込み操作を分離し、2 つのロックに分割してスレッドに割り当て、複数のスレッドが同時に読み取り操作を実行できるようにします。読み取りロックは共有モードを使用し、書き込みロックは排他モードを使用します。書き込みロックがない場合、読み取りロックは複数のスレッドによって同時に保持でき、書き込みロックは排他的です。読み取りロックがある場合、書き込みロックは取得できません。また、書き込みロックがある場合、書き込みロックを取得したスレッドを除き、読み取りロックを取得でき、他のスレッドは読み取りロックを取得できません。
- writeLock(): 書き込みロックを取得します。
- readLock(): 読み取りロックを取得します。
3 つのスレッドを実行して読み取りおよび書き込み操作を実行し、バリアを設定します。各スレッドの準備が整ったら、ロックを取得する前に待機します。3 番目のスレッドが cyclicBarrier.await() を実行すると、バリアが解放され、3 つのスレッドが実行されます
。同時に。
public class WriteAndReadLockTest {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10,
60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>());
private static CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
private static int i = 100;
public static void main(String[] args) {
threadPoolExecutor.execute(()->{
read(Thread.currentThread());
});
threadPoolExecutor.execute(()->{
write(Thread.currentThread());
});
threadPoolExecutor.execute(()->{
read(Thread.currentThread());
});
threadPoolExecutor.shutdown();
}
private static void read(Thread thread) {
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
reentrantReadWriteLock.readLock().lock();
try {
System.out.println("读线程 "+ thread.getName() + " 开始执行, i=" + i);
Thread.sleep(1000);
System.out.println(thread.getName() +" is over!");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
private static void write(Thread thread) {
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
reentrantReadWriteLock.writeLock().lock();
try {
i++;
System.out.println("写线程 "+ thread.getName() + " is doing, i=" + i);
System.out.println(thread.getName() +" is over!");
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
}
実行結果:スレッド3が先にリードロックを獲得 リードロックは共有できるため、スレッド1もリードロックを獲得できる スレッド1とスレッド3のリード操作が完了した後、スレッド2よりも先にリードロックを解放できる書き込みロックを取得し、書き込み操作を開始できます
5. LockロックのAPIコード実装
1、ロック()、ロック解除()
public class LockAndUnlock implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
//上锁
lock.lock();
try {
System.out.println("ThreadA获取锁");
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // 解锁
System.out.println("ThreadA释放锁");
}
}
public static void main(String[] args) {
LockAndUnlock lockAndUnlock = new LockAndUnlock();
Thread threadA = new Thread(lockAndUnlock,"threadA");
threadA.start();
}
}
結果:
2、割り込みロック()
例: 2 つのスレッドが同時に lock.lockInterruptibly() を通じてロックを取得したい場合、この時点でスレッド A がロックを取得し、スレッド B が待機しているだけであれば、スレッド B で threadB.interrupt() メソッドを呼び出すことができます。スレッド B の待機中のプロセスを中断します。
public class LockInterruptiblyDemo {
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly();
System.out.println("ThreadA获得锁 ");
// 模拟线程A持有锁的一段时间
Thread.sleep(2000);
} catch (InterruptedException e) {
System.out.println("ThreadA在等待锁时被中断");
} finally {
if (Thread.holdsLock(lock)) {
lock.unlock();
}
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly();
System.out.println("ThreadB获得锁");
} catch (InterruptedException e) {
System.out.println("ThreadB在等待锁时被中断");
} finally {
if (Thread.holdsLock(lock)) {
lock.unlock();
}
}
}
});
threadA.start();
// 让线程A先获取到锁
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.start();
// 在线程B等待锁的过程中,中断线程B
threadB.interrupt();
}
}
結果:
3、tryLock()
例: 2 つのスレッドが同時に lock.tryLock() を通じてロックを取得したい場合、この時点でスレッド A がロックを取得すると、スレッド B は待機せずに直接ロックの取得を諦めます。
public class TryLock{
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
Thread threadA = new Thread(new Runnable() {
@Override
public void run() {
if (lock.tryLock()) {
try {
System.out.println("ThreadA获得了锁");
// 模拟线程A持有锁的一段时间
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
} else {
System.out.println("ThreadA获取锁失败");
}
}
});
Thread threadB = new Thread(new Runnable() {
@Override
public void run() {
if (lock.tryLock()) {
try {
System.out.println("ThreadB获得了锁");
} finally {
lock.unlock();
}
} else {
System.out.println("ThreadB获取锁失败");
}
}
});
threadA.start();
// 让线程A先获取到锁
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadB.start();
}
}
結果:
4、tryLock(長時間、TimeUnit単位)
ロックが使用可能な場合、このメソッドはすぐに値 true を返します。
ロックが使用できない場合、現在のスレッドはスレッド スケジューリングが無効になり、次の 3 つのいずれかが起こるまでスリープ状態になります。
● 現在のスレッドがロックを取得する。
● 他のスレッドが現在のスレッドに割り込みます。
● 待機時間が経過した場合、false を返します。
public class TryLockParam implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
try {
//假设 threadA 线程先持有锁, 完成任务需要 4 秒钟,
//这个时候 threadB 线程尝试获得锁, threadB 线程在 3 秒内还没有获得锁的话, 那么它就不再等了,直接放弃
if (lock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得锁,执行耗时任务");
Thread.sleep(1000 * 4);
}else {
System.out.println(Thread.currentThread().getName() + "放弃获得锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (Thread.holdsLock(lock)) {
lock.unlock();
}
}
}
public static void main(String[] args) {
TryLockParam tryLockParam=new TryLockParam();
Thread threadA=new Thread(tryLockParam,"threadA");
Thread threadB=new Thread(tryLockParam,"threadB");
threadA.start();
threadB.start();
}
}
結果:
ロックが利用できない場合、現在のスレッドはスレッド スケジューリングが無効になり、次の 3 つの状況のいずれかが発生するまで休止状態になります。
public class TryLockParam implements Runnable{
Lock lock = new ReentrantLock();
@Override
public void run() {
try {
//假设 threadA 线程先持有锁, 完成任务需要 4 秒钟,
//这个时候 threadB 线程尝试获得锁, threadB 线程在 3 秒内还没有获得锁的话, 那么它就不再等了,直接放弃
if (lock.tryLock(3, TimeUnit.SECONDS)) {
System.out.println(Thread.currentThread().getName() + "获得锁");
for (int i = 0; i <= 500000000; i++) {
if (i == 500000000) {
System.out.println("运算结束");
}
}
lock.unlock();
}else {
System.out.println(Thread.currentThread().getName() + "放弃获得锁");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() +"中断");
e.printStackTrace();
}
}
public static void main(String[] args) {
TryLockParam tryLockParam=new TryLockParam();
Thread threadA=new Thread(tryLockParam,"threadA");
Thread threadB=new Thread(tryLockParam,"threadB");
threadA.start();
threadB.start();
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadA.interrupt();
threadB.interrupt();
}
}
実行結果: threadA がロックを取得し、他のスレッドがスリープし、その後中断される