JUC高级编程

JUC高级编程

一 多线程基础知识

  • 一把锁

    synchronized

    • 修饰一个同步代码块, 作用的对象是调用这个代码块的对象.
    • 修饰一个方法, 该方法称为同步方法
    • 修饰一个静态的方法
    • 修饰一个类
  • 两个并

    • 并发(concurrent): 在同一实体上的多个事件 , 在同一台机器同时处理过个任务 , 同一时刻 , 只有一个事情发生
    • 并行(parallel) : 是在不同尸体上的多个事件 , 在多个处理器上同时处理多个任务 , 同一时刻 , 多个机器干多个事
  • 三个程

    • 进程 : 系统正在运行的一个应用程序 ; 程序一旦运行就是进程 , 每个进程都有它自己的内存空间和系统资源 , 进程是资源分配的最小单位
    • 线程 : 系统分配处理器时间资源的最小单位 , 或者说是进程之内独立执行的一个单元执行流 , 线程是程序执行的最小单位,也被称为轻量级进程 .
    • 管程 : Monitor(锁) , Monitor是一种同步机制 , 也就是我们平时说的锁, 保证了同一时刻只有一个进程在管程中活动 , 每个对象实例都会一个Monitor对象 , 执行线程首先要持有管程对象, 然后才能执行方法, 当方法完成之后会释放管程, 方法在执行时候会持有管程, 其他线程无法在获取同一个管程
  • 线程分类

    **用户线程: **平时用到的普通线程,自定义线程

    **守护线程: **运行在后台,是一种特殊的线程,eg: 垃圾回收

    **注意:**守护线程结束, 主线程不一定结束; 主线程结束, 守护线程一定结束

    Thread t1 = new Thread(()->{
             System.out.println(Thread.currentThread().getName()+"::"+Thread.currentThread().isDaemon());
    
         },"t1");
    //设置守护线程		
    t1.setDaemon(true);
    

二 CompletableFuture

2.1 Future接口知识

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

异步任务就是把一个大的任务分成好多小任务 , 需要这些小任务的返回值时, 主线程再获取就可以了

在这里插入图片描述

2.2 FutureTask异步任务

2.2.1 Future接口介绍

Future是java5新加的一个接口 , 它提供了一种异步并行计算的功能 , 如果主线程需要执行一些很耗时的计算任务 , 我们就会可以通过Future把这个任务放进异步线程中执行 , 主线程继续处理其他任务或先行结束 , 然后通过Future获取计算结果

2.2.2 Future接口相关构架
  • 目的 : 异步多线程任务执行且返回有结果

  • 三个特点 : 多线程 , 有返回 , 异步任务

  • 代码实现 : Runnable接口+Callable接口+Future接口+FutureTask实现类

在这里插入图片描述

FutureTask<String> futureTask = new FutureTask<>(()->{
        try {
            TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
        return "hello";
    });

    Thread thread = new Thread(futureTask);
    thread.start();
    
    try {
        System.out.println(futureTask.get());
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }
}
2.2.3 Future的优缺点
  • 优点 : Future + 线程池异步任务配合使用 , 能显著提高程序的运行效率
  • 缺点 :
    • get()阻塞 一旦调用get方法 , 必须要有一个返回值 , 否则会一直阻塞 ; 也可以在get方法里设置参数 , 设置要等待的时间
    • isDone()轮询 会一直循环访问 , 知道有结果
  • Future只能通过阻塞或轮询来得到任务的结果
/**
 * @AUTHOR TingFeng
 * @DATE 2023/10/18-9:09
 * @DESCRIPTION
 */
public class Demo2 {
    public static void main(String[] args) {
        FutureTask<String> futureTask = new FutureTask<>(()->{
            try {
                TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {e.printStackTrace();}
            return "结果";
        });

        Thread t = new Thread(futureTask);
        t.start();

        while (true){
            if (futureTask.isDone()){
                try {
                    System.out.println(futureTask.get());
                    break;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }else {
                try {TimeUnit.MILLISECONDS.sleep(500);
                    System.out.println("努力获得结果");
                } catch (InterruptedException e) {e.printStackTrace();}
            }
        }
    }
}
2.2.4 对于一些复杂任务
  • 简单任务完全可行
  • 回调通知:
    • 当Future完成后通知我 , 这就是回调通知
    • 通过轮询一直判断的话会占用cpu
  • 创建异步任务 : Future+线程池
  • 优化 : 使用CompletableFuture

2.3 CompletableFuture

jdk8设计出CompletableFuture , 它提供了一种类似观察者模式的机制 , 可以让任务执行完成后通过回调函数通知监听的一方

2.3.1 CompletableFuture介绍

类架构

在这里插入图片描述

  • 提供了强大的Future的扩展功能 , 简化异步编程的复杂性
  • 提供了函数式编程的能力 , 通过回调的方式处理结果
  • 它可能代表一个明确完成的Future , 也可能代表一个完成阶段
  • 支持在计算完成后触发一些函数或执行某些动作
2.3.2 静态方法
  • runAsync是无返回值的异步

  • supplyAsync是带返回值的异步

/**
 * @AUTHOR TingFeng
 * @DATE 2023/10/18-9:33
 * @DESCRIPTION
 */
public class Demo3 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}

        },pool);
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}
/**
 * @AUTHOR TingFeng
 * @DATE 2023/10/18-9:36
 * @DESCRIPTION
 */
public class Demo4 {
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(3);
        CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName());
            try {
                TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}
            return "hello";
        },pool);
        try {
            System.out.println(future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
}

CompletableFuture优点

  • 异步任务结束时 , 会自动回调某个对象的方法
  • 主线程设置好回调后 , 不用关心异步任务的执行 , 异步任务之间可以顺序执行
  • 异步任务出错 , 会自动回调某个对象的方法

2.4 电商网站的比价案例

2.4.1 扩展知识

函数式编程

函数式接口名方法名称参数返回值
Runnablerun无参数无返回值
Functionapply1个参数有返回值
Consumeaccept1个参数无返回值
Supplierget没有参数有返回值
BiConsumeraccept2个参数无返回值

案例 :

电商网站比价需求分析:

  1. 需求说明:

    1. 同一款产品,同时搜索出同款产品在各大电商平台的售价
    2. 同一款产品,同时搜索出本产品在同一个电商平台下,各个入驻卖家售价是多少
  2. 输出返回:

    1. 出来结果希望是同款产品的在不同地方的价格清单列表,返回一个List

例如:《Mysql》 in jd price is 88.05 《Mysql》 in taobao price is 90.43

  1. 解决方案,对比同一个产品在各个平台上的价格,要求获得一个清单列表

    1. step by step,按部就班,查完淘宝查京东,查完京东查天猫…
    2. all in,万箭齐发,一口气多线程异步任务同时查询
/**
 * @AUTHOR TingFeng
 * @DATE 2023/10/18-15:44
 * @DESCRIPTION
 */
public class Demo5 {

    static List<NetMall> list = Arrays.asList(
            new NetMall("jd"),
            new NetMall("dangdan"),
            new NetMall("meituan")
    );
    public static void main(String[] args) {
        long start = System.currentTimeMillis();

        List<String> price = getPrice(list,"mysql");
        for (String s : price) {
            System.out.println(s);
        }
        long end = System.currentTimeMillis();
        System.out.println(1.0*(end-start)/1000+"秒");
    }

    private static List<String> getPrice(List<NetMall> list, String name) {
        return list.stream().map(s-> CompletableFuture.supplyAsync(()->
            String.format(name+" in %s price is %.2f" , s.getNetMallName(),s.calcPrice(name))))
            .collect(Collectors.toList())
            .stream()
                .map(s->s.join())
                .collect(Collectors.toList());

    }

    private static List<String> getPrice1(List<NetMall> list, String name) {
        return list.stream().map(s->String.format(name+" in %s price is %.2f" , s.getNetMallName(),s.calcPrice(name)))
                .collect(Collectors.toList());
    }
}
class NetMall{

    @Getter
    private String  netMallName;

    public NetMall(String netMallName) {
        this.netMallName = netMallName;
    }

    public double calcPrice(String productName){
        try {
            TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
        return ThreadLocalRandom.current().nextDouble()*2+productName.charAt(0);
    }
}

结论:

  • 没用异步之前 , 耗时3秒多
  • 用了异步之后 , 耗时一秒多
2.4.2 常用方法
  1. 获得结果和触发计算

    • 获得结果
      • public T get()
      • public T get(long timeout , TimeUnit unit)
      • public T join()
      • public T get()
      • public T getNow(T valueAbsent) : 如果拿到结果就返回正常值 , 否则返回valueAbsent , 不阻塞
    • 触发计算
      • public boolean complete(T value) 是否打断get方法立即返回括号值
  2. 对计算结果进行处理

    • thenApply : 计算结果存在依赖关系 , 这两个线程串行化 , 由于存在依赖关系, 当前步骤有异常直接结束
    • handle : 计算结果存在依赖关系 , 这两个线程串行化 , 有异常继续走
    /**
     * @AUTHOR TingFeng
     * @DATE 2023/10/18-16:12
     * @DESCRIPTION
     */
    public class Demo6 {
        public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(3);
            CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
                try {
                    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
                return "1";
            },pool).thenApply(v->{
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}
                return v+"2";
            }).handle((v,e)->{
                //int i = 10/0;
                return v+"3";
            }).whenComplete((v,e)->{
                if (e==null){
                    System.out.println(v);
                }
            }).exceptionally(e->{
                e.printStackTrace();
                System.out.println(e.getMessage());
                return null;
            });
            System.out.println(Thread.currentThread().getName()+"有任务");
            pool.shutdown();
        }
    }
    
  3. 对计算结果进行消费

    • 消费结果 , 不进行返回
    • thenaccept

    补充:

    • thenRun(Runnable Runnable) : 任务A执行完执行任务B , 并且不需要A的结果
    • thenAccept(Consumer action) : 任务A执行完执行B, B需要A的结果 , 但B没有返回值
    • thenApply(Function f) : 任务A执行完执行B, B需要A的结果 , 同时B有返回值

三 java锁

3.1 乐观锁和悲观锁

  • 乐观锁

    认为自己在使用数据的时候不会有别的线程修改数据,不会添加锁, 只是在更新的时候去判断 , 之前有没有别的线程更新了这个数据 , 如果数据没有更新 , 当前线程将自己修改的数据成功写入 , 如果被其他线程更新 , 则根据不同的实现方式执行不同的操作 ,

    最常用的是CAS自选

  • 悲观锁

    认为自己在修改数据的时候一定有别的线程来修改数据 , 因此在获取数据的时候先加锁 , 确保数据不会被别的线程修改 , synchronized和Lock的实现类都是悲观锁 , 适合写操作多的场景

3.2 synchronized的三种应用方式

  • 作用于实例方法 , 当前实例加锁 , 进入同步代码前要获取当前实例的锁
  • 作用于代码块 , 锁的是括号里的对象
  • 作用于静态方法, 当前类加锁 , 进入同步代码块要获得当前类对象的锁

3.3 公平锁和非公平锁

3.3.1 介绍
  • 公平锁

    多个线程按照申请锁的顺序来获取锁 , 类似排队, 先到先得 .

    Lock lock= new ReentrantLock(true) 就是一个公平锁

  • 非公平锁

    多个线程获取锁的顺序不是按照申请的顺序 , 谁抢到锁就是谁的 , 在高并发下, 有可能造成线程饥饿的状态

    Lock lock= new ReentrantLock() 默认非公平锁

**面试题: **

  • 为什么会有公平锁/非公平锁的设计?为什么默认非公平?

    • 恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但是从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分地利用CPU的时间片,尽量减少CPU空间状态时间
    • 使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得很大,所以就减少了线程的开销。
  • 什么时候用公平?什么时候用非公平?

    • 如果为了更高的吞吐量,很显然非公平锁是比较合适的,因为节省了很多线程切换的时间,吞吐量自然就上去了;否则就用公平锁,大家公平使用。

3.4 可重入锁(递归锁)

  • 介绍

    同一线程在外层方法获取得到锁的时候 , 在进入该线程的内层方法会自动获取锁 , (前提: 锁对象是同一个对象) , 不会因为之前已经获取过还未释放而阻塞-----------------一定程度可避免死锁

  • 分类

    • 隐式锁 (synchronized使用的锁) . 默认是可重入锁

      • 在一个synchronized修饰的方法或者代码块的内部调用本类的其他synchronized修饰的方法或者代码时 , 是永远可以得到锁
      public class ReEntryLockDemo {
      
          public static void main(String[] args) {
              final Object o = new Object();
              new Thread(() -> {
                  synchronized (o) {
                      System.out.println("外层调用");
                      synchronized (o) {
                          System.out.println("中层调用");
                          synchronized (o) {
                              System.out.println("内层调用");
                          }
                      }
                  }
              }, "t1").start();
              
          }
      }
      
    • 显式锁(Lock) 也有ReentrantLock

      Lock lock = new ReentrantLock();
              new Thread(() -> {
                  lock.lock();
                  try {
                      System.out.println("外层调用");
                      lock.lock();
                      try {
                          System.out.println("中层调用");
                          lock.lock();
                          try {
                              System.out.println("内层调用");
                          } finally {
                              lock.unlock();
                          }
                      } finally {
                          lock.unlock();
                      }
                  } finally {
                      lock.unlock();
                  }
              }, "t2").start();
      
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值