JUC高级-0608

news2024/11/16 7:41:50

重新看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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/642611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Monocle3个性化分析作图:拟时热图/拟时基因GO分析/拟时基因趋势分析

Mnocle3往期精彩内容&#xff0c;因为monocle2有问题&#xff0c;且官网也放弃了monocle2&#xff0c;目前用的比较主流的拟时方法就是monocle3了。Mnocle3我们也写过全面的内容&#xff0c;不论是基础的分析还是个性化分析&#xff1a;Monocle3&#xff08;1&#xff09;&…

【C#】并行编程实战:并行编程简介

本章内容为多线程编程入门知识&#xff0c;旨在介绍多线程的特点&#xff0c;以及提供了C#部分基础的多线程API使用。 1、进程与线程 这一小节包含大量概念和基础知识&#xff0c;虽然建议阅读但确实比较枯燥。 可以直接跳到后面的实际应用的章节。 进程 狭义定义&#xff1a;正…

2.数据表的基本操作

SQL句子中语法格式提示&#xff1a; 1.中括号&#xff08;[]&#xff09;中的内容为可选项&#xff1b; 2.[&#xff0c;...]表示&#xff0c;前面的内容可重复&#xff1b; 3.大括号&#xff08;{}&#xff09;和竖线&#xff08;|&#xff09;表示选择项&#xff0c;在选择…

思科(Cisco)7000交换机软件版本升级步骤

思科&#xff08;Cisco&#xff09;交换机软件版本升级步骤 一、准备软件版本 在思科官方网站&#xff08;思科官网传送门&#xff09;下载你需要的系统版本文件&#xff0c;将软件版本准备好拷贝到U盘。 二、准备设备 将交换机加电启动&#xff0c;通过CRT- console进行连接…

使用JMeter进行接口高并发测试

一般的网络接口测试&#xff0c;功能性测试postman较为好用&#xff0c;需要测试高并发的情况下&#xff0c;可以用Jmeter来进行测试&#xff0c;postman是串行&#xff0c;而Jmeter可以多线程并行测试。 官网 Apache JMeter - Apache JMeter™正在上传…重新上传取消https://j…

100个句子记3500个单词

Typical of the grassland dwellers of the continent is the American antelope, or pronghorn. [ˈtɪpɪkl]典型[ˈɡrɑːslnd]草原[dweləz]居民[ˈkɒntɪnənt]大陆 [ˈntɪləʊp] [prɒŋhɔːn] 1.美洲羚羊&#xff0c;或称叉角羚&#xff0c;是该大陆典型的草原动物…

Gradle版本目录(Version Catalog)

Gradle版本目录(Version Catalog) “版本目录是一份依赖项列表&#xff0c;以依赖坐标表示&#xff0c;用户在构建脚本中声明依赖项时可以从中选择。” 我们可以使用版本目录将所有依赖项声明及其版本号保存在单个位置。这样&#xff0c;我们可以轻松地在模块和项目之间共享依…

react-useId

// App.tsx const id Math.random();export default function App() {return <div id{id}>Hello</div> }如果应用是CSR&#xff08;客户端渲染&#xff09;&#xff0c;id是稳定的&#xff0c;App组件没有问题。 但如果应用是SSR&#xff08;服务端渲染&#xff…

Spring Boot 属性配置解析

基于Spring Boot 3.1.0 系列文章 Spring Boot 源码阅读初始化环境搭建Spring Boot 框架整体启动流程详解Spring Boot 系统初始化器详解Spring Boot 监听器详解Spring Boot banner详解 属性配置介绍 Spring Boot 3.1.0 支持的属性配置方式与2.x版本没有什么变动&#xff0c;按照…

充电桩计量装置TK4800充电机(桩)现场校验仪检定装置

支持同时开展直流充电机现场校验仪和交流充电桩现场校验仪的检定工作&#xff0c;提高检定效率。 专用检定枪线&#xff1a;配有国标直流充电枪线及国标交流充电枪线&#xff0c;可直接接至交直流充电桩&#xff08;机&#xff09;现场校验仪开展检定工作&#xff0c;无需额外…

JMeter从数据库中获取数据并作为变量使用

目录 前言 1、JMeter连接MySQL数据库 2、线程组下新建一个 JDBC Connection Configuration 配置元件 3、实现数据库的查询-单值引用 4、实现数据库的查询-多值引用 总结&#xff1a; 前言 JMeter如何从数据库中获取数据并作为变量使用&#xff1f;这在我们使用JMeter做接…

企业转型在搭建BI时,需要注意什么

如今&#xff0c;商业智能BI在全世界范围内掀起了一股热潮&#xff0c;形成了一个庞大的市场&#xff0c;在信息化时代&#xff0c;企业需要借助BI来进行更好的成长。 在这种全新的社会、商业BI环境下&#xff0c;各行各业的企业都开始寻求探索新的商业模式&#xff0c;通过转…

Vue基本概念、vue-cli和插值表达式的快速使用

一、vue基本概念 &#xff08;一&#xff09;vue介绍 Vue (读音 /vjuː/&#xff0c;类似于 view) 是一套用于构建用户界面的渐进式javascript框架。 1. 渐进式的概念 渐进式&#xff1a;逐渐增强&#xff0c;可以在项目中使用vue的一部分功能&#xff0c;也可以使用vue的全…

网络安全实战植入后门程序

在 VMware 上建立两个虚拟机&#xff1a;win7 和 kali。 Kali&#xff1a;它是 Linux 发行版的操作系统&#xff0c;它拥有超过 300 个渗透测试工具&#xff0c;就不用自己再去找安装包&#xff0c;去安装到我们自己的电脑上了&#xff0c;毕竟自己从网上找到&#xff0c;也不…

GitHub 上“千金难求”!啃完这两本书,Spring在你面前便没有秘密

前言 Spring对Java程序员的重要性相信懂的都懂&#xff0c;夸张点甚至可以说是Spring成就了Java。 为什么说要啃这两本书。前者告诉你怎么用Spring&#xff0c;后者给你简单展示如何用的同时&#xff0c;还告诉你Spring是怎么实现的两者一起&#xff0c;让你知其然并知其所以…

在字节打酱油6年,被淘汰?太真实了...

涛子哥普通本科计算机专业毕业&#xff0c;目前在字节&#xff0c;部门是视频云中台。现在比较稳定&#xff0c;生活也算美满&#xff0c;算是个资深的打酱油高手&#xff0c;在字节也有6、7年左右的划水经验了。 刚好划水的时候在某乎上看到了一个问题&#xff1a;“软件测试会…

2023年Q1天猫电脑品类数据分析(含笔记本、游戏本、平板电脑)

目前&#xff0c;PC市场中正经历新旧产品的换代&#xff0c;在各行业消费复苏的背景下&#xff0c;PC市场的整体市场需求也有回暖的可能。结合鲸参谋平台上第一季度的销售数据&#xff0c;我们一起来看一看电脑市场当前的销售表现如何&#xff01; 笔记本电脑 尽管人们的消费需…

SPI FLASH Fatfs文件系统移植

一.FATFS文件系统简介 FATFS是面向小型嵌入式系统的FAT文件系统。他由C语言编写并且独立与底层I/O介质。支持的内核有&#xff1a;8051,PLC,ARV&#xff0c;ARM等。FATFS支持FAT12,FAT16,FAT32等文件系统格式。 官网链接 二.FATFS源码文件结构 diskio.c:包含底层存储介质的操…

linux搭建hadoop集群

linux搭建hadoop集群 1、创建4台虚拟机2、修改主机名3、配置网络4、配置hosts文件5、分配本地网络给虚拟机6、下载jdk&#xff0c;hadoop压缩包7、用xftp传输到虚拟机8、配置jdk9、配置hadoop10、创建脚本shell脚本&#xff0c;方便同步数据11、配置ssh免密登录12、同步jdk和ha…

希尔贝壳参与构建可信人工智能数据空间,助力大模型行业应用落地

2023年5月30日&#xff0c;由中国信息通信研究院、浙江省经济和信息化厅、杭州市人民政府、中国人工智能产业发展联盟主办的杭州通用人工智能发展论坛在未来科技城圆满落幕。本次会议以“大模型应用机遇和挑战”为主题&#xff0c;众多产学研代表现场参会&#xff0c;共同探讨人…