java多线程
这部分可以多参考剑指offer
https://blog.csdn.net/evankaka/article/details/44153709#t3
https://www.cnblogs.com/gaopeng527/p/4234211.html
https://www.cnblogs.com/lixuan1998/p/6937986.html
https://www.cnblogs.com/felixzh/p/6036074.html
并发编程三要素
(1)原子性
原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。
(2)可见性
可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。
(3)有序性
有序性,即程序的执行顺序按照代码的先后顺序来执行
并发编程中线程的通信和同步
通信方式:共享内存和消息传递
同步:线程之间通过发送消息来显式进行 通信。
Java中实现多线程有几种方法
主要有四种:
继承Thread类、
实现Runnable接口、
通过ExecutorService、Callable实现有返回值的线程
基于线程池
创建线程方式的对比
(1)采用实现 Runnable、Callable 接口的方式创建多线程。
优势是: 线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。在这种方式下,多个线程可以共享同一个 target 对象,所以非常适合多个相同线程来处理同一份资源的情况。
(2)使用继承 Thread 类的方式创建多线程
优势是: 编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread()方法,直接使用 this 即可获得当前线程。
劣势是: 线程类已经继承了 Thread 类,所以不能再继承其他父类。
(3)通过线程池创建
Runnable 和 Callable 的区别
1、Callable 规定(重写)的方法是 call(),Runnable 规定(重写)的方法是 run()。
2、Callable 的任务执行后可返回值,而 Runnable 的任务是不能返回值的。
3、Call 方法可以抛出异常,run 方法不可以。
4、运行 Callable 任务可以拿到一个 Future 对象,表示异步计算的结果。通过 Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
进程有哪几种状态?(一般说三种)
- 创建状态(new) :进程正在被创建,尚未到就绪状态。
- 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
- 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
- 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
- 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
订正:下图中 running 状态被 interrupt 向 ready 状态转换的箭头方向反了。
什么是上下文切换
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。
Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
线程的生命周期 或者五种基本状态和过程
(1)创建状态(New):
当线程对象对创建后,即进入了新建状态,如:Thread t= new MyThread();
(2)就绪状态(Runnable):
当调用线程对象的 start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待 CPU 调度执行,并不是说执行了t.start()此线程立即就会执行;
(3)运行状态(Running):
当 CPU 开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
(4)阻塞状态(Blocked):
处于运行状态中的线程由于某种原因,暂时放弃对 CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被 CPU 调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1)等待阻塞:运行状态中的线程执行 wait()方法,使本线程进入到等待阻塞状态;
2)同步阻塞:线程在获取 synchronized 同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3)其他阻塞:通过调用线程的 sleep()或 join()或发出了 I/O 请求时,线程会进入到阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O 处理完毕时,线程重新转入就绪状态。
(5)死亡状态(Dead): 线程执行完了或者因异常退出了 run()方法,该线程结束生命周期。
如何停止一个正在运行的线程
-
正常运行结束
-
使用退出标志(isinterrupt()方法:这个方法由对象调用,测试线程是否已经被中断,仅仅返回标志的状态,不会对标志有任何影响。),使线程正常退出,也就是当run方法完成后线程终止。
-
使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
-
使用interrupt方法中断线程。
锁
什么是死锁
死锁是指多个进程因竞争资源而造成的一种僵局(互相等待) , 若无外力作用,这些进程都将无法向前推进。
死锁产生的四个必要条件
互斥条件: 在一段时间内某资源仅为一个进程所占有。 此时若有其他进程请求该资源, 则请求进程只能等待。
不剥夺条件: 进程所获得的资源在未使用完毕之前, 不能被其他进程强行夺走, 即只能由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件: 一个进程因请求资源而阻塞时,对已获得的资源保持不放。
循环等待条件: 若干进程之间形成一种头尾相接的循环等待资源关系。
避免死锁 四种主要处理方法:
鸵鸟策略:直接忽略死锁。因为解决死锁问题的代价很高,因此鸵鸟策略这种不采取任务措施的方案会获得更高的性能。当发生死锁时不会对用户造成多大影响,或发生死锁的概率很低,可以采用鸵鸟策略。
死锁避免:简单来说就是破坏死锁产生的四个必要条件。
- 避免一个进程同时获取多个锁
- 避免一个进程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
死锁检测和解除:
如何检测死锁:检测有向图是否存在环;或者使用类似死锁避免的检测算法。
死锁解除的方法:
- 利用抢占:挂起某些进程,并抢占它的资源。但应防止某些进程被长时间挂起而处于饥饿状态;
- 利用回滚:让某些进程回退到足以解除死锁的地步,进程回退时自愿释放资源。要求系统保持进程的历史信息,设置还原点;
- 利用杀死进程:强制杀死某些进程直到死锁解除为止,可以按照优先级进行。
线程通信
全局变量,消息队列
进程/线程 同步
临界区,互斥量,信号量,事件
sleep wait yield notify notifyAll join
一. Sleep 与 wait 区别
1.sleep 是线程类(Thread) 的方法, 导致此线程暂停执行指定时间, 给执行机会给其他线程, 但是监控状态依然保持, 到时后会自动恢复。 调用 sleep 不会释放对象锁。 sleep()使当前线程进入阻塞状态, 在指定时间内不会执行。
2.wait 是 Object 类的方法, 对此对象调用 wait 方法导致本线程放弃对象锁, 进入等待此对象的等待锁定池, 只有针对此对象发出 notify 方法(或 notifyAll) 后本线程才进入对象锁定池准备获得对象锁进入运行状态。
区别比较:
1、 这两个方法来自不同的类分别是 Thread 和 Object
2、 最主要是 sleep 方法没有释放锁, 而 wait 方法释放了锁, 使得其他线程可以使用同步控制块或者方法。
3、 wait, notify 和 notifyAll 只能在同步控制方法或者同步控制块里面使用, 而 sleep 可以在任何地方使用(使用范围)
4、 sleep 必须捕获异常, 而 wait, notify 和 notifyAll 不需要捕获异常
Thread 类中的start() 和 run() 方法有什么区别?
new 一个 Thread,线程进入了新建状态。调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。 但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。
总结: 调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。
线程池的核心组件和核心类
线程池管理器:用于创建并管理线程池
工作线程:线程池中执行具体任务的线程
任务接口:用于定义工作线程的调度和执行策略,线程只有实行了改接口,线程中的任务才能被线程池调度
任务队列:存放待处理的任务,新的任务会不断的被加入队列中,执行完成的任务将会被从队列中移除。
核心类:P63
ThreadPoolExecutor线程池的核心类构造函数。
corePoolSize:指定了线程池中的主线程数量;
maximumPoolSize:指定了线程池中的最大线程数量;
keepAliveTime:当线程池中空闲线程数量超过corePoolSize时,多余的线程会在多长时间内被销毁;
unit:keepAliveTime的单位;
workQueue:任务队列,被提交但是还没有执行的任务存放的地方
threadFactory:线程工厂,用于创建线程,一般用默认即可;
handler:拒绝策略;
线程池工作原理用线程池的好处
当提交一个新任务到之后, 线程池的处理流程如下:
- 线程复用: 通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理型。 使用线程池可以进行统一分配、 调优和监控。
常用的线程池有哪些?
Executors.newFixedThreadPool(int):创建一个固定线程数量的线程池,可控制线程最大并发数,超出的线程需要在队列中等待。注意它内部corePoolSize和maximumPoolSize的值(就是第一和第二个参数 nThreads)是相等的,并且使用的是LinkedBlockingQueue(背后使用的是无界的工作队列):
Executors.newSingleThreadExecutor():创建一个单线程的线程池,它只有唯一的线程来执行任务,保证所有任务按照指定顺序执行。注意它内部corePoolSize和maximumPoolSize的值都为1,它使用的是LinkedBlockingQueue(无界限的工作队列):LinkedBlockingQueue是一个无界缓存等待队列。当前执行的线程数量达到corePoolSize的数量时,剩余的元素会在阻塞队列里等待。(所以在使用此阻塞队列时maximumPoolSizes就相当于无效了)
Executors.newCachedThreadPool():会试图缓存线程并重用,创建一个可缓存的线程池,如果线程长度超过处理需要,可灵活回收空闲线程,若无可回收线程,则创建新线程。注意它内部将corePoolSize值设为0,maximumPoolSize值设置为Integer.MAX_VALUE,并且使用的是SynchronizedQueue,keepAliveTime值为60,即当线程空闲时间超过60秒,就销毁线程:
SynchronousQueue没有容量,是无缓冲等待队列,是一个不存储元素的阻塞队列,会直接将任务交给消费者
Executors.newScheduledThreadPool(int):创建一个固定线程数量的线程池,相比于newFixedThreadPool(int)固定个数的线程池强大在 ①可以执行延时任务,②也可以执行带有返回值的任务,并且使用的是
DelayedWorkQueue:基于堆结构的等待队列,DelayedWorkQueue取根节点延迟时间,然后等待,直到延迟结束,从新取根节点达到延迟取元素。
重要一点就是任务队列会根据任务延时时间的不同进行排序,延时时间越短地就排在队列的前面,先被获取执行。
无界队列有界队列
与有界队列相比,除非系统资源耗尽,否则无界的任务队列不存在任务入队失败的情况。无界队列会保持快速增长,直到耗尽系统内存。
线程池和Executor框架
ThreadPoolExecutor
Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,主要由下列4个组件构成。
- corePool:核心线程池的大小。
- maximumPool:最大线程池的大小。
- BlockingQueue:用来暂时保存任务的工作队列。
- RejectedExecutionHandler:当ThreadPoolExecutor已经关闭或ThreadPoolExecutor已经饱和 时(达到了最大线程池大小且工作队列已满),execute()方法将要调用的Handler。
序号 | 名称 | 类型 | 含义 |
---|---|---|---|
1 | corePoolSize | int | 核心线程池大小 |
2 | maximumPoolSize | int | 最大线程池大小 |