3.5 多线程

2021/4/13 18:29:26

本文主要是介绍3.5 多线程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

5.1 线程与进程

5.1.1 概念

  1. 进程:一个内存中运行的应用程序,每个进程都有一个独立的内存空间
  2. 线程:进程中的一个执行路径共享一个内存空间,线程之间可以自由切换,并发执行,一个进程最少有一个线程

5.1.2 线程调度

  • java采用抢占式调度。多线程不能提高运行速度,可以提高运行效率,提高CPU使用率。

5.1.3 同步与异步

  • 同步:排队执行,效率低但是安全
  • 异步:同时执行,效率高但是不安全

5.1.4 并发与并行(面试题)

  • 并发:两个或多个事件在同一个时间段内发生
  • 并行:两个或多个事件在同一时刻发生

5.2 多线程

5.2.1 实现途径

  1. 继承Thread类
  • 重写run方法,通过start方法调用
  1. 实现Runnable接口
  • 重写run方法,创建一个线程并为其分配任务
  1. 实现Callable接口(较少使用)
    //  1. 编写类实现Callable接口 , 实现call方法 
    class XXX implements Callable<T> { 
        @Override public <T> call() throws Exception { 
            return T; 
        } 
    } 
    //  2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象 
    FutureTask<Integer> future = new FutureTask<>(callable); 
    //  3. 通过Thread,启动线程 
    new Thread(future).start();

Runnable与Callable异同

  • 相同点:
  • 都是接口
  • 都可以编写多线程
  • 都采用Thread.start()启动线程
  • 不同点:
  • Runnable没有返回值;Callable可以返回执行结果
  • Callable接口的call()允许抛出异常;Runnable的run()不能抛出

Callable获取返回值

  • 调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执行,如果不调用不会阻塞。

5.2.1.1 Runnable比Thread的优势

  1. 更适合多个线程执行相同任务的情况
  2. 避免单继承带来的局限性
  3. 任务与线程分离,提高程序健壮性
  4. 线程池技术只接受Runnable类型任务,不接受Thread线程

5.2.2 线程中断

  • 提醒线程该中断
  • 添加中断标记:interrupt

5.2.3 进程同步方法

  1. 法一:同步代码块
  • run()方法内通过将同一对象传入synchronized(),可调用this关键字或提前实例化一个对象
  1. 法二:同步方法
  • 方法权限修饰符后添加synchronized,通过同一任务不同线程的start方法调用
    * 若添加了同步代码块和同步方法,若某一线程抢到资源,则其他线程不能执行同步方法中的内容
  1. 法三:显式锁
  • run()方法前创建锁对象 Lock l=new ReentrantLock();
  • 上锁:l.lock(); 解锁:l.unlock();

5.2.3 显式锁和隐式锁区别

  1. 两者出身不同
  • synchronized是Java中的关键字,由JVM维护,是JVM层面的锁;
  • lock是JDK5之后才出现的具体的类,使用Lock是调用对应的API,是API层面的锁。
  1. 使用方式不同
  • synchronized隐式锁;lock是显式锁
  • 显式锁和隐式锁的区别在于:使用显式锁的时候,使用者需要手动去获取和释放锁。如果没有释放锁,就可能出现死锁的现象
  1. 加锁的时候是否可以公平
  • Sync:非公平锁
  • lock:默认是非公平锁。在其构造方法的时候可以传入Boolean值。true:公平锁
    (原文链接:https://zhuanlan.zhihu.com/p/131286865)

5.2.4 线程阻塞

  • 在某一时刻某一个线程在运行一段代码的时候,这时候另一个线程也需要运行,但是在运行过程中的那个线程执行完成之前,另一个线程是无法获取到CPU执行权的,这个时候就会造成线程阻塞。

5.2.5 公平锁和非公平锁

  • 公平锁:保证获取锁的线程按照先来后到的顺序
  • 非公平锁:获取锁的顺序和申请锁的顺序不一定一致

5.2.6 死锁

  • 两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。

5.2.7 线程六种状态

  1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
  2. 运行(RUNNABLE):Java线程中将就绪(ready)和**运行中(running)****两种状态笼统的称为“运行”。
    线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取CPU的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得CPU时间片后变为运行中状态(running)。
  3. 阻塞(BLOCKED):在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
  4. 等待(WAITING):处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。(wait方法)
  5. 超时等待(TIMED_WAITING):处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。(sleep方法)
  6. 终止(TERMINATED):表示该线程已经执行完毕。

5.2.8 线程池(较少使用)

5.2.8.1 分类

  1. 缓存线程池
  2. 定长线程池
  3. 单线程线程池
  4. 周期性任务定长线程池

5.2.8.2 应用

    /*** 缓存线程池. 
    * (长度无限制) 
    * 执行流程: 
    * 1. 判断线程池是否存在空闲线程 
    * 2. 存在则使用 
    * 3. 不存在,则创建线程 并放入线程池, 然后使用 
    */ 
    ExecutorService service = Executors.newCachedThreadPool(); 
    //向线程池中 加入 新的任务 
    service.execute(new Runnable() { 
        @Override public void run() {        
            System.out.println("线程的名称:"+Thread.currentThread().getName()); 
        } 
    }); 
    /*** 定长线程池. 
    * (长度是指定的数值) 
    * 执行流程:
    * 1. 判断线程池是否存在空闲线程 
    * 2. 存在则使用 
    * 3. 不存在,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    * 4. 不存在,且线程池已满的情况下,则等待线程池存在空闲线程 
    */ 
    ExecutorService service = Executors.newFixedThreadPool(2); //线程池个数
    service.execute(new Runnable() { 
        @Override public void run() {        
            System.out.println("线程的名称:"+Thread.currentThread().getName()); 
        }
    });
    //效果与定长线程池 创建时传入数值1 效果一致. 
    /*** 单线程线程池. 
    * 执行流程: 
    * 1. 判断线程池 的那个线程 是否空闲 
    * 2. 空闲则使用 
    * 3. 不空闲,则等待 池中的单个线程空闲后 使用 
    */ 
    ExecutorService service = Executors.newSingleThreadExecutor(); 
    service.execute(new Runnable() { 
        @Override public void run() {
            System.out.println("线程的名称:"+Thread.currentThread().getName()); 
        } 
    });
    /*** 周期任务 定长线程池. 
    * 执行流程: 
    * 1. 判断线程池是否存在空闲线程 
    * 2. 存在则使用 
    * 3. 不存在,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用 
    * 4. 不存在,且线程池已满的情况下,则等待线程池存在空闲线程 
    */
    
    /*** 格式1:
    * 参数1. runnable类型的任务 
    * 参数2. 时长数字 
    * 参数3. 时长数字的单位 
    */ 
    ScheduledExecutorService service = Executors.newScheduledThreadPool(1); 
    service.schedule(new Runnable() { 
        @Override public void run() { 
            System.out.println("xxx"); 
        }
    },5,TimeUnit.SECONDS); //5s后执行
    
   /*** 格式2:
   * 参数1. runnable类型的任务 
   * 参数2. 时长数字(延迟执行的时长) 
   * 参数3. 周期时长(每次执行的间隔时间) 
   * 参数4. 时长数字的单位 
   */
   service.scheduleAtFixedRate(new Runnable() { 
        @Override public void run() { 
            System.out.println("俩人相视一笑~ 嘿嘿嘿"); 
        } 
    },5,2,TimeUnit.SECONDS);//5s后执行,每隔2s执行

5.3 Lambda表达式

5.3.1 概念

  • Lambda 表达式是 JDK8 的一个新特性,可以取代大部分的匿名内部类.

5.3.2 作用与要求

  • 对某些接口进行简单的实现,但并不是所有的接口都可以使用 Lambda 表达式来实现。Lambda 规定接口中只能有一个需要被实现的方法,不是规定接口中只能有一个方法
  • @FunctionalInterface
    修饰函数式接口的,要求接口中的抽象方法只有一个。 这个注解往往会和 lambda 表达式一起出现。
  • 语法形式为 () -> {},其中 () 用来描述参数列表,{} 用来描述方法体,-> 为 lambda运算符 ,读作(goes to)


这篇关于3.5 多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程