前面已经介绍了synchronized关键字如何实现线程安全的同步,下面介绍一个更为灵活的Lock接口(ReadWriteLock接口)来实现多线程的线程安全。
基础知识
synchronized 与Lock(或ReadWriteLock)的区别
声明:以下几点https://www.cnblogs.com/dolphin0520/p/3923167.html总结的很好,这里直接引用前辈的。
- Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断。
- 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
- Lock可以提高多个线程进行读操作的效率。
注:在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时
竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。
锁的相关知识介绍
可重入锁
当某个方法(或代码块儿)已经获得了锁时,如果该方法(或代码块儿)内部犯法(或内部代码块儿)还需要获取该(同
一个)锁,那么无需竞争,默认获得。只要“外面”已经获得了锁,那么进入里面需要同一个锁的方法(或代码块儿)时,可重入。
即:最外层已经获得了锁了,那么只要程序还处在这个大范围内,那么就一直持有着该锁。
可中断锁
当某个线程正在等待一个正在被其它线程占用的锁时,如果这个正在等待锁的线程可以被响应中断。那么即为可中断锁。
注:Lock为可中断锁,而synchronized为不可中断锁。
公平锁
如果多个线程抢占同一个锁时,总是被等待时间最长(排队最前面)的线程获得,那么就是公平锁。
即:“先到先得”即为公平锁。
非公平锁
如果多个线程抢占同一个锁时,能否抢占到与等待时间无关,那么就是非公平锁。
即:“先到不一定先得”,能不能抢到锁看自己,即为非公平锁。
注:synchronized为非公平锁;Lock的ReentrantLock()实现默认为非公平锁。当然我们可以通过
ReentrantLock(boolean flag)来指定其属于公平锁还是非公平锁。
读写锁
读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
即:多个读锁时,可提高效率。
Lock接口方法说明
package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;
/**
* Lock接口 保证线程安全
*
* @author JustryDeng
* @DATE 2018年9月4日 下午1:49:56
*/
public interface Lock {
/**
* 获取锁
* 注:如果当前线程没有获取到锁,那么会一直等待直到获取到锁
*/
void lock();
/**
* 获取锁
* 注:如果一个线程在等待其他线程释放锁时线程中断,那么这个线程抛出异常
* 注:相当于等待时长无限的tryLock(long time, TimeUnit unit)方法
*
* @throws InterruptedException
* 线程中断异常
* @DATE 2018年9月4日 下午1:36:12
*/
void lockInterruptibly() throws InterruptedException;
/**
* 获取锁
* 注:立即返回结果。如果获取到了立马返回true;如果没有获取到,那么也不作等待,直接返回false
*
* @DATE 2018年9月4日 下午1:38:42
*/
boolean tryLock();
/**
* 获取锁
* 注:如果在time时间内(时间单位为unit)获取到了锁,那么返回true;否则返回false
* 注:如果在等待获取锁时,当前线程中断,那么抛出异常
*
* @throws InterruptedException
* 线程中断异常
* @DATE 2018年9月4日 下午1:38:42
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 释放锁
*
* @DATE 2018年9月4日 下午1:44:13
*/
void unlock();
/**
* 返回一个(绑定到此 Lock实例的) Condition 实例。
* 注:Condition 将 Object 监视器方法(wait、notify 和 notifyAll)
* 分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,
* 为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了
* synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
*
* @DATE 2018年9月4日 下午1:44:37
*/
Condition newCondition();
}
详细代码示例说明
.lock()示例
/**
* .lock()使用测试
*
* @author JustryDeng
* @DATE 2018年9月4日 上午11:20:17
*/
public class LockTest {
/**
* 多线程测试方法
* 注:多个线程同时调用此方法(即:抢占此资源)
*
* @DATE 2018年9月4日 下午2:18:49
*/
static void multiThreadTest(Thread thread) {
String threadName = thread.getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "进入for循环,此次i为:" + i);
}
System.out.println(threadName + "执行完毕!");
}
/**
* 程序入口
*
* @DATE 2018年9月4日 下午2:28:40
*/
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 匿名内部类,开启第一个线程
new Thread("线程One") {
public void run() {
// 需要获取到lock的锁能进入下面的代码
lock.lock();
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
} catch (Exception e) {
}finally {
lock.unlock();
}
};
}.start();
// 匿名内部类,开启第二个线程
new Thread("线程Two") {
public void run() {
// 需要获取到lock的锁能进入下面的代码
lock.lock();
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
} catch (Exception e) {
}finally {
lock.unlock();
}
};
}.start();
}
}
注意事项:
一个Lock实例只有一个锁,当某一个线程获取到了这个对象的锁后,其他线程要想获取到这个(同一个)对象的锁,就需
要等待(读锁例外,下面会讲到)。当然,如果每个线程需要获取不同的Lock对象的锁,那么也不能保证线程安全。即:多个线
程必须保证要抢占的是同一个“锁”,才能保证线程安全。下面也是一样的道理,下面就不再赘述了。
输出结果为:
注:无论执行多少遍main函数,都会发现都是先等其中一个线程执行完,其他线程才开始执行。由此可见:lock实现了线程安全。
.tryLock()示例
/**
* .tryLock()使用测试
* 注:无论线程是否抢占到了Lock实例的锁,都会立即返回一个结果
*
* @author JustryDeng
* @DATE 2018年9月4日 上午11:20:17
*/
public class TryLockNoParamTest {
/**
* 多线程测试方法
* 注:多个线程同时调用此方法(即:抢占此资源)
*
* @DATE 2018年9月4日 下午2:18:49
*/
static void multiThreadTest(Thread thread) {
String threadName = thread.getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "进入for循环,此次i为:" + i);
}
System.out.println(threadName + "执行完毕!");
}
/**
* 程序入口
*
* @DATE 2018年9月4日 下午2:28:40
*/
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 匿名内部类,开启第一个线程
new Thread("线程One") {
public void run() {
// 需要获取到lock的锁能进入下面的代码
if(lock.tryLock()) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock()抢占到了锁,立即返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
} catch (Exception e) {
}finally {
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock()没抢占到锁,不作等待,立即返回false");
}
};
}.start();
// 匿名内部类,开启第二个线程
new Thread("线程Two") {
public void run() {
// 需要获取到lock的锁能进入下面的代码
if(lock.tryLock()) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock()抢占到了锁,立即返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
} catch (Exception e) {
}finally {
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock()没抢占到锁,不作等待,立即返回false");
}
};
}.start();
}
}
注意事项:
只有需要Lock锁的程序,才会遵循“排队”执行规则,即:只有对应的需要锁的代码块儿才会在抢占到了锁的前提下才执行,如果线程中压根就没有需要锁的代码,那么是不需要抢占锁才执行的。
注:锁的基本规范、基本概念、基本遵循流程,都与synchronized是一样的。
某次main函数输出结果为:
注:上面已经提到,如果没抢占到锁,线程Two就不会进入if(){}成立的逻辑中,就会进入esle{}逻辑中,而else{}中的逻辑属于
“普通”代码,无需抢占Lock实例的锁、无需考虑线程安全问题,所以线程Two不会等线程One释放了锁才执行,会和线
程One抢占执行时机。
注:假设线程A、线程B、线程C之间是需要同时抢占同一个Lock实例lock123的锁,线程H、线程I、线程J之间是需要同时
抢占另一个Lock实例lock456的锁,线程X、线程Y、线程Z是不需要锁的。那么:线程X、线程Y、线程Z、线程X(或线程
Y或线程Z)、线程H(或线程I或线程J)最多五个线程同时执行代码。下面也是一样的道理,下面就不再赘述了。
.tryLock(long time, TimeUnit unit)之等待获取锁时长测试示例
/**
* .tryLock(long time, TimeUnit unit)使用测试(注:这里主要测试time)
* 注:如果线程没有抢占到锁,那么等待time这么久(单位为unit),
* 注:等待期间,抢占到了返回true;过了时间仍然没有抢占到,那么返回false
* 注:等待期间,线程中断了,那么throws InterruptedException
*
* @author JustryDeng
* @DATE 2018年9月4日 上午11:20:17
*/
public class TryLockHasParamTestTime {
/**
* 多线程测试方法
* 注:多个线程同时调用此方法(即:抢占此资源)
*
* @DATE 2018年9月4日 下午2:18:49
*/
static void multiThreadTest(Thread thread) {
String threadName = thread.getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "进入for循环,此次i为:" + i);
}
System.out.println(threadName + "执行完毕!");
}
/**
* 程序入口
*
* @DATE 2018年9月4日 下午2:28:40
*/
public static void main(String[] args) {
Lock lock = new ReentrantLock();
// 匿名内部类,开启第一个线程
new Thread("线程One") {
public void run() {
try {
// 需要获取到lock的锁才能进入if条件成立对应的代码
// 设置等待获取Lock实例锁,最多等待100毫秒
if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)在指定时"
+ "间内抢占到了锁,返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
// 休眠101毫秒。如果此线程获得了锁,那么另一个线程必定等待超时,返回false
Thread.sleep(101);
}finally {
// 释放锁
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)指定时"
+ "间内没抢占到锁,返回false");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
};
}.start();
// 匿名内部类,开启第二个线程
new Thread("线程Two") {
public void run() {
try {
// 需要获取到lock的锁才能进入if条件成立对应的代码
// 设置等待获取Lock实例锁,最多等待100毫秒
if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)在指定时"
+ "间内抢占到了锁,返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
// 休眠50毫秒。如果此线程获得了锁,那么另一个线程必定等待超时,返回false
Thread.sleep(50);
}finally {
// 释放锁
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)指定时"
+ "间内没抢占到锁,返回false");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
};
}.start();
}
}
多次运行main函数,其中某两次的输出结果为:
某一次:
某一次:
.tryLock(long time, TimeUnit unit)之等待期间中断进而抛出异常测试示例
/**
* .tryLock(long time, TimeUnit unit)使用测试(这里主要测试 线程 中断时 抛出异常)
* 注:如果线程没有抢占到锁,那么等待time这么久(单位为unit),
* 注:等待期间,抢占到了返回true;过了时间仍然没有抢占到,那么返回false
* 注:等待期间,线程中断了,那么throws InterruptedException
*
* @author JustryDeng
* @DATE 2018年9月4日 上午11:20:17
*/
public class TryLockHasParamTestException {
/**
* 多线程测试方法
* 注:多个线程同时调用此方法(即:抢占此资源)
*
* @DATE 2018年9月4日 下午2:18:49
*/
static void multiThreadTest(Thread thread) {
String threadName = thread.getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "进入for循环,此次i为:" + i);
}
System.out.println(threadName + "执行完毕!");
}
/**
* 为了将代码放在一个类里面展示,所以本人在这里使用了成员内部类
* 注:实际上单独创建一个继承Thread的类更轻松
*
* @DATE 2018年9月4日 下午5:42:28
*/
class MyThread extends Thread {
Lock lock = null;
public MyThread(Lock lock, String name) {
super(name);
this.lock = lock;
}
@Override
public void run() {
try {
// 需要获取到lock的锁才能进入if条件成立对应的代码
// 设置等待获取Lock实例锁,最多等待200毫秒
if(lock.tryLock(200, TimeUnit.MILLISECONDS)) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)在指定时"
+ "间内抢占到了锁,返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
}finally {
// 释放锁
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)指定时"
+ "间内没抢占到锁,返回false");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
}
}
/**
* 程序入口
*
* @DATE 2018年9月4日 下午2:28:40
*/
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
// 匿名内部类,开启第一个线程
new Thread("线程One") {
public void run() {
try {
// 需要获取到lock的锁才能进入if条件成立对应的代码
// 设置等待获取Lock实例锁,最多等待100毫秒
if(lock.tryLock(100, TimeUnit.MILLISECONDS)) {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)在指定时"
+ "间内抢占到了锁,返回true;进入if条件成立逻辑");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
// 休眠101毫秒。如果此线程获得了锁,那么另一个线程必定等待超时,返回false
Thread.sleep(101);
}finally {
// 释放锁
lock.unlock();
}
}else {
System.out.println(Thread.currentThread().getName()
+ "中的.tryLock(long time, TimeUnit unit)指定时"
+ "间内没抢占到锁,返回false");
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
};
}.start();
// 成员内部类,开启第二个线程
TryLockHasParamTestException.MyThread myThread =
new TryLockHasParamTestException().new MyThread(lock, "线程Two");
myThread.start();
// 在主线程中计时,过20毫秒,中断myThread线程
// 注:如果线程One率先抢占到了锁,那么至少会消耗101毫秒(因为我在线程One中sleep了200毫秒)才会释放锁
// 那么线程Two至少需要等待101毫秒;这里我们在其等待的第20毫秒左右中断该线程,那么线程Two中的
// lock.tryLock(100, TimeUnit.MILLISECONDS)方法会抛出InterruptedException异常,
// 进而被捕获,输出"线程Two在等待获取锁时,【被中断】了!!!"
long startTime = System.currentTimeMillis();
for (;;) {
if(System.currentTimeMillis() - startTime >= 20) {
myThread.interrupt();
break;
}
}
}
}
输出结果为:
注:线程的.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待
新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被
中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。
更多可参考:https://www.cnblogs.com/onlywujun/p/3565082.html
.lockInterruptibly()之等待期间中断进而抛出异常测试示例
/**
* .lockInterruptibly()使用测试
* 注:如果线程没有抢占到锁,那么进行等待(等待时间无限大)
* 注:等待期间,线程中断了,那么throws InterruptedException
* 注:此方法相当于等待时间无限久的.tryLock(long time, TimeUnit unit)方法
*
* @author JustryDeng
* @DATE 2018年9月4日 上午11:20:17
*/
public class LockInterruptiblyTest {
/**
* 多线程测试方法
* 注:多个线程同时调用此方法(即:抢占此资源)
*
* @DATE 2018年9月4日 下午2:18:49
*/
static void multiThreadTest(Thread thread) {
String threadName = thread.getName();
for (int i = 0; i < 10; i++) {
System.out.println(threadName + "进入for循环,此次i为:" + i);
}
System.out.println(threadName + "执行完毕!");
}
/**
* 为了将代码放在一个类里面展示,所以本人在这里使用了成员内部类
* 注:实际上单独创建一个继承Thread的类更轻松
*
* @DATE 2018年9月4日 下午5:42:28
*/
class MyThread extends Thread {
Lock lock = null;
public MyThread(Lock lock, String name) {
super(name);
this.lock = lock;
}
@Override
public void run() {
try {
// 需要获取到lock的锁才能进入下面的代码
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()
+ "抢占到了锁!");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
}finally {
// 释放锁
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
}
}
/**
* 程序入口
*
* @DATE 2018年9月4日 下午2:28:40
*/
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
// 匿名内部类,开启第一个线程
new Thread("线程One") {
public void run() {
try {
// 需要获取到lock的锁才能进入下面的代码
lock.lockInterruptibly();
System.out.println(Thread.currentThread().getName()
+ "抢占到了锁!");
try {
// 竞争multiThreadTest()资源
multiThreadTest(Thread.currentThread());
// 休眠200毫秒
Thread.sleep(200);
}finally {
// 释放锁
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()
+ "在等待获取锁时,【被中断】了!!!");
}
};
}.start();
// 成员内部类,开启第二个线程
LockInterruptiblyTest.MyThread myThread =
new LockInterruptiblyTest().new MyThread(lock, "线程Two");
myThread.start();
// 在主线程中计时,过20毫秒,中断myThread线程
// 注:如果线程One率先抢占到了锁,那么至少会消耗200毫秒(因为我在线程One中sleep了200毫秒)才会释放锁
// 那么线程Two至少需要等待200毫秒;这里我们在其等待的第20毫秒左右中断该线程,那么线程Two中的
// .lockInterruptibly();方法会抛出InterruptedException异常,
// 进而被捕获,输出"线程Two在等待获取锁时,【被中断】了!!!"
long startTime = System.currentTimeMillis();
for (;;) {
if(System.currentTimeMillis() - startTime >= 20) {
myThread.interrupt();
break;
}
}
}
}
输出结果为:
注:此方法相当于等待时间无限久的.tryLock(long time, TimeUnit unit)方法。不过此方法无返回值,而
.tryLock(long time, TimeUnit unit)方法方法返回布尔值。
Condition简单介绍
/**
* Condition 将 Object 监视器方法(wait、notify 和 notifyAll)
* 分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,
* 为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了
* synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
*
* @DATE 2018年9月4日 下午11:09:06
*/
public interface Condition {
/**
* 当前线程进入等待状态,直到被.signal()通知运行 或者 被中断(中断时抛出异常)
*
* @throws InterruptedException
* @DATE 2018年9月4日 下午10:56:05
*/
void await() throws InterruptedException;
/**
* 当前线程进入等待状态,直到被通知,对中断不做响应;
*
* @DATE 2018年9月4日 下午10:58:12
*/
void awaitUninterruptibly();
/**
* 在await()的基础上增加了等待时间,返回值表示当前剩余的时间
* 即:如果在nanosTimeout之前被唤醒,返回值 = nanosTimeout - 实际消耗的时间
* 注:返回值 <= 0 则表示超时
*
* @DATE 2018年9月4日 下午10:59:23
*/
long awaitNanos(long nanosTimeout) throws InterruptedException;
/**
* 在await()基础上增加了 等待时间、时间单位 ;且如果在规定时间内被唤醒,那么返回true,否者返回false
*
* @DATE 2018年9月4日 下午11:01:02
*/
boolean await(long time, TimeUnit unit) throws InterruptedException;
/**
* 在await()的基础上增加了一个 “期限”时间;
* 注:在期限时间到来之前被唤醒返回true;如果到期限时间之前还没有被唤醒,那么返回false
*
* @DATE 2018年9月4日 下午11:02:33
*/
boolean awaitUntil(Date deadline) throws InterruptedException;
/**
* 唤醒 处于await等待中的线程
*
* @DATE 2018年9月4日 下午11:04:01
*/
void signal();
/**
* 唤醒 处于await等待中的线程
*
* @DATE 2018年9月4日 下午11:04:29
*/
void signalAll();
}
注:被唤醒的线程(不论是Object的notify、notifyAll唤醒的,还是Condition的signal、signalAll唤醒的),直接执行
等待方法(不论是Object的wait等待方法还是Condition的await等待方法)之后的逻辑,并不会从头开始走。
追注:Object唤醒Object的等待线程、Condition唤醒Condition的等待线程,别混淆了。
Condition之简单使用示例
场景说明:
甲和乙打赌,甲说可以在乙先跑39米的情况下,挥刀仍能伤着乙;乙不信,要比一下速度。于是请来裁判丁。
于是比赛规则(流程)就有了:
第一步:丁(裁判)倒计时5个数,数到0时,(通知)乙开始跑。
第二步:乙开始跑,跑到第39米时,(通知)甲可以挥刀了。
第三步:甲挥刀。
我们接下来就在代码里面进行说明
/*
* 场景说明:
* 甲和乙打赌,甲说可以在乙先跑39米的情况下,挥刀仍能伤着乙;乙不信,要比一下速度。于是请来裁判丁。
*
* 于是比赛规则(流程)就有了:
* 第一步:丁(裁判)倒计时5个数,数到0时,(通知)乙开始跑。
* 第二步:乙开始跑,跑到第39米时,(通知)甲可以挥刀了。
* 第三步:甲挥刀。
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* Condition简单测试
*
* @author JustryDeng
* @DATE 2018年9月4日 下午8:42:14
*/
public class ConditionTest {
/** 获取Lock实例 */
Lock lock = new ReentrantLock();
/** 丁(裁判)倒计时结束后,由false变为true; */
boolean gameStartFlag = false;
/** 乙跑了39米后,由false变为true; */
boolean swingFlag = false;
/** 对应jia中用到的Condition */
Condition jiaCondition = lock.newCondition();
/** 对应yi中用到的Condition */
Condition yiCondition = lock.newCondition();
/**
* 对应 --- 甲
*/
public void jia() {
lock.lock();
try {
if(!swingFlag) {// 如果乙还没跑够39米,那么继续等乙跑够
jiaCondition.await();
}
if(swingFlag) {
System.out.println(Thread.currentThread().getName() + ":我挥刀了!!!");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 对应 --- 乙
*/
public void yi() {
lock.lock();
try {
if(!gameStartFlag) { // 如果游戏还没开始,那么继续等
yiCondition.await();
}
System.out.println(Thread.currentThread().getName() + ":我开始跑了!!!");
Thread.sleep(2000);
System.out.println(Thread.currentThread().getName() + ":我已经跑了39米了!!!");
// 将 挥刀 标识符 状态改为true;这样 甲就知道可以 挥刀了
swingFlag = true;
// 由于要 唤醒 甲; 解铃还须系铃人,用jiaCondition.await()的,这里就用jiaCondition来唤醒
jiaCondition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 对应 --- 丁
*/
public void ding() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + "(裁判):倒计时开始!!!");
for (int i = 5; i >= 0; i--) {
System.out.println(Thread.currentThread().getName() + "(裁判):" + i);
}
System.out.println(Thread.currentThread().getName() + "(裁判):比赛开始!!!");
// 将 开始 标识符 状态改为true;这样乙就知道可以开始跑了
gameStartFlag = true;
// 由于要 唤醒 乙; 解铃还须系铃人,用yiCondition.await()的,这里就用yiCondition来唤醒
yiCondition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
/**
* 入口
*/
public static void main(String[] args) {
ConditionTest ct = new ConditionTest();
new Thread("甲") {
@Override
public void run() {
ct.jia();
}
}.start();
new Thread("乙") {
@Override
public void run() {
ct.yi();
}
}.start();
new Thread("丁") {
@Override
public void run() {
ct.ding();
}
}.start();
}
}
输出结果为
注:被唤醒的线程(不论是Object的notify、notifyAll唤醒的,还是Condition的signal、signalAll唤醒的),直接执行
等待方法(不论是Object的wait等待方法还是Condition的await等待方法)之后的逻辑,并不会从头开始走。
追注:Object唤醒Object的等待线程、Condition唤醒Condition的等待线程,别混淆了。
ReadWriteLock读写锁
注:Lock与ReadWriteLock都是锁接口,不过ReadWriteLock将整个锁再细分成了读锁、写锁。
我们知道:一个资源,如果只是读的话,是对该资源本身没什么影响的。读锁就是基于此考虑,被设计出来的。
假设当前线程竞争到了某个锁实例的读锁,另一个想竞争这个锁对象的读锁时,需要等待(即,如果当下是一个写锁线程获得
了锁时,有再多的读锁也得等着。)如:
/**
* 读写锁 ---> 已经获得锁的线程 与 其它要获得同一个读锁的线程,可以同时进行(无需等待)
*
* @author JustryDeng
* @DATE 2018年9月4日 下午7:36:59
*/
public class ReadWriteLockTestReadAndRead {
public static void main(String[] args) {
// ReentrantReadWriteLock是ReadWriteLock接口的主要实现之一;不仅提供了基本的实现,还额外丰富了几种方法
ReentrantReadWriteLock rrwl = new ReentrantReadWriteLock();
// 匿名内部类 开启第一个线程
new Thread("线程One") {
public void run() {
rrwl.readLock().lock();
try {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "第" + i+ "次读");
}
} finally {
rrwl.readLock().unlock();
}
};
}.start();
// 匿名内部类 开启第二个线程
new Thread("线程Two") {
public void run() {
rrwl.readLock().lock();
try {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "第" + i+ "次读");
}
} finally {
rrwl.readLock().unlock();
}
};
}.start();
}
}
输出结果为:
假设当前线程竞争到了某个锁实例的写锁,另一个想竞争这个锁对象的写锁时,需要等待,如:
输出结果为:
假设当前线程竞争到了某个锁实例的读锁(写锁),另一个想竞争这个锁对象的写锁(读锁)时,需要等待,如:
输出结果为: