java.util.concurrent.*下的常见类你了解多少?
Posted 你这家伙
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了java.util.concurrent.*下的常见类你了解多少?相关的知识,希望对你有一定的参考价值。
java.util.concurrent.*是标准库提供的一组工具类,帮我们简化并发编程
1.concurrent下的locks锁
1.1.locks锁下常见的锁
![在这里插入图片描述](https://img-blog.csdnimg.cn/b9bd751d7875497ba5693a76c5da8025.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3draDE4ODkxODQzMTY1,size_16,color_FFFFFF,t_70)1.2.lock和sychronized的之间的区别
- lock锁的加锁和解锁操作是分开的
- lock体系是Java语言层面实现的,sychronized是JVM实现的
- lock提供了一些更灵活的方法(如trylock,如果尝试获取失败就直接放弃,或等待指定等待时间再放弃)
大部分情况下还是在使用sychronized,特殊情况下载使用其他锁,如果是自定制的锁,那么就需要lock系列
2.concurrent.atomic下的原子类
如:AtomicInterger
public class Test{
public static AtomicInteger num = new AtomicInteger(0);
public static void main(String[] args) {
//1.获取值
int ret = num.get();
//2.修改值
num.set(100);
//3.进行自增/自减
int num1 = num.getAndIncrement(); // n++
int num2 = num.incrementAndGet(); // ++n
}
}
3.concurrent下的Callable
通常Callable/Future/FutureTask是搭配使用的,就是把线程封装成了能够返回结果的样子,我们之前创建的Thread都是没有返回值的,如果想多线程进行一些计算,就需要自己来汇总
上代码
如:求1—100的和
/**
* Callable和Runnable很像,都是来描述一个具体的“任务”,
* 但是Runnable是没有返回值的,而Callable是有返回值的
*/
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1.创建Callable
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
//实现call方法,来求值
int num = 0;
for (int i = 0; i <= 100; i++) {
num += i;
}
return num;
}
};
//2.创建一个FutureTask
FutureTask<Integer> futureTask = new FutureTask<>(callable);
//3.创建一个个线程
Thread thread = new Thread(futureTask);
thread.start();
//4.获取线程执行结果,get方法会发生阻塞,一直到call方法执行完毕,get方法才会返回
Integer integer = futureTask.get();
System.out.println(integer);
}
}
如果创建的线程不需要返回值,就可以使用Runnable,如果需要返回值,就可以使用Callable(搭配Future或者FutureTask使用)。因为Thread里面没有直接获取结果的方法
4.Semaphore(信号量)
相当于一个“计数器”,表示当前可用资源的个数
每次有人申请一个资源,信号量的值就+1(P操作)
每次有人释放一个资源,信号量的值就 -1(V操作)
当信号量的值为0的时候,那么此时就有两种策略:1.等待直到有人释放资源;2.不再等待,去找其他的资源
多线程在进行·PV操作的时候,此时线程都是安全的(信号量计数加减都是原子的)
信号量中有一个特殊的情况,当信号量的值只有0和1两种取值的时候,这种成为“二元信号量”,所谓的“二元信号量”本质上就是一个互斥锁(synchronized就可以视为一个“二元信号量”,同一时刻一个线程能获取到锁,相当于资源为1)
比如:停车场就会有一个计数器,门口就会有一个显示牌,上面显示还剩下多少车位,每当有车进入停车场,计数就-1,每当有人开出停车场,计数牌就+1,但是如果此时停车位为0的时候,在当有人进来,此时有两种情况:要么等待里面有人开出来,要么扭头就走,去找其他停车场
上代码
public class Test{
public static void main(String[] args){
//表示可用资源的个数
Semaphore semaphore = new Semaphore(5);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
long threadId = Thread.currentThread().getId();
System.out.println("申请资源:"+threadId);
//P 操作申请资源,计数器就-1
semaphore.acquire();
System.out.println("我获取到资源了:"+threadId);
Thread.sleep(1000);
System.out.println("我释放资源了:"+threadId);
//释放资源,计数器就+1
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
//创建20个线程去获取这5个资源
for (int i = 0; i < 20; i++) {
Thread thread = new Thread(runnable);
thread.start();
}
}
}
执行结果
在有多个共享资源的时候,就可以使用信号量(比如实现一个线程安全的阻塞队列)
5.线程池ThreadPoolExecutor
线程池系列相关的类还有Executors,ExecutorService
线程池存在的意义,就是为了频繁创建/销毁线程的开销
面试常考:线程池构造方法里面的参数含义
corePoolSize:核心线程数大小,当线程数<corePoolSize ,会创建线程执行runnable
maximumPoolSize: 最大线程数, 当线程数 >= corePoolSize的时候,会把runnable放入workQueue中
keepAliveTime:保持存活时间,当线程数大于corePoolSize的空闲线程能保持的最大时间。
unit :时间单位
workQueue: 保存任务的阻塞队列
threadFactory :创建线程的工厂
handler :拒绝策略
任务执行顺序:
1、当线程数小于corePoolSize时,创建线程执行任务。
2、当线程数大于等于corePoolSize并且workQueue没有满时,放入workQueue中
3、线程数大于等于corePoolSize并且当workQueue满时,新任务新建线程运行,线程总数要小于maximumPoolSize
4、当线程总数等于maximumPoolSize并且workQueue满了的时候执行handler的rejectedExecution。也就是拒绝策略。
拒绝策略
1、ThreadPoolExecutor.AbortPolicy() 直接抛出异常RejectedExecutionException
2、ThreadPoolExecutor.CallerRunsPolicy() 直接调用run方法并且阻塞执行
3、ThreadPoolExecutor.DiscardPolicy() 直接丢弃后来的任务
4、ThreadPoolExecutor.DiscardOldestPolicy() 丢弃在队列中队首的任务
6.CountDownLatch
好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。
public class Test{
public static void main(String[] args) throws InterruptedException {
//表示有10个选手
CountDownLatch countDownLatch = new CountDownLatch(10);
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
//某个选手到达终点
countDownLatch.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
//必须要等到10人全部到达终点,await才会返回,否则就会阻塞
countDownLatch.await();
System.out.println("比赛结束");
}
}
还比如:下载一个1G大小的文件,就可以分成10个线程,每个线程负责100MB,等待10个线程下载完,这个文件才算下载完成
面试常考
- Thread,Runnable,Callable之间的区别和联系
- 线程同步的方式有哪些?
- 你平时用过哪些线程同步的方式?
- 为什么有了 synchronized 还需要 juc (java.util.concurrent)下的 lock?
- AtomicInteger 的实现原理是什么?
- 信号量听说过么?之前都用在过哪些场景下?
- 说一下并发包下有哪些并发类,concurrentHashmap,unsafe,原子类,都分别讲一下怎么支持并发 的?
以上是关于java.util.concurrent.*下的常见类你了解多少?的主要内容,如果未能解决你的问题,请参考以下文章
JUC包(java.util.concurrent)下的常用子类
Java源码之 java.util.concurrent 学习笔记01
Java多线程20:多线程下的其他组件之CountDownLatchSemaphoreExchanger
Thinking in Java---Concurrent包下的新构件学习+赛马游戏仿真