JUC高级-0608

重新看JUC课程,选择周阳讲的JUC

1.前置知识

lombok插件

Lombok是一个Java库,它通过注解的方式,能够在编译时自动为类生成构造函数、getters、setters、equals、hashCode和toString方法,以及其他常用方法,从而使我们的代码更加简洁,更易于阅读和编写。

  1. @AllArgsConstructor:这个注解会生成一个包含所有字段的构造函数,这个构造函数的参数顺序与字段在类中声明的顺序一致。
  2. @NoArgsConstructor:这个注解会生成一个无参数的默认构造函数。
  3. @Data:这个注解包含了@ToString,@EqualsAndHashCode,@Getter/@Setter和@RequiredArgsConstructor的功能,即:为类提供读写属性,同时生成equals,canEqual,hashCode,toString方法,以及参数为final的构造方法。所以,如果一个类被@Data注解,那么这个类就拥有了以上这些基本的数据操作功能。

方法引用

在Java 8中,方法引用是一种简化Lambda表达式的写法。方法引用可以更简洁、更直观地表示现有的方法、构造方法或者特定类型的任意对象的实例方法。

方法引用有以下四种形式:

  1. 静态方法引用:如果函数签名和定义已存在的静态方法签名一致,就可以使用静态方法引用。
    // Lambda 表达式
    Consumer<String> lambdaConsumer = (String s) -> System.out.println(s);
    
    // 方法引用
    Consumer<String> methodRefConsumer = System.out::println;
    
  2. 特定实例的实例方法引用:如果函数签名和特定实例对象的某个实例方法一致,就可以使用特定实例的实例方法引用。
    String str = "abc";
    Predicate<String> lambdaPredicate = (String s) -> str.startsWith(s);
    Predicate<String> methodRefPredicate = str::startsWith;
    
  3. 任意对象的实例方法引用:如果函数签名和某个类的实例方法一致,就可以使用任意对象的实例方法引用。
    Predicate<String> lambdaPredicate = (String s) -> s.isEmpty();
    Predicate<String> methodRefPredicate = String::isEmpty;
    
  4. 构造方法引用:如果函数签名和构造方法一致,就可以使用构造方法引用。
    Supplier<List<String>> lambdaSupplier = () -> new ArrayList<>();
    Supplier<List<String>> methodRefSupplier = ArrayList::new;
    

总的来说,方法引用是一种让你可以重复使用已有方法的功能。在许多情况下,它们可以使你的代码更简洁、更清晰。

2.线程基础复习

  1. 多线程的优势和劣势:
    1. 优势:
      1. 充分利用多核处理器
      2. 提高程序性能,高并发系统
      3. 提高程序吞吐量,异步+回调等生产需求
    2. 劣势:
      1. 线程安全问题:i++,集合线程安全问题
      2. 线程锁问题:synchronized过重,怎样使用更灵活的锁
      3. 线程性能问题:
  2. 从start一个线程说起:
    1. Thread类中的start是一个同步方法,内部调用了一个start0的本地方法,是用C++写的
    2. C++就是JDK的JDK,Java语言就是C++的简化版。
    3. openjdk的写JNI一般是一一对应的,Thread.java对应的就是Thread.c
    4. 读C++远码:
      1. thread.c : openjdk8\jdk\src\share\native\java\lang
      2. jvm.cpp : openjdk8\hotspot\src\share\vm\prims
        Thread::start(native_ thread);
        
      3. Thread.cpp : openjdk8\hotspot\src\share\vm\runtime
        os::start_ thread(thread);
        //	操作系统提供分配的线程
        
    5. 总结:线程分配是JVM结合操作系统进行分配的
  3. Java多线程相关概念:
    1. 一把锁:synchronized
    2. 两个并:并行和并发
    3. 3个程:进程、线程、管程
      1. 管程:Monitor,监视器,也就是我们说的锁,
        synchronized(Monitor){}	//	monitor就是一个监视器,也就是一个锁	
        
      2. Monitor :是一种同步机制,他的义务是保证同时间只有一个线程可以访问被保护的数据和代码
      3. JVM中同步是基于进入和退出监视器对象(Monitor,管程对象)来实现的,每个对象实例都会有一个Monitor对象,
      4. Monitor对象会和Java对象一同创建并销毁,它底层是由C++语言来实现的。
      5. 执行线程就要求先成功持有管程,然后才能执行方法,最后当方法完成(无论是正常完成还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。
  4. 用户线程和守护线程
    1. Java线程分为两种,用户线程和守护线程
      1. 一般而已说的都是用户线程
      2. 守护线程用于在后台完成一些必要的操作,例如垃圾回收线程
      3. 当用户线程运行时,守护线程会一直运行;用户线程结束,守护线程也会随之结束
    2. 线程的daemon属性:
      1. 源码解读:
        public final boolean isDaemon() {
        	return daemon;
        }
        
      2. true就是守护线程,false是用户线程
      3. code演示
        1. main线程也是用户线程,main线程结束了,用户线程并不一定结束
      4. 总结:
        1. 如果用户线程全部结束意味着程序需要完成的业务操作已经结束了,守护线程随着JVM一同结束工作
        2. setDaemon(true)方法必须在start()之前设置,否则报llegalThreadState Exception异常

3.CompletableFuture

3.1 Future接口理论复习

Future接口(FutureTask实现类)定义了操作异步任务执行一些方法,如获取异步任务的执行结果、取消任务的执行、判断任务是否被取消、判断任务执行是否完毕等。

比如主线程让一个子线程去执行任务,子线程可能比较耗时,启动子线程开始执行任务后,主线程就去做其他事情了,忙其它事情或者先执行完,过了一会才去获取子任务的执行结果或变更的行务状态。
请添加图片描述
Future又称异步任务接口:
一句话:Future接口可以为主线程开一个分支任务,专门为主线程处理耗时和费力的复杂业务。

3.2 Future接口常用实现类Future Task异步任务

  1. Future接口可以做什么
    1. Future是Java5新加的一个接口,它提供了一种异步并行计算的功能。如果主线程需要执行一个很耗时的计算任务,我们就可以通过future把这个任务放到异步线程中执行。主线程继续处理其他任务或者先行结束,再通过Future获取计算结果。
  2. 本源的Future接口相关架构
    1. 代码说话:
      1. Runnable接口 : 实现run方法,无返回值,不抛出异常
      2. Callable接口 :实现call方法,有返回值,抛出异常
      3. Future接口和FutureTask实现类
      4. 目的:异步多线程任务执行且返回有结果,
      5. 三个特点:多线程/有返回/异步任务
    2. 存在问题:我如果想创建一个线程实现类,需要多线程、有返回值、异步任务,则需要实现callable接口,但是使用Thread创建线程必须传Runnable类型参数,所以我们去找runnable的子类。有没有可以满足的:RunnableFuture<T>,这个接口有一个实现类:FutrueTask,但是FutureTask没有实现Callable接口,不过他支持构造注入。
    3. FutrueTask两种构造:不支持空参构造
      1. FutureTask(Callable <V> callable)
      2. FutureTask( Runnable runnable , V result)
    4. 代码实例:
      //	如何获得一个带有返回值的异步多线程任务,结合Thread类,并且获得处理结果
      public class MyCompletableFutrueDemo {
          
          public static void main(String[] args) throws ExecutionException, InterruptedException {
              
              FutureTask<String> futureTask = new FutureTask<>(new MyThread());
      
              Thread t1 = new Thread(futureTask,"t1");
      
              t1.start();
      				//	获取异步执行结果
              System.out.println(futureTask.get());
          }
      }
      
      
      class MyThread implements Callable<String>{
      
          @Override
          public String call() throws Exception {
              System.out.println("come in callable");
              return "hello world";
          }
      }
      
  3. Future编码实战和优缺点分析
    1. 优点:
      1. future+线程池异步多线程任务配合,能显著提高程序的执行效率。
      2. 代码说话:
        ExecutorService threadPool = Executors.newFixedThreadPool(3);
        
            FutureTask<String> futureTask1 = new FutureTask<>(() -> {
            try {TimeUnit.MICROSECONDS.sleep(500); } catch (InterruptedException e) {e.printStackTrace();}
                  return "task1 over";
            });
            threadPool.submit(futureTask1);
            futureTask1.get();
        
      3. 如果3个任务都交给主线程做,需要消耗1100ms,交给3个异步线程,800ms
    2. 缺点:
      1. get因为是异步的,一般建议放在程序最后,不然会阻塞主线程的其他任务执行
      2. 我希望可以过时不侯,可以自动离开:futureTask.get(3,TimeUnit.SECONDS);这个方法会抛出超时异常,不过可能会影响其他程序执行。
      3. isDone()轮询空转,消耗系统资源,而且也不见得能及时获取结果
        1. 如果想要异步获取结果,通常都会以轮询的方式去获取结果,尽量不要阻塞。
    3. 结论:Future对于结果的获取并不友好,只能轮询或者阻塞的方式去获取
  4. 完成一些复杂的任务
    1. 对于简单的业务场景使用Future完全OK
    2. 回调通知
      1. 应对Future的完成时间,完成了可以告诉我,也就是我们的回调通知
      2. 通过轮询的方式去判断任务是否完成这样非常古cPU并且代码也不优雅
    3. 创建异步任务:Future+线程池配合
    4. 多个任务前后依赖可以组合处理(水煮鱼)Future做不到
      1. 想将多个异步任务的计算结果组合起来,后一个异步任务的计算结果需要前一个异步任务的值
      2. 将两个或多个异步计算合成一个异步计算,这几个异步计算互相独立,同时后面这个又依赖前一个处理的结果。
    5. 对计算速度选最快
      1. 当Future集合中某个任务最快结束时,返回结果,返回第一名处理结果。
    6. Future不足以胜任复杂任务:
      1. 使用Future之前提供的那点API就囊中羞涩,处理起来不够优雅,这时候还是让CompletableFuture 以声明式的方式优雅的处理这些需求。
      2. 从i到i++,o(n_n)0哈哈~
      3. Future能干的,CompletableFuture都能干

3.3 CompletableFuture对Future的改进

  1. Completable为什么出现?
    1. get()方法在Future 计算完成之前会一直处在阻寨状态下,
    2. isDone()方法容易耗费CPU资源,
    3. 对于真正的异步处理我们希望是可以通过传入回调函数,在Future结束时自动调用该回调函数,这样,我们就不用等待结果。
    4. 阻寨的方式和异步编程的设计理念相违背,而轮询的方式会耗费无谓的CPU资源。因此,JDK8设计出CompletableFuture。
    5. CompletableFuture提供了一种观察者模式类似的机制,可以让任务执行完成后通知监听的一方。
  2. CompletableFuture 和CompletionStage源码分别介绍
    1. 类架构说明
      public class CompletableFuture<T> implements Future<T>, CompletionStage<T> { 
      
    2. 接口CompletionStage
      1. CompletionStage代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段
      2. 一个阶段的计算执行可以是一个Function,Consumer或者Runnable。
      3. 一个阶段的执行可能是被单个阶段的完成触发,也可能是由多个阶段一起触发
      4. 代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段,有些类似Linux系统的管道分隔符传参数。
      5. 在实际操作中,就是.whenComplete 和 .exceptionally 两个方法。
    3. 类CompletableFuture
      1. 在Java8中,CompletableFuture提供了非常强大的Future的扩展 可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。
      2. 它可能代表一个明确完成的Future,也有可能代表一个完成阶段(CompletionStage),它支持在计算完成以后触发一些西数或执行某些动作。
      3. 它实现了 Future和CompletionStage接口
  3. 核心的四个静态方法,来创建一个异步任务(不推荐使用new CompletableFuture()去获得,创建的是不完备的)
    0. 两组四个方法:
    1. runAsync 无返回值
      1. public static Completable Future <Void> runAsync(Runnable runnable)
      2. public static CompletableFuture <Void> runAsync(Runnable runnable,Executor executor)
    2. supplyAsync 有返回值(常用)
      1. public static <U> Completable Future<U> supplyAsync(Supplier <U> supplier)
      2. public static <U> CompletableFuture <U> supplyAsync(Supplier <U> supplier, Executor executor)
    3. 上述Executor executor参数说明
      1. 没有指定Executor的方法,直接使用默认的Fork JoinPool.commonPool(),作为它的线程池执行异步代码。
      2. 如果指定线程池,则使用我们自定义的或者特别指定的线程池执行异步代码
    4. code:
      ExecutorService threadPool = Executors.newFixedThreadPool(3);
      
              CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
                  System.out.println(Thread.currentThread().getName());
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  }catch (InterruptedException e){
                      e.printStackTrace();
                  }
              },threadPool);
              System.out.println(completableFuture.get());
      
              CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  }catch (InterruptedException e){
                      e.printStackTrace();
                  }
                  return "hello world";
              }, threadPool);
      
              System.out.println(completableFuture2.get());
      
              threadPool.shutdown();
      
    5. code通用演示,减少阻塞和轮询:
      1. 从Java8开始引入了CompletableFuture,它是Future的功能增强版,减少阻塞和轮询可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
      2. 解释下为什么默认线程池关闭,自定义线程池自动关闭
        • 主线程不能立刻结束,否则CompletableFuture就以使用的线程池会立刻关闭:暂停3秒钟线程
        • completable线程类似守护线程,主线程完成之后会关闭
      3. code :
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
        
                try {
                    CompletableFuture.supplyAsync(()->{
                        System.out.println(Thread.currentThread().getName() + "come in");
                        int result = ThreadLocalRandom.current().nextInt(10);
                        try {
                            TimeUnit.SECONDS.sleep(1);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("1秒钟之后出结果!");
                        return result;
                    },fixedThreadPool).whenComplete((result,exception)->{
        
                        System.out.println("计算完成!——:"+result);
        
                    }).exceptionally(e->{
                        e.printStackTrace();
                        System.out.println("异常情况:"+e.getCause()+"  "+e.getMessage());
                        return null;
                    });
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }finally {
                    fixedThreadPool.shutdown();
                }
        
                //  主线程不能立刻结束,否则CompletableFuture就以使用的线程池会立刻关闭:暂停3秒钟线程
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        
    6. Completable优点:
      1. 异步任务结束时,会自动回调某个对象的方法:
      2. 主线程设置好回调后,不再关心异步任务的执行,异步任务之间可以顺序执行
      3. 异步任务出错时,会自动回调某个对象的方法;
      4. 和ajax很像

3.4 函数式编程

复习:

  1. Runnable,无参数,无返回值
    @FunctionalInterface
    public interface Runnable {
        public abstract void run();
    }
    
  2. Function<T,R>:功能性函数接口:接收一个参数,并且有返回值
    @FunctionalInterface
    public interface Function<T, R> {
    
        R apply(T t);
    }   
    
  3. Consumer<T> : 消费者型接口:接收一个参数,无返回值
    @FunctionalInterface
    public interface Consumer<T> {
    
        void accept(T t);
    }
    
  4. Supplier:供给者型接口,无参数,有返回值
    @FunctionalInterface
    public interface Supplier<T> {
    
        T get();
    }
    
  5. BiConsumer : 双参数消费者,两个参数,无返回值
    @FunctionalInterface
    public interface BiConsumer<T, U> {
    
        void accept(T t, U u);
    }
    
  6. 小总结
函数式接口名称方法名称参数返回值
Runnablerun无参数无返回值
Functionapply1个参数有返回值
Consumeraccept1个参数无返回值
Supplierget无参数有返回值
BiComsumeraccept2个参数无返回值
  1. Chain链式调用
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Accessors(chain = true)
    class Student{
    
        private Integer id;
        private String studentName;
        private String major;
        
    }
    public static void main(String[] args) {
    
            Student student = new Student();
    
      			//	链式调用
            student.setId(12).setStudentName("mike").setMajor("CS");
        }
    
  2. join和get对比:都可以从conpletable里面取值:
    1. 作用几乎一样,只是join不抛出异常

3.5 案例精讲-电商网站比价需求

  1. 需求说明:
    1. 同一款产品,同时搜索出同款产品在各大电商平台的售价:
    2. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
  2. 输出返回:
    1. 出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List<String>
    2. 《mysql》 in jd price is 88.05
    3. 《mysql》 in dangdang price is 86.11
    4. 《mysql》 in taobao price is 90.43
  3. 解决方案,比对同一个商品在各个平台上的价格,要求获得一个清单列表,
    1. step by step,按部就班,查完京东查淘宝,查完淘宝查天猫……
    2. all in. 万箭齐发,一口气多线程异步任务同时查询。。。。。
  4. 核心逻辑写法:
     public static List<String> getPriceByASync(List<NetMall> list,String productName) {
            return list
                    .stream()
                    .map(netMall -> 
                            CompletableFuture.supplyAsync(() -> 
                                    String.format(productName + " is %s price is %.2f",
                                            netMall.getMallName(),
                                            netMall.calcPrice(productName))))	//Stream<CompletableFuture<String>>
                    .collect(Collectors.toList())	// List<CompletableFuture<String>>
                    .stream()	//	Stream<CompletableFuture<String>>
                    .map(CompletableFuture::join)	//Stream<String>
                    .collect(Collectors.toList());	//	List<String>
        }
    
    
    以上是周阳代码,不知道为什么不这么写,减少一次来回转换
      
      public static List<String> getPriceByASync(List<NetMall> list,String productName) {
            return list
                    .stream()
                    .map(netMall -> CompletableFuture.supplyAsync(()->
                            String.format(String.format(productName + " is %s price is %.2f",
                            netMall.getMallName(),
                            netMall.calcPrice(productName)))).join())
                    .collect(Collectors.toList());
        }
    
    然后自己去测试了一下:
    后面的写法看似简单,实则失去了异步队列的优势,而是一个一个去执行,并且等待join,然后映射成新的值,相当于异步任务退化成串行化执行,耗时差距非常大!!
    

3.6 CompletableFuture常用方法

  1. Completable实现了Future和CompletionStage,前者方法少功能弱,后者方法多。现以分类的方式介绍Completable的方法。
  2. 获得结果和触发计算
    1. 获取结果
      1. public T get() 一定要拿到结果,容易阻塞
      2. public T get(long timeout, TimeUnit unit) 过时不候
      3. public T join() 和get一样,只是没有异常
      4. public T getNow (T valuelfAbsent) 现在取值,如果没完成给一个替代结果,不阻塞
    2. 主动触发计算
      1. public boolean complete(T value):返回是否打断了get阻塞,打断的话用替代值,没打断用计算值
      2. //	code
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
                    //暂停几秒钟线程
                    //暂停几秒钟线程
                    try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
                    return 1;
                },threadPoolExecutor);
        
                System.out.println(future.get());
                System.out.println(future.get(2L,TimeUnit.SECONDS));
         				System.out.println(future.getNow(9999));
        				System.out.println(future.complete(-44)+"\t"+future.get());
        
  3. 对计算结果进行处理
    1. thenApply:(一般常用
      1. 计算结果存在依赖关系,这两个线程串行化
      2. completable默认使用forkJoinPool线程池,如果主线程结束,他也会自动消失
      3. 异常:由于存在依赖关系(当前步错,不走下一步),当前步骤有异常的话就叫停。
      4. System.out.println(CompletableFuture.supplyAsync(() -> {
                    return 1;
                }).thenApply(f -> {
                    return f + 2;
                }).thenApply(f -> {
                    return f + 3;
                }).whenComplete((v, e) -> {
                    if (e == null) {
                        System.out.println("0-------result: " + v);
                    }
                }).exceptionally(e -> {
                    e.printStackTrace();
                    System.out.println(e.getMessage());
                    return null;
                }).join());
        
    2. handle
      1. 计算结果存在依赖关系,这两个线程串行化,这一点和上面一样
      2. 区别:有异常也可以往下一步走,根据带的异常参数可以进一步处理,只能走一步。
      3. System.out.println(CompletableFuture.supplyAsync(() ->  {
                    return 1;
                }).handle((f,e) -> {
                    System.out.println("-----1");
                    return f + 2;
                }).handle((f,e) -> {
                    System.out.println("-----2");
                    return f + 3;
                }).handle((f,e) -> {
                    System.out.println("-----3");
                    return f + 4;
                }).whenComplete((v, e) -> {
                    if (e == null) {
                        System.out.println("----result: " + v);
                    }
                }).exceptionally(e -> {
                    e.printStackTrace();
                    return null;
                }).join());
        
    3. 总结:
      1. Exceptionally : try/catch
      2. whenComplete / handle -> try/finally
  4. 对计算结果进行消费
    1. 接收任务的处理结果,并消费处理,无返回结果
    2. thenAccept
      CompletableFuture.supplyAsync(() -> {
                  return 1;
              }).thenApply(f -> {
                  return f+2;
              }).thenApply(f -> {
                  return f+3;
              }).thenAccept(System.out::println);
      
    3. 对比补充:code之间执行顺序问题:
      1. thenRun
        1. thenRun(Runnable runnable)
        2. 任务A 执行完执行 B,并且R不需要A 的结果
        3. System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenRun(() -> {}).join());	//	A忙完了忙B,两者没有依赖,只是有顺序关系
          
      2. thenAccept
        1. thenAccept(Consumer action)
        2. 任务A执行完执行 B,B需要A的结果,但是任务B无返回值
        3. System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenAccept(resultA -> {}).join());	//	A忙完了,B需要A的结果,有顺序也有依赖关系,但是B无返回值
          
      3. thenApply
        1. thenApply(Function fn)
        2. 任务 A执行完执行 B,B需要A的结果,同时任务B有返回值
        3. System.out.println(CompletableFuture.supplyAsync(() -> "resultA").thenApply(resultA -> resultA + " resultB").join());	//	B需要A的结果,并且还有返回值
          
    4. CompletableFuture和线程池说明
      1. 以thenRun和thenRunAsync为例,有什么区别?
      2. 小总结:
        1. 没有传入自定义线程池,都用默认线程池ForkJoinPool
        2. 传入了一个自定义线程池,如果你执行第一个任务的时候,传入了一个自定义线程池:
          1. 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。
          2. 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池
        3. 备注:有可能处理太快,系统优化切换原则,直接使用main线程处理
        4. 其它如: thenAccept和thenAcceptAsync, thenApply和thenApplyAsync等,它们之间的区别也是同理
      3. 源码分析:判断用户cpu核数,一般都大于1,所以用了Async默认就是用forkJoinPool
  5. 对计算速度进行选用
    1. 谁快用谁:
    2. applyToEither
    3. System.out.println(CompletableFuture.supplyAsync(() -> {
                  //暂停几秒钟线程
                  try {
                      TimeUnit.SECONDS.sleep(1);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  return 1;
              }).applyToEither(CompletableFuture.supplyAsync(() -> {
                  try {
                      TimeUnit.SECONDS.sleep(2);
                  } catch (InterruptedException e) {
                      e.printStackTrace();
                  }
                  return 2;
              }), r -> r + " is winner ").join());
      
  6. 对计算结果进行合并
    1. 两个CompletionStage任务都完成后,最终能把两个任务的结果一起交给thenCombine来处理先完成的先等着,等待其它分支任务
    2. thenCombine
    3. System.out.println(CompletableFuture.supplyAsync(() -> {
                  return 10;
              }).thenCombine(CompletableFuture.supplyAsync(() -> {
                  return 20;
              }), (r1, r2) -> {
                  return r1 + r2;
              }).join());
      
    4. 二者的结果合并之后,依然可以继续合并。

4. Java多线程锁

4.1 乐观锁和悲观锁

  1. 悲观锁
    1. 认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。
    2. synchronized关键字和Lock的实现类都是悲观锁
    3. 适合写操作多的场景,先加锁可以保证写操作时数据正确。显式的锁定之后再操作同步资源
    4. 一句话:狼性锁
  2. 乐观锁
    1. 认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。
    2. 在Java中是通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。
    3. 如果这个数据没有被更新,当前线程将自己修改的数据成功写入。
    4. 如果这个数据己经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重试抢锁等等
    5. 判断规则
      1. 版本号机制Version
      2. 最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的

4.2 8锁案例展示锁的原理

  1. 锁相关的8种案例演示code,和JUC基础里面的8锁案例一样,不再次记录。
  2. 本质上,锁的范围;是否同一把锁

4.3 synchronized三种方式及底层原理

  1. synchronized三种应用方式
    1. 作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁;
    2. 作用于代码块,对括号里配置的对象加锁。
    3. 作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁;
  2. synchronized底层原理:
    1. 同步代码块:实现使用的是monitorenter和monitorexit指令
      1. 现象:会有一个enter和两个exit:原因:为了避免异常导致锁无法退出,自动进行退出
      2. 请添加图片描述
      3. 相比synchronized,lock就必须写在finally里面保证必然释放
      4. 一定是一个enter对应两个exit吗?
        1. 默认情况是的
        2. 如果一个同步块必然会抛出异常,那么第一个exit就省略掉了
    2. 普通同步方法:ACC_SYNCHRONIZED
      1. 调用指念将会检查方法的ACCSYNCHRONIZED访问标志是否被设置。如果设置了,执行线程会将先持有monitgr锁,然后再执行方法,最后在方法完成(无论是正常完成还是非馆常完成)时释放monitor
    3. 静态同步方法ACC_PUBLIC, ACC_ SYNCHRONIZED
  3. synchronized锁的是什么?
    0. 管程:
    1. 管程(英语:Monitors,也称为监视器)是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。这些共享资源一般定硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
    2. 同步指令:Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步,这两种同步结构都是使用管程(Monitor,更常见的是直接将它称为“锁”)来实现的。
      方法级的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟
      机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为
      同步方法。当方法调用时,调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如
      果设置了,执行线程就要求先成功持有管程,然后才能热行方法,最后当方法完成(无论是正當完成
      还是非正常完成)时释放管程。在方法执行期间,执行线程持有了管程,其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同
      步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。
    3. 为什么任何一个对象都能成为锁
      1. 每个对象天生都带着一个对象监视器
      2. 每一个被锁住的对象都会和Monitor关联起来
    4. 什么是管程monitor,C++源码解读
      1. ObjectMonitor java 一> ObjectMonitor.cpp 一> objectMonitor.hpp
      2. objectMonitor.hpp:属性及作用
        1. _owner:指向持有ObjectMoniter对象的线程
        2. _WaitSet :存放处于wait状态的线程队列
        3. _EntryList :存放处于等待锁block状态的线程队列
        4. _recursions:锁的重入次数
        5. _count:用来记录该线程获取锁的次数
    5. 在Hotspot虚拟机中,monitor采用ObjectMonitor实现
      1. synchronized必须作用于某个对象中,所以Java在对象的头文件存储了锁的相关信息。锁升级功能主要依赖于 MarkWord 中的锁标志。位和释放偏向锁标志位,后续讲解锁升级时候我们再加深,
      2. 请添加图片描述

4.4 公平锁和非公平锁

  1. 从ReentrantLock卖票编码演示公平和非公平现象
  2. 何为公平锁/非公平锁
    1. 公平:是指多个线程按照申请锁的顺序来获取锁,这里类似排队买票,先来的人先买后来的人在队尾排着,这是公平的
      Lock lock = new ReentrantLock(true);/true 表示公平锁,先来先得
    2. 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某个线程一直得不到锁)
      Lock lock = new ReentrantLock(false);/false 表示非公平锁,后来的也可能先获得锁
      Lock lock = new ReentrantLock(😕/默认非公平锁
  3. 为什么会有公平锁/非公平锁的设计?为什么默认非公平?
    1. 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间养存在的还是很明显的。所以非公平锁能更充分的利用CPU 的时间片,尽量减少 CPU 空闲状态时间。
    2. 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当1个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。
  4. 为什么非公平锁效率更高
    1. 非公平锁在锁被释放时不保证任何特定的线程获取锁。如果一个线程刚好在锁被释放时请求锁,那么这个线程可以立即获取锁,而不需要经过上下文切换。这可以大大减少上下文切换的开销,提高效率。
  5. 什么时候用公平?什么时候用非公平
    1. 如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了;否则那就用公平锁,大家公平使用。
  6. 底层:AbstractQueuedSynchronizer简称AQS

4.5 可重入锁(递归锁)

  1. 指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁,这样的锁就叫做可重入锁。
    简单的来说就是:在一个synchronized修饰的方法或代码块的内部调用本类的其他synchronized修饰的方法或代码块时,是永远可以得到锁的
    如果是1个有 synchronized 修饰的递归调用方法,程序第2次进入被自己阻塞了岂不是天大的笑话,出现了作茧自缚
    所以Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
  2. 可重入锁种类:
    1. 隐式锁(即synchronized关键字使用的锁)默认是可重入锁:由于底层设计原理,天生自带可重入属性
      1. 同步方法、同步块
    2. Synchronized的重入的实现机理:
      1. 每个锁对象拥有一个锁计数器(_count)和一个指向持有该锁的线程的指针(_owner)
      2. 当执行monitorenter,如果目标锁对象的计数器为零,那么说明它没有被其他线程所持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将其计数器加1。
      3. 在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么 Java 虚拟机可以将其计数器加1,否则需要等待,直至持有线程择放该锁。
      4. 当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁己被释放。
    3. 显式锁(即Lock)也有ReentrantLock这样的可重入锁:
      1. 必须lock和unlock一一匹配,每一层嵌套都要匹配起来,虽然自己的线程依然可以进入,但是由于锁有一个线程监视器,所以不进行unlock的话,其他线程会被卡住,自己不会被卡住。

4.6 死锁及排查

  1. 定义:死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进租的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
  2. 哪些会导致死锁:原因
    1. 系统资源不足
    2. 进程运行推进的顺序不合适
    3. 资源分配不当
  3. 如何排查死锁
    1. jps -l 查看进程号
    2. jstack pid -> Found deadlock
    3. 或图形化jconsole
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值