原创

JUC基础

温馨提示:
本文最后更新于 2024年09月18日,已超过 226 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

一.基础

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的区别

  1. synchronized内置的java关键字,lock是一个java类
  2. synchronized无法获取锁的状态,lock可以判断是否获取到了锁
  3. synchronized会自动释放锁,lock必须手动释放锁,否则会造成死锁
  4. synchronized 线程1(获取锁,阻塞)线程2(等待);lock锁不一定会等待下去
  5. synchronized 可重入锁,不可以中断,非公平锁;lock,可重入锁,可以判断锁,非公平(可以设置)
  6. synchronized 适合锁少量的代码同步问题,lock适合锁大量的同步代码

3. 使用lock的基本顺序

  1. new 一个Lock对象
  2. 加锁 lock.lock
  3. 业务代码放在try catch内
  4. 在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 ---拒绝策略,线程池打满后,对应的拒绝处理方式
      1. AbortPolicy ---丢弃多余的任务,并抛出rejected异常
      2. CallerRunsPolicy ---由当前调用者所在的线程执行
      3. DiscardPolicy ----直接丢弃多余的线程,不做任何处理
      4. 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大操作

  1. read线程从主内存中读取共享变量
  2. load 线程将读取到的共享变量加载到工作内存中
  3. use 使用,线程使用共享变量
  4. assign 赋值,线程将修改后的共享变量写回工作内存
  5. wirte 线程将修改后的变量写回主内存
  6. store主内存将修改后的值存储到变量值
  7. lock主内存在将修改后的变量存储之前,先加锁
  8. unlock 主内存在将修改后的变量存储之前,解锁

2.一些JMM的同步约定

  1. 线程解锁前,必须把工作内存中的共享变量写回主内存
  2. 线程加锁前,必须读取最新的主内存中的共享变量
  3. 线程加锁和解锁必须是一把锁

6.指令重排 --->>>>内存屏障

在单例模式中使用最多

7.volatile

  1. 保证可见性
  2. 不保证原子性
  3. 可以防止指令重排

    (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问题因为修改版本号,修改一次,添加一个版本号,修改成功后,版本号也随之改变,修改的时候使用版本号和期望值共同判断

正文到此结束