多线程学习知识点笔记

  • Post author:
  • Post category:其他




多线程学习



多线程的实现方案一:继承Thread类



1、为什么不直接调用了run方法,而是调用start启动线程。

  • 直接调用run方法会当成普通方法执行,此时相当于还是单线程执行。



多线程的实现方案二:实现Runnable接口

  • 定义一个线程任务类MyRunnable实现Runnable接口,重写run()方法
  • 创建MyRunnable任务对象
  • 把MyRunnable任务对象交给Thread处理。
  • 调用线程对象的start()方法启动线程



优点:

线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。



缺点:

编程多一层对象包装,如果线程有执行结果是不可以直接返回的。



多线程的实现方案三:Callable和FutureTask



前2种线程创建方式都存在一个问题:

  • 他们重写的run方法均不能直接返回结果。
  • 不适合需要返回线程执行结果的业务场景。



使用方法:

  • 得到任务对象

    定义类实现Callable接口,重写call方法,封装要做的事情。

    用FutureTask把Callable对象封装成线程任务对象。

  • 把线程任务对象交给Thread处理。

  • 调用Thread的start方法启动线程,执行任务、线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果。



方式三优缺点:

  • ·优点:

    线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。

    可以在线程执行完毕后去获取线程执行的结果。

  • 缺点:

    编码复杂一点。

    image-20220814230033739



Thread常用的构造器

image-20220814231610651

image-20220814231631776



解决线程安全的方法



方式一:同步代码块

  1. 作用:把出现线程安全问题的核心代码给上锁。
  2. 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行。

synchronized(同步锁对象){

​ 操作共享资源的核心代码(核心代码)

}



  • 锁对象要求

    锁对象只要对于当前同时执行的线程来说是同一个对象即可。

    规范上:建议使用共享资源作为锁对象

    ​ 锁对象不可以使用任意唯一的对象,这样会影响到其他无关线程的执行

    ​ 对实例方法可以用调用该锁地址(this)作为锁对象

    ​ 对于静态方法建议使用字节码(类名.class)对象作为锁对象。



  • 同步代码块是如何实现线程安全的?

    对出现问题的核心代码使用synchronized进行加锁

    每次只能一个线程占锁进入访问



方式二:同步方法



同步方法

  • 作用:把出现线程安全问题的核心方法给上锁。
  • 原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

image-20220815145346310



同步方法底层原理

  • 同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码。
  • 如果方法是实例方法:同步方法默认用this作为的锁对象。但是代码要高度面向对象!
  • 如果方法是静态方法:同步方法默认用类名.class作为的锁对象。



同步方法和同步代码块的差别

  • 同步代码块锁的范围更小
  • 同步方法锁的范围更大



方式三:Lock锁

  • 为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,更加灵活、方便。
  • Losk实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。
  • Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来构建Lock锁对象

image-20220815153441499



线程通信



什么是线程通信、如何实现?

  • 所谓线程通信就是线程间相互发送数据,线程通信通常通过共享一个数据的方式实现。
  • 线程间会根据共享数据的情况决定自己该怎么做,以及通知其他线程怎么做。



线程通信常见模型

  • 生产者与消费者模型:生产者线程负责生产数据,消费者线程负责消费数据。
  • 要求:生产者线程生产完数据后,唤醒消费者,然后等待自己;消费者消费完该数据后,唤醒生产者,然后等待自己。



Object类的等待和唤醒方法:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-c71sN0Wq-1660659468969)(https://gitee.com/mazzz777/pic-go-warehouse/raw/master/202208151711514.png)]


注意

:上述方法应该由锁对象去调取



线程池



什么是线程池?

  • 线程池就是一个可以

    复用线程

    的技术。



不使用线程池的问题

  • 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程的开销是很大的,这样会严重影响系统的性能。

    例如

    淘宝系统,有100W人同时来访问,如果不使用线程池就意味着要创建100W个线程,这个并不可能,并且极大的浪费了系统的资源。

image-20220815201437260



谁代表线程池?

  • JDK5.0起提供了代表线程池的接口:ExecutorService



如何得到线程池对象


  • 方式一

    (更通用):使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
ExecutorService pool = new ThreadPoolExecutor(3, 5, 6,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(5),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy());

image-20220815202850949

  • 方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

image-20220815203033628



临时线程什么时候创建啊?

  • 新任务提交时发现核心线程都在忙 ,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。



什么时候会开始拒绝任务?

  • 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。



ExecutorService常用方法



新任务拒绝策略

image-20220815204113113



ExecutorService方法

image-20220815204228607

一般核心线程回去处理自己的任务和等待队列中的任务,如果核心线程正在忙,并且等待队列已满,就会触发创建临时线程来参与工作。如果当创建临时线程达到上限之后,依然在提供任务,就会触发任务拒绝,并且抛出异常。



Exccutors创建线程池对象(动态线程池)



常用方法

image-20220815225031909



使用固定线程池的方法:

Executors.newFixedThreadPool(n) n为固定线程数量



使用Executors可能存在的风险

image-20220815230044714

  • 第一个:任务队列没有限制,可能存在内存溢出的风险
  • 第二个:同样没有任务队列限制,可能存在内存溢出的风险
  • 第三个:任务没有限制,创造线程也没有限制,内存溢出
  • 第四个:限制了核心线程数量,但是没有限制临时线程的数量,总成内存溢出



推荐的模式

阿里的规范文档建议我们使用ThreadPoolExecutor的方式去创建线程池,参数可控,并且可以直观看线程池创建的规则。



定时器

  • 定时器是一种控制任务延时调用,或者周期调用的技术。
  • 作用:闹钟、定时邮件发送。



定时器的实现方式

  • 方式一:Timer
  • 方式二:ScheduledExecutorService



Timer定时器



Timer定时器的特点和存在的问题

1、Timer是

单线程

,处理多个任务按照顺序执行,存在延时与设置定时器的时间出入。

2、可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行。

image-20220816212855303



ScheduledExecutorService定时器

  • ScheduledExecutorService是jdk1.5中引入了并发包,目的是为了弥补Timer的缺陷ScheduledExecutorService内部为线程池。

image-20220816212954829



ScheduledExecutorService的优点

  1. 基于线程池,某个任务的执行情况不会影响其他定时任务的执行。
  2. 如果有任务出现了问题,就会对问题线程进行拦截,不影响别的线程继续运行



并发与并行

  • 正在运行的程序(软件)就是一个独立的进程,线程是属于进程的,多个线程其实是并发与并行同时进行的。



并发的理解:

  • CPU同时处理线程的数量有限。
  • CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发。



并行的理解

  • 在同一时刻上,同时多个线程在被CPU处理并执行



简单说说并发和并行的含义

·并发:CPU分时轮询的执行线程。

·并行:同一个时刻同时在执行。



线程的生命周期



线程的状态

  • 线程的状态:也就是线程从生到死的过程,以及中间经历的各种状态及状态转换。
  • 理解线程的状态有利于提升并发编程的理解能力。



Java线程的状态

  • Java总共定义了6种状态。
  • 6种状态都定义在Thread类的内部枚举类中。

image-20220816220903646

image-20220816221509980

image-20220816221550406



版权声明:本文为qq_30578751原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。