线程与线程池重点知识总结

Java中线程有哪些状态?

六种状态:

  • 初始状态 (NEW)尚未启动的线程处于此状态。通常是新创建了线程,但还没有调用 start () 方法
  • 运行状态(RUNNABLE):Java 线程中将就绪(ready)和运行中(running)两种状态笼统的称为 “运行中”。比如说线程在运行线程池中,等待被调度选中,获取 CPU 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得 CPU 时间片后变为运行中状态(running)
  • 阻塞状态 (BLOCKED):表示线程阻塞于锁
  • 等待状态 (WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)
  • 超时等待状态 (TIMED_WAITING):进入该状态的线程需要等待其他线程在指定时间内做出一些特定动作(通知或中断),可以在指定的时间自行返回
  • 终止状态 (TERMINATED):表示该线程已经执行完毕,已退出的线程处理此状态
  1. 线程状态 RUNNABLE 如何变为 BLOCKED?
  • 线程状态变为阻塞有多种原因:可能调用 wait () 方法进入等待池,可能执行同步方法 / 同步代码块进入等锁池,可能调用了 sleep() / join() 等待休眠或其他线程结束,可能发生了 I/O 中断等
    在这里插入图片描述
  • 线程创建之后它将处于 NEW 状态,调用 start () 方法后开始运行,线程这时候处于 READY 状态。可运行状态的线程获得了 CPU 时间片后就处于 RUNNING 状态
  • 线程执行 wait () 方法后,进入 WAITING 状态,此时需要依靠其他线程的通知才能够返回到运行状态,而 TIME_WAITING 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep()或 wait()可以将线程置于 TIME_WAITING 状态。当超时时间到达又会返回到 RUNNABLE 状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到 BLOCKED 状态。线程在执行 Runnable 的 run () 方法之后将会进入到 TERMINATED 状态

线程活性故障有哪些?

由于资源的稀缺性或者程序自身的问题导致线程一直处于非Runnable状态,并且其处理的任务一直无法完成的现象被称为是线程活性故障。常见的线程活性故障包括死锁,锁死,活锁与线程饥饿

解析:

每一个线程都有其特定的任务处理逻辑。由于资源的稀缺性或者资源本身的一些特性,导致多个线程需要共享一些排他性资源,比如说处理器,数据库连接等。当出现资源争用的时候,部分线程会进入等待状态。

线程死锁(重点)

死锁是最常见的一种线程活性故障。死锁的起因是多个线程之间相互等待对方而被永远暂停(处于非Runnable)。死锁的产生必须满足如下四个必要条件:

  • 资源互斥:一个资源每次只能被一个线程使用
  • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:线程已经获得的资源,在未使用完之前,不能强行剥夺
  • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系
如何避免死锁的发生?
  • 粗锁法:使用一个粒度粗的锁来消除“请求与保持条件”,缺点是会明显降低程序的并发性能并且会导致资源的浪费。
  • 锁排序法:(重点
    指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁?
    通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法
    (使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁)
线程锁死:

线程锁死是另一种常见的线程活性故障,与线程死锁不可以混为一谈。线程锁死的定义如下:

  • 线程锁死是指等待线程由于唤醒其所需的条件永远无法成立,或者其他线程无法唤醒这个线程而一直处于非运行状态(线程并未终止)导致其任务 一直无法进展。

线程死锁和线程锁死的外部表现是一致的,即故障线程一直处于非运行状态使得其所执行的任务没有进展。但是锁死的产生条件和线程死锁不一样,即使产生死锁的4个必要条件都没有发生,线程锁死仍然可能已经发生。

线程锁死分为了如下两种:

  • 信号丢失锁死
    信号丢失锁死是因为没有对应的通知线程来将等待线程唤醒,导致等待线程一直处于等待状态。
    (典型例子是等待线程在执行Object.wait( )/Condition.await( )前没有对保护条件进行判断,而此时保护条件实际上可能已经成立,此后可能并无其他线程更新相应保护条件涉及的共享变量使其成立并通知等待线程,这就使得等待线程一直处于等待状态,从而使其任务一直无法进展)

  • 嵌套监视器锁死
    嵌套监视器锁死是由于嵌套锁导致等待线程永远无法被唤醒的一种故障。
    (比如一个线程,只释放了内层锁Y.wait(),但是没有释放外层锁X; 但是通知线程必须先获得外层锁X,才可以通过 Y.notifyAll()来唤醒等待线程,这就导致出现了嵌套等待现象)

活锁:

活锁是一种特殊的线程活性故障。当一个线程一直处于运行状态,但是其所执行的任务却没有任何进展称为活锁。比如,一个线程一直在申请其所需要的资源,但是却无法申请成功。

线程饥饿:

线程饥饿是指线程一直无法获得其所需的资源导致任务一直无法运行的情况。线程调度模式有公平调度和非公平调度两种模式。在线程的非公平调度模式下,就可能出现线程饥饿的情况。

线程活性故障总结:
  • 线程饥饿发生时,如果线程处于可运行状态,也就是其一直在申请资源,那么就会转变为活锁
  • 只要存在一个或多个线程因为获取不到其所需的资源而无法进展就是线程饥饿,所以线程死锁其实也算是线程饥饿

为什么要使用线程池?如何使用线程池?线程池有哪些核心参数?初始化线程池的大小的如何算?shutdown 和 shutdownNow 有什么区别?

  1. 为什么要使用线程池?
  • 降低资源消耗,通过重复利用已经创建的线程降低线程创建和销毁造成的消耗
  • 提高响应速度,当任务到达时,可以不用等待线程创建就能直接执行
  • 提高线程的可管理性,使用线程池进行统一的分配,调优和监控
  1. 如何使用线程池?
    工具类 Executors 提供了静态工厂方法以生成常用的线程池:
  • newSingleThreadExecutor:创建一个单线程的线程池。如果该线程因为异常而结束,那么会有一个新的线程来替代它
  • newFixedThreadPool:创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大值,一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程
  • newCachedThreadPool:创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(默认 60 秒不执行任务)的线程。当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说 JVM)能够创建的最大线程大小
  • newScheduledThreadPool:创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求
  1. Java线程池的核心参数 (了解)
public ThreadPoolExecutor ( 
int corePoolSize, // 常驻线程数,即使空闲时仍保留在池中的线程数
int maximumPoolSize, // 线程池中允许的最大线程数
long keepAliveTime, // 存活时间。线程数比 corePoolSize 多且处于闲置状态的情况下,这些闲置的线程能存活的最大时间,为 0 表示会立即回收;
TimeUnit unit, //keepAliveTime 的单位
BlockingQueue workQueue, // 被提交尚未被执行的任务阻塞队
ThreadFactory threadFactory, // 创建线程的工厂
RejectedExecutionHandler handler // 饱和拒绝策略,当队列满了并且线程个数达到 maximunPoolSize 后采取的策略。目前支付四种:AbortPolicy (抛出异常),CallerRunsPolicy (调用者线程处理),DiscardOldestPolicy (直接丢弃任务,不予处理也不抛出异常),DiscardPolicy (默默丢弃,不抛出异常)
)
  1. 如何设置初始化线程池的大小?
    可根据线程池的线程处理任务的不同进行分别估计 (以下为单个服务器一个应用一个线程池情况)
  • CPU密集型任务,这类任务需要大量的运算,通常CPU利用率很高,无阻塞,因此应配置尽可能少的线程数量,可设置CPU核数 + 1;
  • IO密集型任务,这类任务有大量的IO操作,伴随着大量的线程被阻塞,可配置更多的线程数,通常可设置为CPU核心数 * 2;
  • 另一种估算方式:最佳线程数目 = (线程等待时间+线程CPU时间)/ 线程CPU时间 )* CPU数目
  1. shutdown 和 shutdownNow 有什么区别?

shutdown:有序停止。将线程池状态设置为 SHUTDOWN

  • 停止接收外部提交的任务
  • 先前提交的任务会继续执行(不保证完全执行)

shutdownNow :尝试立即停止,将线程池状态设置为 STOP,相对来说是一种更优雅的关闭方法

  • 停止接收外部提交的任务
  • 不再处理队列里的任务
  • 忽略队列里等待的任务
  • 返回正在执行的任务列表

注: shutdownNow 试图取消线程的方法是通过调用 Thread.interrupt () 方法来实现的,非强制的,如果线程中没有 sleep/wait 等应用,interrupt () 方法是无法中断当前的线程的。所以,ShutdownNow 并不代表线程池就一定立即就能退出,它也可能必须要等待所有正在执行的任务都执行完成了才能退出,但是大多数时候是能立即退出的

ThreadLocal 的作用是什么?有什么风险?

  1. ThreadLocal的作用是什么?
    提供每个线程存储自身专属的局部变量
  • 原理: 每个 Thread 维护着一个 ThreadLocalMap 的引用,ThreadLocalMap 是 ThreadLocal 的内部类,用 Entry 来进行存储。调用 ThreadLocal 的 set () 方法时,实际上就是ThreadLocalMap 设置值,key 是 ThreadLocal 对象,值是传递进来的对象;调用 ThreadLocal 的 get () 方法时,实际上就是往 ThreadLocalMap 获取值,key 是 ThreadLocal 对象 ThreadLocal 本身并不存储值,它只是作为一个 key 来让线程从 ThreadLocalMap 获取 value。因为这个原理,所以 ThreadLocal 能够实现 “数据隔离”,获取当前线程的局部变量值,不受其他线程影响
  1. 有什么风险?
  • 内存泄露,ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用,如果 ThreadLocal 没有被强引用, 那么 GC 时 Entry 的 key 就会被回收,但是对应的 value 却不会回收。就会造成内存泄漏。 ​ 解决方案,每次使用完 ThreadLocal,都调用它的 remove () 方法,清除数据。

线程和进程的区别?

  • 进程和线程的主要差别在于它们是不同的操作系统资源管理方式。

  • 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响。

  • 线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。

  • 对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。

  • 可一句话概括:进程是资源分配的基本单位;线程是程序执行的基本单位

什么是线程安全?

  • 当多个线程访问同一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替运行,也不需要进行额外的同步,或者在调用方进行任何其他的协调操作,调用这个对象的行为都可以获取正确的结果,那这个对象是线程安全的。

线程安全的级别?

  • 不可变,不可变的对象一定是线程安全的,并且永远也不需要额外的同步
Java类库中大多数基本数值类如Integer、String和BigInteger都是不可变的
  • 无条件的线程安全,由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步
如 Random 、ConcurrentHashMap、Concurrent集合、atomic
  • 有条件的线程安全,有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步
有条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器
  • 非线程安全(线程兼容),线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用
如ArrayList HashMap
  • 线程对立,线程对立是那些不管是否采用了同步措施,都不能在多线程环境中并发使用的代码
如System.setOut()、System.runFinalizersOnExit()`

调用 start() 方法时会执行 run() 方法,为什么我们不能直接调用 run() 方法?

  • 调用 start 方法方可启动线程并使线程进入就绪状态(READY),而 run 方法只是 thread 的一个普通方法调用,还是在主线程里执行,这并不是多线程工作

多线程可能带来的问题?

  • 内存泄漏,上下文切换,死锁,受限于硬件和软件的资源闲置问题

线程池有哪些状态?

线程池的5种状态:Running、ShutDown、Stop、Tidying、Terminated

  • RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务

  • SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。在线程池处于 RUNNING 状态时,调用 shutdown()方法会使线程池进入到该状态

  • STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。在线程池处于 RUNNING 或 SHUTDOWN 状态时,调用 shutdownNow() 方法会使线程池进入到该状态

  • TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态

  • TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做
    在这里插入图片描述

线程池中 execute()和submit()方法有什么区别?

  • execute()方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否
  • submit()方法用于提交需要返回值的任务。线程池会返回一个Future类型的对象,通过这个Future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完

Runnable接口和 Callable接口的区别?

  • Runnable接口不会返回结果,Callable接口可以返回结果
  • (工具类Executors可以实现Runnable对象和Callable对象之间的相互转换)

创建线程的方式?

  • 继承Thread类创建线程类
  • 通过Runnable接口创建线程类
  • 通过Callable 和 Future 创建线程
  • 线程池

用户线程和守护线程有什么区别?

  • 守护线程都是为JVM中所有非守护线程的运行提供便利服务: 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。

如何创建守护线程?以及在什么场合来使用它?

  • 任何线程都可以设置为守护线程和用户线程,通过方法Thread.setDaemon(bool on);true则把该线程设置为守护线程,反之则为用户线程。Thread.setDaemon()必须在Thread.start()之前调用,否则运行时会抛出异常
  • 守护线程相当于后台管理者 比如 : 进行内存回收、垃圾清理等工作

线程中断是否能直接调用stop,为什么?

Java提供的终止方法只有一个stop,但是不建议使用

  • stop方法是过时的,已经过时的方式不建议采用
  • stop方法会导致代码逻辑不完整,stop方法是一种 "恶意"的中断,一旦执行stop方法,即终止当前正在运行的线程,不管线程逻辑是否完整,这是非常危险的
  • stop方法会破坏原子逻辑。多线程为了解决共享资源抢占的问题,使用了锁概念,避免资源不同步,但是正因此原因,stop方法却会带来更大的麻烦:它会丢弃所有的锁,导致原子逻辑受损

线程池详细讲解强烈安利:Java并发编程:线程池的使用

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值