ReentrantLock 的一些方法
参考知乎关于lockInterruptibly用法
参考这位博主的多线程文章
getHoldCount() 方法:查询当前线程保持此锁定的个数,也就是调用 lock() 的次数;请注意这是当前线程锁定的次数
getQueueLength() 方法:返回正等待获取此锁定的线程估计数目;
isFair() 方法:判断是不是公平锁;
lockInterruptibly()
此方法比较特别,指的深入探究一下,通过网上查阅资料,首先研究一下线程的中断机制
- 线程在sleep或wait,join, 此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException,即需要手工处理异常
- 此线程在运行中, 则不会收到提醒。但是 此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并 作出处理。
lockInterruptibly的方法原理是:
- 线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。
- 并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException
下面是原作者的例子:
public class TestLock {
// @Test
public void test() throws Exception {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName()+" "+Thread.currentThread().isInterrupted());
System.out.println(Thread.currentThread().getName() + " interrupted.");
}
}, "child thread -1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
Thread.sleep(4000);
System.out.println(1);
lock.unlock();
}
public static void main(String[] args) throws Exception {
new TestLock().test();
}
}
运行结果:
1
child thread -1 true
child thread -1 interrupted.
可以看出lock方法对于子线程在等待锁过程中的中断,没有采取任何措施,只是将子线程的中断位设置了
public class TestLockInterruptibly {
// @Test
public void test3() throws Exception {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
lock.lockInterruptibly(); //子线程响应中断抛出异常
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted.");
}
}
}, "child thread -1");
t1.start();
Thread.sleep(1000);
t1.interrupt();
Thread.sleep(1000000);
}
public static void main(String[] args) throws Exception {
new TestLockInterruptibly().test3();
}
}
结果:
child thread -1 interrupted.
这种情况对应线程已经被中断,然后又lockInterruptibly,会抛出中断异常
public class TestLockInterruptibly {
// @Test
public void test3() throws Exception {
final Lock lock = new ReentrantLock();
lock.lock();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(500);
lock.lockInterruptibly();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " interrupted.");
}
}
}, "child thread -1");
t1.start();
t1.interrupt();
Thread.sleep(1000);
t1.interrupt();
Thread.sleep(1000000);
}
public static void main(String[] args) throws Exception {
new TestLockInterruptibly().test3();
}
}
运行结果:
child thread -1 interrupted.
ReentrantReadWriteLock的知识
ReentrantReadWriteLock 有两个锁:一个是与读相关的锁,称为“共享锁”;另一个是与写相关的锁,称为“排它锁”,也就是多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。
在没有线程进行写操作时,进行读操作的多个线程都可以获取到读锁,而写操作的线程只有获取写锁后才能进行写入操作。即:多个线程可以同时进行读操作,但是同一时刻只允许一个线程进行写操作。
读读共享;
写写互斥;
读写互斥;
写读互斥;
写写共享的例子:
public class ReentrantReadWriteLockDemo {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
new Thread(() -> demo.read(), "ThreadA").start();
new Thread(() -> demo.read(), "ThreadB").start();
}
private void read() {
try {
try {
lock.writeLock().lock();
for (int i = 0; i < 5; i++) {
System.out.println("获得读锁" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());
Thread.sleep(20);
}
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
获得读锁ThreadA 时间:1539446947855
获得读锁ThreadA 时间:1539446947878
获得读锁ThreadA 时间:1539446947898
获得读锁ThreadA 时间:1539446947918
获得读锁ThreadA 时间:1539446947940
获得读锁ThreadB 时间:1539446947963
获得读锁ThreadB 时间:1539446947983
获得读锁ThreadB 时间:1539446948004
获得读锁ThreadB 时间:1539446948024
获得读锁ThreadB 时间:1539446948044
即写锁的获取是串行的
读读的例子:
public class ReentrantReadWriteLockDemo {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
new Thread(() -> demo.read(), "ThreadA").start();
new Thread(() -> demo.read(), "ThreadB").start();
}
private void read() {
try {
try {
lock.readLock().lock();
for (int i = 0; i < 5; i++) {
System.out.println("获得读锁" + Thread.currentThread().getName() + " 时间:" + System.currentTimeMillis());
Thread.sleep(20);
}
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
获得读锁ThreadA 时间:1539447173394
获得读锁ThreadB 时间:1539447173396
获得读锁ThreadB 时间:1539447173416
获得读锁ThreadA 时间:1539447173416
获得读锁ThreadA 时间:1539447173436
获得读锁ThreadB 时间:1539447173436
获得读锁ThreadA 时间:1539447173456
获得读锁ThreadB 时间:1539447173456
获得读锁ThreadA 时间:1539447173476
获得读锁ThreadB 时间:1539447173476
可以看出A/B交替运行!
CountDownLatch
CountDownLatch是一个多线程控制工具类,它允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行。
比如下面这个例子,车子等人,当七个人到齐后,开车
public class CountDownLatchDemo {
private static final int THREAD_COUNT_NUM = 7;
private static CountDownLatch countDownLatch = new CountDownLatch(THREAD_COUNT_NUM);
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
int index = i;
new Thread(() -> {
try {
System.out.println("第" + index + "个人上车!");
//模拟不同人上车时间
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
//每上车一个人,需要等待的数减1
countDownLatch.countDown();
}).start();
}
//等待检查,即上述7个线程执行完毕之后,执行await后边的代码
countDownLatch.await();
System.out.println("七个人到齐,开车");
}
}
一般使用的时候,在主线程启动启动子线程后,会执行countDownLatch.await();阻塞主线程,让子线程得到机会执行,
每一次子线程执行完毕后,执行 countDownLatch.countDown();通知主线程,当countdown到0后,主线程开始执行 countDownLatch.await();后面的代码
循环屏障 CyclicBarrier
CyclicBarrier 和 countDownLatch 都是类似功能,但是CyclicBarrier 会阻塞子进程,而countdownLatch会阻塞主进程,而且CyclicBarrier可以重置操作,countDownLatch则是一次性的
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活。
CyclicBarrier 默认的构造方法是 CyclicBarrier(int parties),其参数表示屏障拦截的线程数量,每个线程调用 await 方法告诉 CyclicBarrier 我已经到达了屏障,然后当前线程被阻塞。
CyclicBarrier 强调的是 N 个线程,大家相互等待,只要有一个没完成,所有人都得等着。
下面是从网上博客摘抄的例子,召唤神龙,需要7个法师去寻找龙珠,但这7个法师并不是一下子就能号召起来的,所以要等待召集齐7个法师,然后在秋名山顶烧香拜佛为这7位法师送行,让他们同时出发,前往不同的地方寻找龙珠(敲黑板:这是第一个屏障点),在这七位法师临行时约定找到龙珠之后,还回到这个地方等待其他法师找到龙珠之后一起去见我。几年之后,第一个法师回来了,然后等待其他的法师……最后所有的法师全部到齐(敲黑板:这是第一个屏障点),然后组队来找我召唤神龙。
public class SummonDragonDemo {
private static final int THREAD_COUNT_NUM = 7;
public static void main(String[] args) {
//设置第一个屏障点,等待召集齐7位法师
CyclicBarrier callMasterBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
@Override
public void run() {
System.out.println("7个法师召集完毕,同时出发,去往不同地方寻找龙珠!");
summonDragon();
}
});
//召集齐7位法师
for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
int index = i;
new Thread(() -> {
try {
System.out.println("召集第" + index + "个法师");
callMasterBarrier.await(); //子线程阻塞自己
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
/**
* 召唤神龙:1、收集龙珠;2、召唤神龙
*/
private static void summonDragon() {
//设置第二个屏障点,等待7位法师收集完7颗龙珠,召唤神龙
CyclicBarrier summonDragonBarrier = new CyclicBarrier(THREAD_COUNT_NUM, new Runnable() {
@Override
public void run() {
System.out.println("集齐七颗龙珠!召唤神龙!");
}
});
//收集7颗龙珠
for (int i = 1; i <= THREAD_COUNT_NUM; i++) {
int index = i;
new Thread(() -> {
try {
System.out.println("第" + index + "颗龙珠已收集到!");
summonDragonBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
执行结果:
召集第1个法师
召集第3个法师
召集第2个法师
召集第4个法师
召集第5个法师
召集第6个法师
召集第7个法师
7个法师召集完毕,同时出发,去往不同地方寻找龙珠!
第1颗龙珠已收集到!
第2颗龙珠已收集到!
第3颗龙珠已收集到!
第4颗龙珠已收集到!
第5颗龙珠已收集到!
第6颗龙珠已收集到!
第7颗龙珠已收集到!
集齐七颗龙珠!召唤神龙!
new CyclicBarrier(int num, Runnable runable) 意思是创建一个内存屏障,只有num个子线程都到达屏障处,则有最后一个到达的线程执行runable里的动作
通过下列图,可以看到其实是thread 6方法在执行召唤神龙的任务