一、为什么要用Reentrantlock
与synchronized相比:
- 相同点:都支持可重入
- 不同点:1.可设置超时时间;2、支持多个条件变量;3、可中断;4、可设置为公平锁
二、快速使用
1、验证可重入
public class ReentrantLockTest {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
//可重入
try {
lock.lock();
System.out.println("进入了main");
m1();
} finally {
lock.unlock();
}
}
public static void m1(){
try {
lock.lock();
System.out.println("进入了m1");
m2();
} finally {
lock.unlock();
}
}
public static void m2(){
try {
lock.lock();
System.out.println("进入了m2");
} finally {
lock.unlock();
}
}
}
结果:
2、验证锁超时
@Slf4j
public class ReentrantLockTest2_tryLock {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
//可超时
Thread t1 = new Thread(() -> {
try {
if (!lock.tryLock(1, TimeUnit.SECONDS)){
log.debug("等了1秒没获得锁,返回");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try{
log.debug("拿到锁了");
}finally{
lock.unlock();
}
}, "reentrantlock可超时测试线程");
lock.lock();
log.debug("获取到锁了");
t1.start();
try {
Thread.sleep(2000);
} catch(InterruptedException e){
e.printStackTrace();
} finally
{
lock.unlock();
}
}
}
结果:
3、验证多个条件变量
@Slf4j
public class ReentrantLockTest3_condition {
private static ReentrantLock room = new ReentrantLock();
//定义两个条件
private static Condition waitCarQueue=room.newCondition();
private static Condition waitTrainQueue=room.newCondition();
//定义两个标志
private static boolean isCar=false;
private static boolean isTrain=false;
public static void main(String[] args) {
//等汽车线程
new Thread(()->{
try {
room.lock();
//汽车来了没
log.debug("汽车来了没?",isCar);
while (!isCar){
//车没来,在汽车候车区等待
try {
waitCarQueue.await();
System.out.println(isCar);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(isCar?"汽车来了":"汽车还没来");
} finally {
room.unlock();
}
},"等汽车线程").start();
//等火车线程
new Thread(()->{
try {
room.lock();
//火车来了没
log.debug("火车来了没?",isTrain);
while (!isTrain){
//车没来,在火车候车区等待
try {
waitTrainQueue.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
log.debug(isTrain?"火车来了":"火车还没来");
} finally {
room.unlock();
}
},"等火车线程").start();
try {
Thread.sleep(1000);
log.debug("等一秒后,汽车来了");
new Thread(()->{
room.lock();
try {
isCar=true;
waitCarQueue.signal();
} finally {
room.unlock();
}
},"汽车线程").start();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
Thread.sleep(1000);
log.debug("又等一秒后,火车来了");
new Thread(()->{
room.lock();
try {
isTrain=true;
waitTrainQueue.signal();
} finally {
room.unlock();
}
},"火车线程").start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
4、验证可中断
@Slf4j
public class ReentrantLockTest2_Interrupt {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
//可打断
Thread t1 = new Thread(() -> {
try {
log.debug("尝试获得锁。。");
lock.lockInterruptibly();
} catch (InterruptedException e) {
log.error("中断异常:",e);
log.error("锁被中断了,不等了,返回");
return;
}
try{
log.debug("拿到锁了");
}finally{
lock.unlock();
}
}, "reentrantlock可打断测试线程");
lock.lock();
log.debug("获取到锁了");
t1.start();
try {
Thread.sleep(1000);
t1.interrupt();
log.debug("执行中断");
} catch(InterruptedException e){
e.printStackTrace();
} finally
{
lock.unlock();
}
}
}
结果:
三、基于Reentrantlock和原生Queue实现一个线程池中的任务队列
首先看下Java中的Queue的继承结构:
show the code:
@Slf4j
class BlockingQueue<T>{
//1.任务队列
private Deque<T> queue=new ArrayDeque<T>();
//2.锁
private ReentrantLock lock =new ReentrantLock();
//3.生产者条件变量:生产者生产时,队列有可能full
private Condition fullWaitSet=lock.newCondition();
//4.消费者条件变量:消费者消费时,队列有可能为empty
private Condition emptyWaitSet=lock.newCondition();
//5.容量
private int capacity;
public BlockingQueue(int capacity) {
this.capacity = capacity;
}
//阻塞获取
public T take(){
lock.lock();
try {
while(queue.isEmpty()){
//如果队列为空,则到消费者条件里等待
try {
log.debug("等待生产者生产");
emptyWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果不为空,则取出第一个,并唤醒生产者条件
T t = queue.removeFirst();
fullWaitSet.signal();
return t;
} finally {
lock.unlock();
}
}
//阻塞添加
public void put(T task){
lock.lock();
try {
while(queue.size()==capacity){
//如果队列满了,则到生产者条件里等待
try {
log.debug("等待加入到任务队列{}",task);
fullWaitSet.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//如果队列没满,则把元素加到队列尾,并唤醒消费者条件
log.debug("加入到任务队列{}",task);
queue.addLast(task);
emptyWaitSet.signal();
} finally {
lock.unlock();
}
}
}
四、线程池的使用与原理
1、快速使用线程池
1.1 先实现ThreadFactory类来配置创建线程的规则(如线程命名)
public class MyThreadFactory implements ThreadFactory {
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public MyThreadFactory(String name) {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
if (null == name || name.isEmpty()) {
name = "pool";
}
namePrefix = name + "-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
1.2 正式使用ThreadPoolExecutor
@Slf4j
public class ThreadPoolTest {
public static void main(String[] args) {
MyThreadFactory myThreadFactory = new MyThreadFactory("自定义-");
//手动创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(2,//核心线程数
4,//最大线程数
10,//线程存活时间
TimeUnit.SECONDS,//存活时间的时间单位
new LinkedBlockingDeque<>(5),//阻塞队列
myThreadFactory,
new ThreadPoolExecutor.CallerRunsPolicy());//拒绝策略
for (int i = 0; i < 4; i++) {
int finalI=i;
executor.submit(()->{
log.debug("任务一:执行目标"+finalI);
});
executor.submit(()->{
log.debug("任务二:执行目标"+finalI);
});
}
//终止线程
executor.shutdown();
}
}
结果:
2、ThreadPoolExecutor线程池执行流程
备注:图片来自美团技术团队