JUC基础
一.基础
1. wait和sleep的区别
- 来自不同得类,wait来自object,sleep来自Thread
- 关于锁的释放,wait需要手动释放锁,sleep不需要手动释放锁
- 使用范围不同。sleep可以在任何地方使用,wait必须在同步代码块中
生产环境中,使用休眠的方法Timeunit
try {
TimeUnit.SECONDS.sleep(10L);
} catch (InterruptedException e) {
e.printStackTrace();
}
2. synchronized和lock的区别
- synchronized内置的java关键字,lock是一个java类
- synchronized无法获取锁的状态,lock可以判断是否获取到了锁
- synchronized会自动释放锁,lock必须手动释放锁,否则会造成死锁
- synchronized 线程1(获取锁,阻塞)线程2(等待);lock锁不一定会等待下去
- synchronized 可重入锁,不可以中断,非公平锁;lock,可重入锁,可以判断锁,非公平(可以设置)
- synchronized 适合锁少量的代码同步问题,lock适合锁大量的同步代码
3. 使用lock的基本顺序
- new 一个Lock对象
- 加锁 lock.lock
- 业务代码放在try catch内
- 在finally中释放锁。lock.unlock
4. 公平锁和非公平锁
- 公平锁:先到先执行 (如果可以线程先到,需要执行1分钟,后面得线程就需要等待,会造成不好得体验,所以默认使用非公平锁)
- 非公平锁:可以插队执行 (默认)
5. 消费者和生产者的问题
使用wait和notifiyall时,需要使用循环判断修改条件,避免造成虚假唤醒
6. 锁的对象是什么?
对象
- synchronized锁用在一般方法上,锁的是当前调用的对象。如果是多个对象调用同一个方法,则会产生多把锁。
类(Class)
- synchronized锁用在静态方法上,锁的是Class对象。如果是多个对象调用同一个静态方法,则使用的是同一把锁
- arraylist set 是线程不安全的集合,可以使用Collections方法Collections.synchronizedList(),Collections.synchronizedSet()实现
- juc下的线程安全的集合,使用了lock锁
7. 集中提升线程执行效率的方法
- CountDownLatch -----线程减法,并行执行提示效率
- CyclicBarrier -----线程加法
- Semaphore ----信号量 高并发时,可以控制线程数量
8. 队列
BlockingQueue
- ArrayBlockingQueue
- DelayQueue(延时队列,在redisson中使用该队列进行延迟消息处理)
- SynchronousQueue(同步队列)
阻塞队列实现方式
- 抛出异常 add(添加) remove(移除) element(判断队列首位元素)
- 不抛出异常,返回值 offer 添加 poll 弹出 如果队列满了或者空的,会返回true和false,不会抛出异常 peek(判断队列首位元素,如果没有元素则返回空值)
- 阻塞,等待(一直等待):put(添加)如果队列已经满了,则会一直等待 take(移除)如果队列已经空了,则会一直等待
- 超时等待,offer方法重载offer(object,time,timeunit)添加数据的时候,如果超出time,则不继续等待,返回false poll方法重载poll(time,timeunit)取数据的时候,如果超出time,则不继续等待,返回false
SynchronousQueue
- 同步队列没有任何内部容量,甚至没有一个容量。其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然
9. 线程池
1.3个方法,7个参数,4种策略(拒绝策略)
- 3个方法
- Executors.newCachedThreadPool() 创建一个可伸缩的线程池。最大大小Integer.MAX_VALUE,容易造成OOM
- Executors.newFixedThreadPool(size) 创建一个固定大小szie的线程池,可能造成OOM
- Executors.newSingleThreadExecutor() 创建一个只有1个线程的线程池,不适用
- 7个参数
- corePoolSize ---核心线程池大小,一般由cpu核数决定。使用
- Runtime.getRuntime().availableProcessors()方法获取当前系统的cpu个数,用于配置核心线程数大小。核心线程池会一直保持连接,提供使用。
- maximumPoolSize --线程池最大同时执行的线程个数
- keepAliveTime ----线程保持存活时间,如果在指定的keepAliveTime时间后,没有过多的线程访问,则会释放部分线程,保持核心线程池个数
- TimeUnit --keepAliveTime 的时间单位
- BlockingQueue
----线程池的阻塞队列。访问的线程高于阻塞队列内的线程数+最大执行的线程数后,会触发拒绝策略。 - RejectedExecutionHandler ---拒绝策略,线程池打满后,对应的拒绝处理方式
- AbortPolicy ---丢弃多余的任务,并抛出rejected异常
- CallerRunsPolicy ---由当前调用者所在的线程执行
- DiscardPolicy ----直接丢弃多余的线程,不做任何处理
- DiscardOldestPolicy ---尝试丢弃最老的任务,并重新执行当前线程任务
2.线程池参数配置策略:
CPU密集型:(使用cpu较多,如运算,赋值,分配内存,查询,排序等)使用Runtime.getRuntime().availableProcessors(),获取当前系统的cpu核数,用来配置线程池的核心线程数
IO密集型:系统多数进行IO操作,对CPU的占用较小,可以多设置一些线程。
二 重点
1. 函数式接口:简化应用开发
只有一个抽象方法的接口,比如Runnable接口,foreach的参数。比如四大函数式接口function(函数式接口),Predicate(断言式函数式接口) Consumer(消费者函数式接口)
Supplier(提供者函数式接口)
2. 链式编程和流式编程
steam计算,过滤,排序,数据截取
3. forkjoin
任务窃取:当一个任务已经完成之后,会去其他任务中抢夺未完成的任务。(可能会造成阻塞)
使用:
- 继承RecursiveTask(递归任务)类(该类是ForkJoinTask类下的实现类,也可以继承其他的类)
- 重写compute方法,在compute方法内根据自己的业务逻辑拆分任务,使用fork()方法执行任务,使用join方法获取执行任务的结果
- 新建一个ForkJoinPool类,新建一个继承了RecursiveTask类的任务类,然后ForkJoinPool类执行submit方法执行RecursiveTask的任务类,执行后会返回一个ForkJoinTask的执行结果,使用ForkJoinTask.get 即可获取执行的最终结果(ForkJoinTask.get会阻塞。)
4. 异步回调
Future类下的实现类CompletableFuture。实现了java的异步回调方法
1. 无返回值执行方法
@Test
void taskTest() throws ExecutionException, InterruptedException, TimeoutException {
//runAsync 异步回调,无返回值
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
//执行代码块
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//1. 获取执行结果,线程阻塞
future.get();
//超时后抛出超时异常
future.get(1L, TimeUnit.SECONDS);//两种写法根据业务获取
}
2.有返回值执行方法,类似于ajax的异步回调提交,成功和失败的返回值
@Test
void taskTest() throws ExecutionException, InterruptedException {
//runAsync 异步回调,无返回值
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
//执行代码块
int a = 3;
int b = 2;
//int b = 0;
return a/b;
});
//等待futrue线程执行完成后再执行该线程,该线程有两个入参 void accept(T t, U u);
Integer integer = future.whenCompleteAsync((t, u) -> {
System.out.println(t);//future线程执行的返回值
System.out.println(u);//future线程执行的错误信息
}).exceptionally((e) -> {
return 0;//如果future线程执行报错,此处可以返回对应的处理值
}).get();
System.out.println(integer);
}
//当b = 2时,打印值 1 null 1
//当b = 0时,打印值 null java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero 0
5.JMM内存模型
1.8大操作
- read线程从主内存中读取共享变量
- load 线程将读取到的共享变量加载到工作内存中
- use 使用,线程使用共享变量
- assign 赋值,线程将修改后的共享变量写回工作内存
- wirte 线程将修改后的变量写回主内存
- store主内存将修改后的值存储到变量值
- lock主内存在将修改后的变量存储之前,先加锁
- unlock 主内存在将修改后的变量存储之前,解锁
2.一些JMM的同步约定
- 线程解锁前,必须把工作内存中的共享变量写回主内存
- 线程加锁前,必须读取最新的主内存中的共享变量
- 线程加锁和解锁必须是一把锁
6.指令重排 --->>>>内存屏障
在单例模式中使用最多
7.volatile
- 保证可见性
- 不保证原子性
可以防止指令重排
(1) 编译器优化重排
(2) cpu执行重排
8.单例模式
1. 饿汉式单例
造成空间浪费
//饿汉式单例
public class HungrySingleton {
//私有化构造方法
private HungrySingleton() {
}
//创建该实例
private static HungrySingleton singleton = new HungrySingleton();
//提供实例访问方法
public static HungrySingleton getInstance() {
return singleton;
}
}
2.懒汉式单例
效率低,可以被反射破解
//懒汉式单例
public class LazySingleton {
//私有化构造方法
private LazySingleton() {
}
//添加volatile关键字,防止多线程访问下出现线程不安全问题
private static volatile LazySingleton lazySingleton = null;
//对外暴露单例获取方法 synchronized 防止多线程访问情况下出现多个实例
public synchronized static LazySingleton getInstance() {
if (null == lazySingleton) {
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
3. 双重监测锁单例
效率低,可以被反射破解
public class DoubleCheckSingleton {
private DoubleCheckSingleton(){}
public static volatile DoubleCheckSingleton checkSingleton = null;
public static DoubleCheckSingleton getInstance(){
if (null==checkSingleton){
synchronized (DoubleCheckSingleton.class){
if (null==checkSingleton){
checkSingleton = new DoubleCheckSingleton();
}
}
}
return checkSingleton;
}
}
4.静态内部类单例
和饿汉式单例类似造成空间浪费
public class InnerInstance {
private InnerInstance(){}
private static class InnerTemp{
private static InnerInstance instance = new InnerInstance();
}
public static InnerInstance getInstance(){
return InnerTemp.instance;
}
}
5.枚举类单例
最安全,当前使用最多
public class EnumSingleton {
//私有化构造方法
private EnumSingleton(){}
//对外提供访问方法
public static EnumSingleton getInstance(){
return Singleton.INSTANCE.getInstance();
}
//枚举类单例
enum Singleton{
INSTANCE;
private EnumSingleton singleton = null;
private EnumSingleton getInstance() {
if (null==singleton){
singleton = new EnumSingleton();
}
return singleton;
}
}
}
6. 使用反射破解单例
@Test
void instanceTest() throws Exception {
//先获取一个单例实例
DoubleCheckSingleton singleton = DoubleCheckSingleton.getInstance();
//返回一个Constructor对象数组,该数组指示此Class对象所表示的类定义的构造函数的类型
Constructor<DoubleCheckSingleton> declaredConstructor = DoubleCheckSingleton.class.getDeclaredConstructor(null);
//设置可访问权限
declaredConstructor.setAccessible(true);
//使用构建器生成一个实例
DoubleCheckSingleton singleton1 = declaredConstructor.newInstance();
System.out.println(singleton.equals(singleton1));
}
Tips:单例加volatile,防止指令重排,造成不可预料的错误。
9.CAS
C:compare
A: And
S: Swape
比较并交换,会存在ABA问题
CAS原理:在更新某一个值之前,先去判断该值是否是所期望的值,如果是期望的值,则进行修改,如果不是期望的值,则一直重试。直到成功
ABA问题:同时执行两个线程,同时操作一个共享变量a=1,使用CAS理论,当线程1执行过程中,已经获取到变量值a=1,正准备下一步的时候,线程2抢夺资源成功,执行获取变量a=1, 修改变量a=2,然后再修改变量a=1。最后同步回主内存。此时线程1继续执行,然后比较a=1是自己所期望的值,然后修改成功。这种现象就是ABA问题。
解决ABA问题:因为修改版本号,修改一次,添加一个版本号,修改成功后,版本号也随之改变,修改的时候使用版本号和期望值共同判断
- 本文标签: Java juc
- 本文链接: https://www.tianyajuanke.top/article/25
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权