目录
1.Callable接口
1.1简介
1.2代码演示
1.3Runnable与Callable的区别
2.ReentrantLock
2.1ReentrantLock的常用方法
2.2ReentrantLock的代码演示
2.3ReentrantLock和synchronized的区别
3.Semaphore信号量
3.1概念
3.2代码演示
4.CountDownLatch
4.1概念
4.2代码演示
JUC是java.util.concurrent包的简称,JDK1.5之后对多线程的一种实现,这个包下放的类都和多线程有关,提供了很多工具类。
1.Callable接口
1.1简介
Callable是一个interface。相当于把线程封装了一个“返回值”,方便程序员借助多线程的方式计算得出结果。
Callable是创建线程的一种的方式,与Runnable类似。
当你的任务需要返回值时,用Callable比较好。
1.2代码演示
先定义一个线程的任务(从1加到10的结果)
public static void main(String[] args) {
//先定义一个线程的任务
Callable<Integer>callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 10; i++) {
sum+=i;
}
return sum;
}
};
}
使用FutureTask类来创建一个对象,这个对象持有callable。执行任务并获取结果。
(FutureTask是Runnable的一个实现类,所以可以传入Thread的构造方法中)
//通过FutureTask类来创建一个对象,这个对象持有callable
FutureTask<Integer>futureTask=new FutureTask<>(callable);
//创建线程并指定任务
Thread thread=new Thread(futureTask);
//让线程执行定义好的任务
thread.start();
//获取线程执行的结果
Integer result=futureTask.get();
//打印结果
System.out.println(result);
实现结果:
1.3Runnable与Callable的区别
如上图所示:
①Callable实现的是Call方法,Runnable实现的是Run方法
②Callable可以返回一个结果,Runnable不能返回结果
③Callable要配合FutureTask一起使用
④Callable可以抛出异常,Runnable不可以
2.ReentrantLock
可重入互斥锁,和synchronized定位类似,都是用来实现互斥效果,保证线程安全。是基于CAS实现的一个纯用户态的锁。
2.1ReentrantLock的常用方法
常用方法:
lock():加锁,如果获取不到锁就死等
trylock():尝试加锁
unlock():解锁
常用方法的使用代码演示:
public static void main(String[] args) throws InterruptedException {
//创建一个ReentrantLock锁对象
ReentrantLock reentrantLock=new ReentrantLock();
//加锁
reentrantLock.lock();
//尝试加锁,死等
reentrantLock.tryLock();
//尝试加锁,有超时时间
reentrantLock.tryLock(1, TimeUnit.SECONDS);
//释放锁
reentrantLock.unlock();
}
2.2ReentrantLock的代码演示
模拟业务中出现异常情况时,如何释放锁:
//模拟业务中如果出现异常情况,如何释放锁
public static void Demo02() throws Exception {
//创建一个ReentrantLock锁对象
ReentrantLock reentrantLock=new ReentrantLock();
try {
//加锁
reentrantLock.lock();
//TODO:业务逻辑
throw new Exception("业务出现异常");
}finally {
//保证出现异常的时候也可以释放锁
reentrantLock.unlock();
}
}
演示创建一个公平锁(默认为false):
//演示创建一个公平锁
public static void demo03(){
//创建一个ReentrantLock锁对象,通过构造方法,传入true时为公平锁,默认为false
ReentrantLock reentrantLock=new ReentrantLock(true);
}
演示创建一个读写锁:
//演示创建一个读写锁
public static void demo04(){
//创建
ReentrantReadWriteLock readWriteLock=new ReentrantReadWriteLock();
//获取读锁,共享锁,读与读可以同时进行
readWriteLock.readLock();
//获取写锁,排他锁(互斥锁),读写,写读,写写都不能共存
readWriteLock.writeLock();
}
ReentrantLock可以根据不同的Condition去休眠或唤醒线程。
演示:
两个条件:1.只处理男生任务
2.只处理女生任务
private static ReentrantLock reentrantLock=new ReentrantLock();
//定义不同的条件
private static Condition boyCondition = reentrantLock.newCondition();
private static Condition girlCondition = reentrantLock.newCondition();
public static void demo05() throws InterruptedException {
Thread threadBoy = new Thread(() -> {
// 让处理男生任务的线程去休眠
try {
boyCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒处理女生任务的线程
girlCondition.signalAll();
});
Thread threadGirl = new Thread(() -> {
// 让处理女生任务的线程去休眠
try {
girlCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 唤醒处理男生任务的线程
boyCondition.signalAll();
});
}
2.3ReentrantLock和synchronized的区别
区别:
①synchronized使用时不需要手动释放锁,ReentrantLock使用时需要手动释放,使用起来更灵活,但是也容易遗漏unlock
②sychronized在申请锁失败时,会一直等待锁资源,ReentrantLock可以通过trylock的方式等待一段时间就放弃
③synchronized是非公平锁,ReentrantLock默认是非公平锁。可以通过构造方法传入一个true开启公平锁模式
④synchronized是一个关键字,是JVM内部实现的,ReentrantLock是标准库的一个类,基于Java JUC实现
3.Semaphore信号量
3.1概念
信号量,用来表示“可用资源的个数”。本质上就是一个计数器。
理解信号量:
停车场外的显示屏上通常会显示当前停车场李可用的车位个数,车位个数相当于是可用资源。
1.当一辆车入场后,相当于申请一个资源,车位的个数就减一(这个称为信号量的 P 操作)
2.当一辆车离开时,相当于释放一个资源,车位的个数就加一(这个称为信号量的 V 操作)
3.停车场所有的车位就是可以显示的最大有效值
申请资源的时候,当资源已经被用完,那么线程申请的时候就会阻塞等待。
补充:当代码需要指定有限的资源个数时,可以考虑使用Semaphore来处理。
3.2代码演示
定义一个信号量,指定可用资源个数为3
//定义一个信号量,指定可用资源个数
private static Semaphore semaphore=new Semaphore(3);
模拟业务处理
public static void main(String[] args) {
//定义一个任务
Runnable runnable=new Runnable() {
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName()+"[+]申请资源");
//调用此方法,可用资源数减一
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"[!]申请到资源");
//模拟业务处理
TimeUnit.SECONDS.sleep(1);
//释放资源
semaphore.release();
System.out.println(Thread.currentThread().getName()+"[-]释放了资源");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
//创建多个线程
for (int i = 0; i < 20; i++) {
Thread thread=new Thread(runnable);
thread.start();
}
}
当申请资源满了时,就会阻塞等待。
4.CountDownLatch
4.1概念
同时等待N个任务执行结束。
理解CountDownLatch:
好像跑步比赛,10个选手依次就位,哨声响起才同时出发,所有选手都通过终点,才能结束比赛。
作用:可以设置所有的线程必须都到达某一个关键点然后再执行后续的操作。
4.2代码演示
演示跑步比赛:
定义一个CountDownLatch,参数为10代表10个选手
//定义一个CountDownLatch
private static CountDownLatch countDownLatch=new CountDownLatch(10);
创建线程,模拟10个选手比赛,直到10个选手都到达终点才会颁奖
public static void main(String[] args) throws InterruptedException {
System.out.println("所有选手各就各位");
//创建线程,模拟跑步比赛
for (int i = 0; i < 10; i++) {
Thread thread=new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出发");
//模拟比赛过程
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//到达终点,计数减一
countDownLatch.countDown();
System.out.println(Thread.currentThread().getName()+"到达终点");
},"player"+(i+1));
thread.start();
}
//等待所有线程执行完成
//一直等到countDownLatch为0时才会执行之后的代码
countDownLatch.await();
//颁奖
System.out.println("开始颁奖");
}
运行结果: