Java多线程【异步编排】

1. 线程和进程

1.1 进程

本质是一个运行的程序,是系统资源分配的基本单位,每一个进程都有一个独立的内存地址空间:(包括代码空间,数据空间),每一个进程内存空间,数据空间都是私有的,不能被其他的进程访问:进程与进程之间相互隔离的。

1.2 线程

线程是为了让CPU执行更高效提出的。

  • 进程中的所有资源都被线程共享
  • 线程是CPU分配的基本单位
  • 线程之间操作共享资源进行通信。
  • 线程比进程通信更简单、高效

1)上下文切换

问题1: 现在的机器可以运行远大于CPU核心数的线程运行,操作资源是如何分配的?

CPU采用时间分片的模式,将CPU的时间片段轮流分配给多个线程执行;分配给每个线程的时间大概是几十毫秒ms,线程在CPU时间片执行,如果当前线程没有执行结束,CPU时间分片已经结束,此线程就会被挂起,下一个时间分片将会分给下一个线程,上一个线程等待唤醒。

问题2: 线程没有执行完毕,线程别挂起,等待下次被唤醒,继续完成任务!系统是怎么知道线程之前运行到哪里?从哪里开始执行呢??

CPU包含

  • 寄存器(存储数据状态信息)
  • 程序计数器:存储CPU正在执行的指令,以及执行下一条指令的位置。

CPU在执行任务时候,都必须依赖寄存器,程序计数器;这些东西就是CPU切换的开销,称之为上下文切换

2)线程的调度

  1. 抢占式调度
  2. 协同式调度

问题1:操作系统如何共享CPU时间分片,如何分配CPU执行权限?线程何时分到时间分片?线程分配多长时间?如何给重要的线程多分配一点时间,次要的少分配点时间呢?

(1)抢占式调度

并发线程下,所有线程会抢占时间分片,获取执行权限。有些线程执行时间长,造成线程堵塞等待,等待CPU资源。

image-20221013091730163

JVM线程调度:抢占式调度模式

(2)协同式调用

一个线程执行完成后主动通知系统切换到另一个线程执行;(同步堵塞)

致命缺点:一个线程堵塞,则会导致整个进程堵塞,一个线程异常,则会导致整个进程崩溃

3)并行和并发

  • 并发:一段时间内,多个线程轮流分配时间分片,抢占式执行。
  • 并行:同一时刻,多个线程同时执行。

2. 多线程实践

2.1 多线程的实现方式

  1. 继承Thread类,实现多线程

    public static void main(String[] args) {
          
        System.out.println("thread.........start...........");
        /* 1. 测试Thread实现的多线程 */
        // 创建多线程对象
        ThreadA threadA = new ThreadA();
        // 开启线程
        threadA.start();
        System.out.println("thread.........end.............");
    }
    
    
    public static class ThreadA extends Thread {
         
        /**
         * run方法是线程执行主体,多线程任务在run方法中执行。
         */
        @Override
        public void run() {
         
            log.info("继承Thread实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{},线程的名称:{},线程的ID:{}", i, this.getName(), this.getId());
        }
    }
    

    总结:多线程线程的执行,是在主线程执行完毕后再执行的。(体现了异步的效果)

  2. 实现Runnable接口,实现多线程

    (1)普通构建方式

    public static void main(String[] args) {
          
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // 创建多线程对象
        Thread02 thread02 = new Thread02();
        // 构建多线程对象
        Thread thread = new Thread(thread02);
        // 开启线程
        thread.start();
        System.out.println("thread.........end.............");
    }
    
    public static class Thread02 implements Runnable {
         
        /**
         * run方法是线程执行主体,多线程任务在run方法中执行。
         */
        @Override
        public void run() {
         
            log.info("实现Runnable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
        }
    }
    

    (2)匿名内部类实现方式

    public static void main(String[] args) {
          
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // 创建多线程对象
        Runnable runnable = new Runnable() {
         
            @Override
            public void run() {
         
                log.info("实现Runnable接口的实现方式.....");
                // 业务代码执行
                int i = 100 / 3;
                log.info("业务代码执行的结果为:{}", i);
            }
        };
        // 构建多线程对象
        Thread thread = new Thread(runnable);
        // 开启线程
        thread.start();
        System.out.println("thread.........end.............");
    }
    

    (3)lambda表达式方式(推荐使用,但需要了解下lambda表达式实现原理)

    public static void main(String[] args) {
          
        System.out.println("thread.........start...........");
        /* 测试实现Runnable接口,实现多线程 */
        // lambda创建多线程对象,直接传入Thread构造参数中,并开启线程
        new Thread(() -> {
         
            log.info("实现Runnable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
        }).start();
        System.out.println("thread.........end.............");
    }
    
    

    lambda表达式的特点:
    1. @FunctionalInterface:表示可以使用lambda表达式编程,此注解相当于一个标识
    2. 接口如果只有一个方法(必须满足),即使没有上面的注解,也可以使用lambda表达式;程序会在后台自动识别。

    lambda表达式写作形式:方法括号(有参写,无参不写) -> {业务执行方法体}

  3. Callable + FutureTask实现多线程

    jdk1.5后: 添加Callable接口,实现多线程,相较于Thread和Runnable接口而言,Callable有返回值。

    @FunctionalInterface :表示支持lambda表达式
    public interface Callable<V>

    1. 具有泛型的接口,只有一个call方法,call方法就是多线程执行业务主体。
    2. 方法有返回值,返回值类型就是指定的泛型类型。

    疑问: 由于多线程执行必须和Thread有关系,Callable和Thread有什么关系呢?

    1. 因为Runnable的实现类FutureTask即创建了Runnable接口的构造函数,也创建了Callable接口的构造函数

    2. 同时Thread类需要的是一个对Runnable接口的实现类,而RunnableFuture是继承于Runnable的,FutureTaskRunnableFuture实现类,所以通过FutureTask构造的多线程对象可以直接传入Thread使用。。

    image-20221013113340237

    (1)普通实现方式

    public static void main(String[] args) {
         
        System.out.println("thread.........start...........");
        // 创建多线程对象
        Thread03 thread03 = new Thread03();
        // 创建FutureTask对象,将thread03对象传递到构造函数中
        FutureTask<Integer> task = new FutureTask<>(thread03);
        // 创建多线程对象
        Thread thread = new Thread(task);
        // 开启线程执行
        thread.start();
    
        try {
         
            // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
            Integer integer = task.get();
            System.out.println("子程序运行结果为:" + integer);
        } catch (InterruptedException | ExecutionException e) {
         
            throw new RuntimeException(e);
        }
        System.out.println("thread.........end.............");
    }
    
    
    public static class Thread03 implements Callable<Integer> {
         
        /**
         * 业务执行主体
         * @return 返回值
         * @throws Exception 异常捕获
         */
        @Override
        public Integer call() throws Exception {
         
            log.info("实现Callable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
            return i;
        }
    }
    

    (2)匿名内部类实现方式

    public static void main(String[] args) {
         
       System.out.println("thread.........start...........");
       // 创建多线程对象
       Callable<Integer> callable = new Callable<Integer>() {
         
           @Override
           public Integer call() throws Exception {
         
               log.info("实现Callable接口的实现方式.....");
               // 业务代码执行
               int i = 100 / 3;
               log.info("业务代码执行的结果为:{}", i);
               return i;
           }
       };
       // 创建FutureTask对象,将Callable接口对象传递到构造函数中
       FutureTask<Integer> task = new FutureTask<>(callable);
       // 创建多线程对象
       Thread thread = new Thread(task);
       // 开启线程执行
       thread.start();
       try {
         
           // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
           Integer integer = task.get();
           System.out.println("子程序运行结果为:" + integer);
       } catch (InterruptedException | ExecutionException e) {
         
           throw new RuntimeException(e);
       }
    
       System.out.println("thread.........end.............");
    }
    

    (3)lambda表达式实现方式

    public static void main(String[] args) {
         
        System.out.println("thread.........start...........");
        // 创建FutureTask对象,通过lambda表达式实现Callable接口
        FutureTask<Integer> task = new FutureTask<>(() -> {
         
            log.info("实现Callable接口的实现方式.....");
            // 业务代码执行
            int i = 100 / 3;
            log.info("业务代码执行的结果为:{}", i);
            return i;
        });
        // 创建多线程对象
        Thread thread = new Thread(task);
        // 开启线程执行
        thread.start();
        try {
         
            // 等待子线程执行结束后,获取返回结果,该代码为同步阻塞,必须等待异步线程执行结束,且返回结果后,才能继续往下执行。
            Integer integer = task.get();
            System.out.println("子程序运行结果为:" + integer);
        } catch (InterruptedException | ExecutionException e) {
         
            throw new RuntimeException(e)
  • 4
    点赞
  • 44
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值