Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例
Posted 张起灵-小哥
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例相关的知识,希望对你有一定的参考价值。
文章目录:
Demo2(int getHoldCount() 返回当前线程调用 lock()方法的次数)
Demo3(int getQueueLength() 返回正等待获得锁的线程预估数)
Demo4(int getWaitQueueLength(Condition condition)返回与 Condition 条件相关的等待的线程预估数)
Demo6(boolean hasWaiters(Condition condition)查询是否有线程正在等待指定的 Condition 条件)
Demo7(boolean isFair() 判断是否为公平锁 & boolean isHeldByCurrentThread() 判断当前线程是否持有该锁)
Demo8(boolean isLocked() 查询当前锁是否被线程持有)
写在前面
首先说一下有关公平锁与非公平锁:
大多数情况下,锁的申请都是非公平的。如果线程1与线程2都在请求锁 A,当锁 A 可用时,系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性。
公平的锁会按照时间先后顺序,保证先到先得,公平锁的这一特点不会出现线程饥饿现象。
synchronized 内部锁就是非公平的。
ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递true 可以把该锁设置为公平锁。公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低。因此默认情况下锁是非公平的,不是特别的需求,一般不使用公平锁。
下面是ReentrantLock类中的一些常用方法:
- int getHoldCount() 返回当前线程调用 lock()方法的次数
- int getQueueLength() 返回正等待获得锁的线程预估数
- int getWaitQueueLength(Condition condition) 返回与 Condition 条件相关的等待的线程预估数
- boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁
- boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁
- boolean hasWaiters(Condition condition) 查询是否有线程正在等待指定的 Condition 条件
- boolean isFair() 判断是否为公平锁
- boolean isHeldByCurrentThread() 判断当前线程是否持有该锁
- boolean isLocked() 查询当前锁是否被线程持有;
Demo1(公平锁与非公平锁)
package com.szh.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* 公平锁与非公平锁
* 运行程序
* 1) 如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
* 2) 如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能, 保证了公平性
*/
public class Test01 {
//默认是非公平锁
//static ReentrantLock lock=new ReentrantLock();
//公平锁
static ReentrantLock lock=new ReentrantLock(true);
public static void main(String[] args) {
Runnable r=new Runnable() {
@Override
public void run() {
while (true) {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得了锁对象");
}finally {
lock.unlock();
}
}
}
};
for (int i = 0; i < 5; i++) {
new Thread(r).start();
}
}
}
如果是:static ReentrantLock lock=new ReentrantLock(); 这默认就是非公平锁,可以看到多线程执行之后,系统更倾向于让一个之前获得锁的线程再次获得锁,这显然就体现了非公平性。
如果是:static ReentrantLock lock=new ReentrantLock(true); 这就是公平锁, 可以看到系统会按照线程等待的先后时间顺序,有序的为每个线程分配锁对象,这个顺序一直就是 1-0-4-2-3。
Demo2(int getHoldCount() 返回当前线程调用 lock()方法的次数)
package com.szh.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getHoldCount() 返回当前线程调用 lock()方法的次数
*/
public class Test02 {
static ReentrantLock lock=new ReentrantLock(); //定义锁对象
public static void method1() {
try {
lock.lock();
//打印当前线程嗲用lock()方法的次数
System.out.println(Thread.currentThread().getName() + " ---> method1 hold count: " + lock.getHoldCount());
//调用method2方法,因为ReentrantLock支持锁的可重入性,所以这里可以再次获得锁
method2();
} finally {
lock.unlock();
}
}
public static void method2() {
try {
lock.lock();
//打印当前线程嗲用lock()方法的次数
System.out.println(Thread.currentThread().getName() + " ---> method2 hold count: " + lock.getHoldCount());
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
//main主线程调用method1方法
method1();
}
}
Demo3(int getQueueLength() 返回正等待获得锁的线程预估数)
package com.szh.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getQueueLength() 返回正等待获得锁的线程预估数
*/
public class Test03 {
static ReentrantLock lock=new ReentrantLock(); //定义锁对象
public static void method1() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "获得锁,执行方法,估计等待获得锁的线程数:"
+ lock.getQueueLength());
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r=new Runnable() {
@Override
public void run() {
method1();
}
};
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
}
}
这个方法返回的是正在等待获得锁的线程预估数。当Thread-0第一个抢到CPU执行权时,进来获得lock锁对象,此时只有它一个线程在执行,所以还没有等待的线程。当它执行到sleep方法时,转入睡眠等待状态,但不会释放lock锁对象。这个时候,剩下的9个子线程都来了,它们肯定要等待啊(因为lock锁对象被Thread-0占有了),那么这个时候,假如Thread-1在等待队列的最前面,那么当Thread-0醒了之后释放锁对象,Thread-1获得了锁对象,此时还在等待的子线程就是(10-Thread0-Thread1=8),是这样的。后面的那几个都是同样的理解,我就不再叙述了。
Demo4(int getWaitQueueLength(Condition condition)返回与 Condition 条件相关的等待的线程预估数)
package com.szh.lock.method;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* int getWaitQueueLength(Condition condition)
* 返回与 Condition 条件相关的等待的线程预估数
*/
public class Test04 {
static class Service {
private ReentrantLock lock=new ReentrantLock(); //定义锁对象
private Condition condition=lock.newCondition(); //获得Condition对象
public void waitMethod() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + "进入等待前,现在该condition条件上等待的线程预估数:"
+ lock.getWaitQueueLength(condition));
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void notifyMethod() {
try {
lock.lock();
condition.signalAll();
System.out.println("唤醒所有的等待后,该condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Service s=new Service();
Runnable r=new Runnable() {
@Override
public void run() {
s.waitMethod();
}
};
//开启10个子线程
for (int i = 0; i < 10; i++) {
new Thread(r).start();
}
Thread.sleep(1000);
s.notifyMethod(); //1秒钟之后,唤醒所有的等待线程
}
}
这个案例说的是,在当前Condition对象的条件上,等待的线程预估数。当Thread-0执行,此时还没有线程执行 condition.await() 进入等待状态,所以线程预估数是0,当Thread-0通过condition对象调用await方法之后,他进入了等待状态,同时释放lock锁对象;这个时候,Thread-2进来执行,那么它就可以知道,在当前Condition对象的条件上,等待的线程预估数为1(Thread-0)。那么后面几个子线程进入等待都是同样的理解。最后main主线程中通过调用唤醒方法中的condition.signAll()之后,唤醒了所有还在await的子线程,那么此时在当前Condition对象的条件上,等待的线程预估数就是0了。
Demo5(boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁 & boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁)
package com.szh.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁
* boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁
*/
public class Test05 {
static ReentrantLock lock=new ReentrantLock(); //定义锁对象
public static void waitMethod() {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " 获得了锁");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " 释放了锁对象");
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
Runnable r=new Runnable() {
@Override
public void run() {
waitMethod();
}
};
Thread[] threads=new Thread[5]; //定义线程数组
//为每个线程赋值,并启动线程
for (int i = 0; i < threads.length; i++) {
threads[i]=new Thread(r);
threads[i].setName("thread ---> " + i);
threads[i].start();
}
Thread.sleep(1000 * 3);
//判断数组中的每个线程对象是否还在等待获得锁
for (int i = 0; i < threads.length; i++) {
System.out.println(lock.hasQueuedThread(threads[i]));
}
Thread.sleep(1000 * 2);
//再次判断是否还有线程正在等待获得锁
System.out.println(lock.hasQueuedThreads());
}
}
首先Thread-0获得了CPU执行权,它执行自己的run方法,首先获得了lock锁对象,sleep睡眠1秒(不会释放锁对象)之后醒来,释放lock锁对象;之后Thread-1与Thread-0执行过程一样,不再多说;再然后Thread-2获得了CPU执行权,它执行自己的run方法,获得了lock锁对象,sleep睡眠1秒(不会释放锁对象)。这里的for循环一共创建了5个子线程,当前执行到了Thread-2,后面还有两个线程都在等待获得lock锁对象呢。这个时候,因为main主线程睡眠了3秒,而上面执行的前三个线程的睡眠加起来也到了3秒,所以这个时候会执行到main主线程中的最后一个for循环,这里就会判断每个子线程是否还在等待获得锁,是返回true、不是返回false。显然Thread0、1都已经获得并且释放了lock锁对象,Thread-2也已经获得了锁对象,它们三个肯定不会再等待获得锁了,所以前三个返回了false,而后两个还在等待获得锁呢,自然就返回了true。for循环执行完,Thread-2也醒了,释放锁对象,之后Thread4执行获得锁(睡了1秒)释放锁,然后Thread-3执行获得锁(睡了1秒)还没释放锁的时候,main主线程中也睡眠了2秒,此时main主线程醒了,所以最后再判断是否还有线程正在等待获得锁,此时5个子线程全部获得了锁,所以没有线程在等待获得锁了。最后Thread-3醒来释放锁。
Demo6(boolean hasWaiters(Condition condition)查询是否有线程正在等待指定的 Condition 条件)
package com.szh.lock.method;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean hasWaiters(Condition condition)
* 查询是否有线程正在等待指定的 Condition 条件
*/
public class Test06 {
static ReentrantLock lock=new ReentrantLock(); //创建锁对象
static Condition condition=lock.newCondition(); //获得Condition对象
static void method() {
try {
lock.lock();
System.out.println("是否有线程正在等待当前Condition条件? " + lock.hasWaiters(condition)
+ " --- waitqueuelength: " + lock.getWaitQueueLength(condition));
System.out.println(Thread.currentThread().getName() + " waiting......");
condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS); //超时会自动唤醒
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + " unlock......");
lock.unlock();
}
}
public static void main(String[] args) {
Runnable r=new Runnable() {
@Override
public void run() {
method();
}
};
for (int i = 0; i < 5; i++) {
new Thread(r).start();
}
}
}
最开始Thread-0抢到CPU执行权开始执行,此时只有它一个线程,而且也没执行到condition对象的某个方法,所以这里没有线程正在等待当前Condition条件(false),与 Condition 条件相关的等待的线程预估数为0。之后Thread-0执行到await方法进入等待状态,同时释放锁对象。
然后Thread-2来了,此时肯定有一个Thread-0在condition.await(XXX)等待啊,所以这里是true,那么与 Condition 条件相关的等待的线程预估数肯定就是1。然后Thread-2执行到await方法进入等待状态,同时释放锁对象。
后面几个子线程都是上述道理,不再多说了。此时5个子线程都等待了,根据 condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS); //超时会自动唤醒 ,所以最后时间超出了1秒,所有的子线程都会被自动唤醒的,最后依次释放锁对象。
Demo7(boolean isFair() 判断是否为公平锁 & boolean isHeldByCurrentThread() 判断当前线程是否持有该锁)
package com.szh.lock.method;
import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isFair() 判断是否为公平锁
* boolean isHeldByCurrentThread() 判断当前线程是否持有该锁
*/
public class Test07 {
static class Service {
private ReentrantLock lock=new ReentrantLock();
public Service(boolean isFair) {
this.lock=new ReentrantLock(isFair);
}
public void method() {
try {
System.out.println("是否是公平锁? " + lock.isFair() + " ---> " + Thread.currentThread().getName()
+ "调用lock方法之前是否持有锁? " + lock.isHeldByCurrentThread());
lock.lock();
System.out.println(Thread.currentThread().getName() + "调用lock方法后是否持有锁? " + lock.isHeldByCurrentThread());
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
Runnable r=new Runnable() {
@Override
public void run() {
int num=new Random().nextInt();
new Service(num % 2 == 0 ? true : false).method();
}
};
for (int i = 0; i < 5; i++) {
new Thread(r).start();
}
}
}
这个案例说白了,就是下面这两行代码:
//默认是非公平锁 static ReentrantLock lock=new ReentrantLock(); //公平锁 static ReentrantLock lock=new ReentrantLock(true);
Demo8(boolean isLocked() 查询当前锁是否被线程持有)
package com.szh.lock.method;
import java.util.concurrent.locks.ReentrantLock;
/**
* boolean isLocked() 查询当前锁是否被线程持有
*/
public class Test08 {
static ReentrantLock lock=new ReentrantLock();
static void method() {
try {
System.out.println("before lock... " + lock.isLocked());
lock.lock();
System.out.println("after lock... " + lock.isLocked());
Thread.sleep(1000 * 2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
method();
}
}).start();
}
}
以上是关于Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例的主要内容,如果未能解决你的问题,请参考以下文章
Java——多线程高并发系列之创建多线程的三种方式(ThreadRunnableCallable)
Java——多线程高并发系列之创建多线程的三种方式(ThreadRunnableCallable)
Java——多线程高并发系列之线程池(Executor)的理解与使用