文章目录
- 使用 ReentrantLock 类
- 使用 Condition 实现等待/通知
- 使用多个 Condition 实现通知部分线程
- 使用多个 Condition 对象实现通知部分线程
- 公平锁与非公平锁
- 方法 getHoldCount()、getQueueLength() 和 getWaitQueueLength() 的测试
- 方法 isFair()、isHeldByCurrentThread() 和 isLocked()
- 方法 lockInterruptibly()、tryLock() 和 tryLock(long timeout, TimeUnit unit)
- 使用 ReentrantReadWriteLock 类
使用 ReentrantLock 类
使用 Condition 实现等待/通知
Condition 类是 JDK5 中出现的技术,使用它有更好的灵活性,比如可以实现多路通知功能。也就是在一个 Lock 对象里面可以创建多个 Condition 实例,线程对象可以注册在指定的 Condition 中,从而可以有选择地进行线程通知,在调度上更加灵活。
在使用 notify() 方法进行线程通知时,被通知的线程是由 JVM 随机选择的。但使用 ReentrantLock 结合 Condition 类可以实现前面介绍过的选择性通知,这是一个非常重要的功能。
而 synchronized 就相当于整个 Lock 对象中只有一个单一的 Condition 对象,所有线程都会注册在它一个对象身上,线程开始 notifyAll() 时,需要通知所有的 WAITING 线程,没有选择权。
public class MyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void await(){
try {
lock.lock();
System.out.println("await 时间为:" + System.currentTimeMillis());
condition.await();
System.out.println("await end");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void single(){
try {
lock.lock();
System.out.println("single 的时间为:" + System.currentTimeMillis());
condition.signal();
} finally {
lock.unlock();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.await();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
MyService myService = new MyService();
ThreadA threadA = new ThreadA(myService);
threadA.start();
Thread.sleep(3000);
myService.single();
}
}
Object 类中的 wait() 方法相当于 Condition 类中的 await() 方法;Object 类中的 notify() 方法相当于 Condition 类中的 single() 方法;Object 类中的 notifyAll() 方法相当于 Condition 类中的 singleAll() 方法。
使用多个 Condition 实现通知部分线程
前面的章节使用一个 Condition 对象来实现等待/通知模式,其实 Condition 对象也可以创建多个。那么一个 Condition 对象和多个 Condition 对象有什么区别呢?
public class MyService {
private Lock lock = new ReentrantLock();
public Condition condition = lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println("begin awaitA time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
condition.await();
System.out.println("end awaitA time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println("begin awaitB time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
condition.await();
System.out.println("end awaitB time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void singleAll(){
try {
lock.lock();
System.out.println("singleAll time = " + System.currentTimeMillis() + " ThreadName : " + Thread.currentThread().getName());
condition.signalAll();
} finally {
lock.unlock();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
public class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
MyService myService = new MyService();
ThreadA a = new ThreadA(myService);
a.setName("A");
a.start();
ThreadB b = new ThreadB(myService);
b.setName("B");
b.start();
Thread.sleep(3000);
myService.singleAll();
}
}
运行结果:
begin awaitA time = 1543846773351 ThreadName : A
begin awaitB time = 1543846773352 ThreadName : B
singleAll time = 1543846776350 ThreadName : main
end awaitA time = 1543846776350 ThreadName : A
end awaitB time = 1543846776350 ThreadName : B
从结果上看,线程 A 和 B 都被唤醒了,如果像单独唤醒部分线程该怎么做呢?这时候就有必要使用多个 Condition 对象了,也就是 Condition 对象可以唤醒部分指定线程。
使用多个 Condition 对象实现通知部分线程
public class MyService {
private Lock lock = new ReentrantLock();
public Condition conditionA = lock.newCondition();
public Condition conditionB = lock.newCondition();
public void awaitA(){
try {
lock.lock();
System.out.println("begin awaitA time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
conditionA.await();
System.out.println("end awaitA time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void awaitB(){
try {
lock.lock();
System.out.println("begin awaitB time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
conditionB.await();
System.out.println("end awaitB time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void singleAll_A(){
try {
lock.lock();
System.out.println("singleAll_A time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
conditionA.signalAll();
} finally {
lock.unlock();
}
}
public void singleAll_B(){
try {
lock.lock();
System.out.println("singleAll_B time = " + System.currentTimeMillis() + "ThreadName = " + Thread.currentThread().getName());
conditionB.signalAll();
} finally {
lock.unlock();
}
}
}
public class ThreadA extends Thread {
private MyService service;
public ThreadA(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitA();
}
}
public class ThreadB extends Thread {
private MyService service;
public ThreadB(MyService service) {
super();
this.service = service;
}
@Override
public void run() {
service.awaitB();
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
MyService service = new MyService();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
Thread.sleep(3000);
service.singleAll_A();
}
}
运行结果:
begin awaitA time = 1543848404904ThreadName = A
begin awaitB time = 1543848404904ThreadName = B
singleAll_A time = 1543848407902ThreadName = main
end awaitA time = 1543848407902ThreadName = A
只有 A 线程被唤醒了。通过此实验可知,使用 ReentrantLock 对象可以唤醒指定种类的线程,这是控制部分线程行为的方便方式。
公平锁与非公平锁
锁 Lock 分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的。而非公平锁是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先得到锁,这种方式可能造成某些线程一直拿不到锁,结果也就不公平了。
公平锁
public class Service {
private ReentrantLock lock;
public Service(boolean isFair) {
super();
lock = new ReentrantLock(isFair);
}
public void serviceMethod(){
try {
lock.lock();
System.out.println("ThreadName = " + Thread.currentThread().getName() + "获得锁定");
} finally {
lock.unlock();
}
}
}
public class RunFair {
public static void main(String[] args) throws InterruptedException{
final Service service = new Service(true);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("*线程" + Thread.currentThread().getName() + "运行了");
service.serviceMethod();
}
};
Thread[] threadArray = new Thread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i = 0; i < 10; i++) {
threadArray[i].start();
}
}
}
运行结果:
*线程Thread-0运行了
ThreadName = Thread-0获得锁定
*线程Thread-1运行了
ThreadName = Thread-1获得锁定
*线程Thread-2运行了
ThreadName = Thread-2获得锁定
*线程Thread-4运行了
ThreadName = Thread-4获得锁定
*线程Thread-6运行了
ThreadName = Thread-6获得锁定
*线程Thread-8运行了
ThreadName = Thread-8获得锁定
*线程Thread-3运行了
ThreadName = Thread-3获得锁定
*线程Thread-7运行了
ThreadName = Thread-7获得锁定
*线程Thread-5运行了
ThreadName = Thread-5获得锁定
*线程Thread-9运行了
ThreadName = Thread-9获得锁定
使用了公平锁的到的结果说明先运行的程序先的到锁。
非公平锁
public class RunFair {
public static void main(String[] args) throws InterruptedException{
final Service service = new Service(false);
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("*线程" + Thread.currentThread().getName() + "运行了");
service.serviceMethod();
}
};
Thread[] threadArray = new Thread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i = 0; i < 10; i++) {
threadArray[i].start();
}
}
}
运行结果:
*线程Thread-0运行了
ThreadName = Thread-0获得锁定
*线程Thread-1运行了
ThreadName = Thread-1获得锁定
*线程Thread-2运行了
ThreadName = Thread-2获得锁定
*线程Thread-3运行了
*线程Thread-4运行了
ThreadName = Thread-3获得锁定
*线程Thread-5运行了
ThreadName = Thread-4获得锁定
*线程Thread-7运行了
ThreadName = Thread-7获得锁定
*线程Thread-8运行了
ThreadName = Thread-5获得锁定
ThreadName = Thread-8获得锁定
*线程Thread-6运行了
ThreadName = Thread-6获得锁定
*线程Thread-9运行了
ThreadName = Thread-9获得锁定
使用非公平锁的运行结果显示先运行的线程不一定先得到锁,谁得到锁是随机的。比如有可能出现这种情况:线程 A 获得 CPU 时间,执行了 run 方法的第一行代码,然后线程 B 取得 CPU 时间,并且得到了锁,执行了 run 方法中所有代码。
方法 getHoldCount()、getQueueLength() 和 getWaitQueueLength() 的测试
方法 getHoldCount() 的作用是查询当前线程保持此锁定的个数,也就是调用 lock() 方法的次数。
public class Service {
private ReentrantLock lock = new ReentrantLock();
public void serviceMethod1(){
lock.lock();
System.out.println("serviceMethod1 getHoldCount = " + lock.getHoldCount());
serviceMethod2();
}
public void serviceMethod2(){
try {
lock.lock();
System.out.println("serviceMethod2 getHoldCount = " + lock.getHoldCount());
} finally {
lock.unlock();
}
}
}
public class Run {
public static void main(String[] args) {
Service service = new Service();
service.serviceMethod1();
}
}
运行结果:
serviceMethod1 getHoldCount = 1
serviceMethod2 getHoldCount = 2
方法 getQueueLength() 的作用是返回正在等待获取此锁定的线程的估计数,比如有 5 个线程,1 个线程首先执行 await() 方法,那么调用 getQueueLength() 方法返回值是 4,说明有 4 个线程同时在等待 lock 的释放。
public class Service {
public ReentrantLock lock = new ReentrantLock();
public void serviceMethod1(){
try {
lock.lock();
System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public class Run {
public static void main(String[] args) throws InterruptedException{
final Service service = new Service();
Runnable runnable = new Runnable() {
@Override
public void run() {
service.serviceMethod1();
}
};
Thread[] threadArray = new Thread[10];
for (int i = 0; i < 10; i++) {
threadArray[i] = new Thread(runnable);
}
for (int i = 0; i < 10; i++) {
threadArray[i].start();
}
Thread.sleep(2000);
System.out.println("有线程数:" + service.lock.getQueueLength() + "在等待获取锁");
}
}
运行结果:
ThreadName = Thread-0进入方法!
有线程数:9在等待获取锁
方法 getWaitQueueLength(Condition condition) 的作用是返回等待与此锁定相关的给定条件 Condition 的线程估计数,比如有 5 个线程,每个线程都执行了同一个 condition 对象的 await() 方法,则调用 getWaitQueueLength(Condition condition) 方法返回的 int 值是 5。
方法 isFair()、isHeldByCurrentThread() 和 isLocked()
方法 boolean isFair() 的作用是判断是不是公平锁。
方法 boolean isHeldByCurrentThread() 的作用是查询当前线程是否保持此锁定。
方法 isLocked() 的作用是查询此锁定是否由任意线程保持。
方法 lockInterruptibly()、tryLock() 和 tryLock(long timeout, TimeUnit unit)
方法 lockInterruptibly() 的作用是:如果当前线程未被中断,则获取锁定;如果已被中断则抛出异常。
方法 boolean tryLock() 的作用是,仅在调用时锁定未被另另一个线程保持的情况下,才获取该锁定。
使用 ReentrantReadWriteLock 类
类 ReentrantLock 具有完全互斥排他的效果,即同一时刻只有一个线程执行 ReentrantLock.lock() 方法后边的任务。这样做,虽然保证了实例变量的线程安全性,但效率却非常低下。在某些不需要操作实例变量的方法中,完全可以使用读写锁 ReentrantReadWriteLock 来提升该方法的代码运行速度。
读写锁表示也有两个锁,一个是读操作相关的锁,称为共享锁;另一个是写操作相关的锁,称为排他锁。也就是多个读锁时间不互斥,读锁与写锁互斥,写锁与写锁互斥。在没有线程 Thread 进行写入操作时,进行读操作的多个 Thread 都可以获取读锁,而进行写入操作的 Thread 只有在获取写操作后才能进行写入操作。即多个 Thread 可以同时进行读取操作,但是同一时刻只允许 一个 Thread 进行写入操作。
类 ReentrantReadWriteLock 的使用:读读共享
public class Service {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try {
try {
lock.readLock().lock();
System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(1000);
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class Test {
public static void main(String[] args) {
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
ThreadB b = new ThreadB(service);
b.setName("b");
a.start();
b.start();
}
}
运行结果:
获取读锁a 1544430449341
获取读锁b 1544430449341
从运行结果来看,两个线程几乎同时进入 lock() 方法后边的代码。说明在此使用 lock.readLock() 读锁可以提高程序运行效率,允许多个线程同时执行 lock() 方法后边的代码。
类 ReentrantReadWriteLock 的使用:写写互斥
public class Service {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try {
try {
lock.readLock().lock();
System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(5000);
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
运行结果:
获取写锁a 1544431488159
获取写锁b 1544431493161
类 ReentrantReadWriteLock 的使用:读写互斥
public class Service {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public void read(){
try {
try {
lock.readLock().lock();
System.out.println("获取读锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(10000);
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void write(){
try {
try {
lock.writeLock().lock();
System.out.println("获取写锁" + Thread.currentThread().getName() + " " + System.currentTimeMillis());
Thread.sleep(5000);
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadA extends Thread {
private Service service;
public ThreadA(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.read();
}
}
public class ThreadB extends Thread {
private Service service;
public ThreadB(Service service) {
super();
this.service = service;
}
@Override
public void run() {
service.write();
}
}
public class Test {
public static void main(String[] args) throws InterruptedException{
Service service = new Service();
ThreadA a = new ThreadA(service);
a.setName("a");
a.start();
Thread.sleep(2000);//为了保证先让线程a执行,因为为了先获取读锁,看看读的时候能不能获取写锁
ThreadB b = new ThreadB(service);
b.setName("b");
b.start();
}
}
运行结果:
获取读锁a 1544432402668
获取写锁b 1544432412669
实验结果证明,在执行读锁后边的代码的时候,是不能进行写锁后边的操作的。说明读写是互斥的,当然写读也是互斥的。
在本章完全可以用 Lock 对象将 synchronized 关键字替换掉,而且具有的独特功能是 synchronized 是不具有的。在学习并发时,Lock 是 synchronized 关键字的进阶,掌握 Lock 有助于学习并发包中源代码的实现原理。