为什么要使用多线程?

从整体上来看

  • 从计算机底层来说: 线程可以看作是轻量级的进程,是最小的程序执行单位,线程间的切换和调度的成本远远小于进程。另外,多核CPU时代,多个线程可以同时运行,这减少了线程上下文切换的开销。
  • 从当代互联网发展趋势来说: 现在的系统动不动就要求百万级甚至千万级的并发量,而多线程并发编程是开发高并发系统的基础,利用好多线程机制可以大大提高系统整体的并发能力以及性能。

深入到计算机底层来看

  • 单核时代: 单核时代多线程主要是为了提高单进程利用CPU和IO系统的效率。假设只运行了一个Java进程的情况下,当我们请求IO的时候,如果只有一个线程,此线程被IO阻塞则整个进程被阻塞。CPU和IO设备只有一个在运行,那么可以简单地说系统整体的效率只有50%。如果使用多线程,一个线程被IO阻塞,其他线程还可以继续使用CPU,从而提高了Java进程利用系统资源的整体效率。
  • 多核时代: 多核时代多线程主要是为了提高进程利用多核CPU的能力。假设我们在计算一个复杂的任务,如果只有一个线程,无论有多少个CPU核心,都只会有一个CPU核心被利用到。如果使用多线程,这些线程可以被映射到底层多个CPU核心上执行,任务执行的效率就会显著性的提高,约等于(单核时执行时间/CPU 核心数)。

使用多线程可能带来哪些问题?

并发编程的目的就是为了能提高程序的执行效率提高程序运行速度,但是并发编程并不总是能提高程序运行速度的,而且并发编程可能会遇到很多问题,比如:内存泄漏、死锁、线程不安全等等。

线程的生命周期和状态

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • NEW:初始状态,线程被创建出来,但是还没有调用start()方法。
  • RUNABLE:运行中状态,调用了start()方法,Java线程将操作系统中的就绪/可运行(READY)和运行(RUNNING)两种状态统称为RUNABLE(运行中)状态。
  • BLOCKED:阻塞状态,线程阻塞于锁,需要等待锁释放。
  • WATING:等待状态,进入该状态表示当前线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIMED_WATING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:表示当前线程已经执行完毕。

  • 由上图可以看出:线程创建之后它将处于 NEW(初始) 状态,调用 start() 方法后开始运行,线程这时候处于 READY(就绪/可运行) 状态。可运行状态的线程获得了 CPU 时间片(timeslice)后就处于 RUNNING(运行) 状态。

  • 在操作系统层面,线程有 READY 和 RUNNING 状态;而在 JVM 层面,只能看到 RUNNABLE 状态,所以 Java 系统一般将这两个状态统称为 RUNNABLE(运行中) 状态 。

  • 为什么 JVM 没有区分这两种状态呢?
    java 现在的时分(time-sharing)多任务(multi-task)操作系统架构通常都是用所谓的“时间分片(time quantum or time slice)”方式进行抢占式(preemptive)轮转调度(round-robin 式)。这个时间分片通常是很小的,一个线程一次最多只能在 CPU 上运行比如 10-20ms 的时间(此时处于 running 状态),也即大概只有 0.01 秒这一量级,时间片用后就要被切换下来放入调度队列的末尾等待再次调度。(也即回到 ready 状态)。线程切换的如此之快,区分这两种状态就没什么意义了。

  • 当线程执行 wait()方法之后,线程进入 WAITING(等待) 状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态。

  • TIMED_WAITING(超时等待) 状态相当于在等待状态的基础上增加了超时限制,比如通过 sleep(long millis)方法或 wait(long millis)方法可以将线程置于 TIMED_WAITING 状态。当超时时间结束后,线程将会返回到 RUNNABLE 状态。

  • 当线程进入 synchronized 方法/块或者调用 wait 后,(被 notify)想要重新进入 synchronized 方法/块时,但是锁被其它线程占有,这个时候线程就会进入 BLOCKED(阻塞) 状态。

  • 线程在执行完了 run()方法之后将会进入到 TERMINATED(终止) 状态。

上下文切换

线程在执行过程中会有自己的运行条件和状态(也称上下文),比如说每个线程都有自己的程序计数器,栈信息等。当出现如下情况的时候,线程会从占用 CPU 状态中退出。

  • 主动让出 CPU,比如调用了 sleep(), wait() 等。
  • 时间片用完,因为操作系统要防止一个线程或者进程长时间占用 CPU 导致其他线程或者进程饿死。
  • 调用了阻塞类型的系统中断,比如请求 IO,线程被阻塞。
  • 被终止或结束运行

这其中前三种都会发生线程切换,线程切换意味着需要保存当前线程的上下文,留待线程下次占用 CPU 的时候恢复现场。并加载下一个将要占用 CPU 的线程上下文。这就是所谓的 上下文切换。

上下文切换是现代操作系统的基本功能,因其每次需要保存“信息恢复”信息,这将会占用 CPU,内存等系统资源进行处理,也就意味着效率会有一定损耗,如果频繁切换就会造成整体效率低下。

死锁

死锁描述

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示:线程A持有资源1,线程B拥有资源2,他们都想拥有对方的资源,所以这两个线程就会相互等待而进入死锁状态。

死锁产生的四个必要条件

  1. 互斥条件: 该资源任意一个时刻只能被一个线程占用。
  2. 请求与保持条件: 一个线程因请求资源阻塞,对已获得的资源保持不放。
  3. 不剥夺条件: 线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放。
  4. 循环等待条件: 若干线程之间形成一种头尾相接循环等待资源的关系。

如何预防死锁

破坏死锁的产生的必要条件即可:

破坏互斥条件 : 这个条件我们没有办法破坏,因为我们用锁本来就是想让他们互斥的(临界资源需要互斥访问)。
破坏请求与保持条件 : 一次性申请所有的资源。
破坏不剥夺条件 : 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
破坏循环等待条件 : 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放,破坏循环等待条件。

锁排序法: 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下,如何避免死锁? 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避免死锁。这通常被认为是解决死锁很好的一种方法。

使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁。

如何避免死锁?

避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态。

安全状态 指的是系统能够按照某种线程推进顺序(P1、P2、P3.....Pn)来为每个线程分配所需资源,直到满足每个线程对资源的最大需求,
使每个线程都可顺利完成。称 <P1、P2、P3.....Pn> 序列为安全序列。

sleep() 方法和 wait() 方法对比

共同点:

  • 两者都可以暂停线程的执行。
  • 两者都可以响应中断。

不同点:

  • sleep()方法没有释放锁,而wait()方法释放了锁。
  • sleep()方法通常用于暂停线程的执行,wait()方法通常用于线程间交互/通信。
  • sleep() 方法执行完成后,线程会自动苏醒;wait() 方法被调用后,线程不会自动苏醒,需要其他线程调用同一个对象上的 notify()或者 notifyAll() 方法。或者也可以使用 wait(long timeout) 超时后线程会自动苏醒。
  • sleep()方法是Thread 类的静态本地方法,wait() 则是 Object 类的本地方法。
  • wait()、notify()方法必须写在同步方法/同步代码块中,是为了防止死锁和永久等待,使线程更安全,而sleep()方法没有这个限制。

为什么 wait() 方法不定义在Thread中?

wait()方法是让获得对象锁的线程实现等待,并自动释放当前线程占有的对象锁。每个对象(Object)都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入WAITING状态,自然要操作对应的对象(Object)而非当前的线程(Thread)。

为什么 sleep() 方法定义在Thread中?

因为 sleep() 是让当前线程暂停执行,不涉及到对象类,也不需要获得对象锁。

可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,这个线程进入初始状态。让这个线程调用start()方法,会启动这个线程并使这个线程进入就绪/可运行(Ready)状态,当这个线程分配到时间片后就可以开始运行了(RUNNING状态)。start()方法会执行线程的相应准备工作,然后自动执行run()方法的内容,这是真正的多线程工作。直接执行run()方法,会把run()方法当成main线程下的一个普通方法去执行,并不会在某个线程中去执行,所以这并不是多线程工作。

总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

Java并发常见面试题(二)相关推荐

  1. Java工程师常见面试题集锦

    Java工程师常见面试题集锦(一)互联网人必看!(附答案及视频教程,持续更新) 2019年01月02日 14:01:14 CSDNedu 阅读数:653 大牛也怕面试题,尤其是基础题,在面试中如果出现 ...

  2. Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3

    Java开发常见面试题详解(LockSupport,AQS,Spring循环依赖,Redis)_3 总览 问题 详解 String.intern()的作用 link LeetCode的Two Sum题 ...

  3. Java开发常见面试题详解(JVM)_2

    Java开发常见面试题详解(JVM)_2 JVM 问题 详解 JVM垃圾回收的时候如何确定垃圾?是否知道什么是GC Roots link 你说你做过JVM调优和参数配置,请问如何盘点查看JVM系统默认 ...

  4. java陷阱常见面试题_Java常见陷阱

    java陷阱常见面试题 总览 Java是一种极简主义的语言,具有比其他语言故意更少的功能,尽管如此,Java仍然具有产生奇怪效果的边缘情况,甚至具有令人惊讶的效果的一些常见情况也会使您轻而易举. 如果 ...

  5. Java基础常见面试题(一)

    Java基础常见面试题(一) 1. 为什么说 Java 语言"编译与解释并存"? 我们可以将高级编程语言按照程序的执行方式分为两种: 编译型 :编译型语言会通过编译器将源代码一次性 ...

  6. 合肥Java面试常考题_北大青鸟java 面试--常见面试题(中)

    上一文中,我们总结了java面试的基础,多线程,jvm的常见面试题,本文合肥北大青鸟合工大校区的袁老师继续介绍面试中网络.数据结构和算法.分布式理论和微服务的常见面试题. 一.网络 网络的话,主要集中 ...

  7. 北大java面试,北大青鸟java 面试--常见面试题(下)

    在之前的两篇文章中,我们已经提到了java面试中的常见问题,还有部分内容,合肥北大青鸟合工大校区的袁老师在本文也给出,希望对大家的面试过程有些帮助.这是我总结的最后一部分常见面试题:分别是数据库,基础 ...

  8. Java多线程常见面试题及答案汇总1000道(春招+秋招+社招)

    Java多线程面试题以及答案整理[最新版]Java多线程高级面试题大全(2021版),发现网上很多Java多线程面试题都没有答案,所以花了很长时间搜集,本套Java多线程面试题大全,汇总了大量经典的J ...

  9. Java虚拟机常见面试题

    2019独角兽企业重金招聘Python工程师标准>>> 1.java引用的四种状态 强引用.软引用.弱引用.虚引用. 强引用 new一个Object存放在堆内存,然后用一个引用指向它 ...

最新文章

  1. Python 3 利用 Dlib 实现人脸检测和剪切
  2. log4j配置文件详解
  3. 9.优先队列,priority_queue
  4. Python第三、四种数据类型——List(列表) and Tuple(元组)
  5. maven中jar下载失败
  6. pdfstamper生成pdf无法显示汉字_正点原子STM32F4/F7水星开发板资料连载第四十六章 汉字显示实验...
  7. C#比较dynamic和Dictionary性能
  8. echart实现3d地图_3D飞线效果——让线“飞”起来的秘密
  9. 功能强大的TCGA再分析平台
  10. 03--STL算法(常用算法)
  11. 干得漂亮!签约“这辈子不可能打工”男子的经纪公司将被拉黑
  12. Axure智慧、智能乡镇通数字管理服务平台+基础数据管理+招商后台管理+web端高保真管理后台
  13. SQL时间格式化 转载备用~
  14. PTA—念数字(C语言)两种方法
  15. centos通过yum的方式快速安装jdk1.8
  16. 基于lis3dh的简易倾角仪c源码_cm-23D柯尼卡美能达分光测色计色差仪CM-2300d
  17. 输入卡号生成银行卡图片python_python 模拟贷个卡号生成规则过程解析
  18. maya表情blendshape_引用 【Maya】角色表情绑定-BlendShape的使用技巧
  19. Windows 微博图床工具(支持多种图床)下载和picgo VSCode插件版的详细介绍
  20. 迎新:Apache IoTDB 喜迎 2 位新 Committer

热门文章

  1. 我的消费记录怎么查看呢?
  2. 为什么Word文档无响应,Word文档无响应的解决方法
  3. 计算机毕业设计Java新生报到管理(源码+系统+mysql数据库+lw文档)
  4. 周报 | 吉吉拍助力消费者转变
  5. 新生入学了,针对腾讯的产品全民K歌(或QQ空间、腾讯游戏......)做一次推广活动,包括产品功能设计和运营活动推广方案。
  6. python PIL库对图片按比例进行分割
  7. 使用网络调试助手通过MQTT协议接入到华为云物联网平台
  8. 两代荣耀Magic历史性同框,荣耀Magic 2如何践行科技理想主义?
  9. NginxWebUI--强大的nginx可视化配置工具
  10. 创建个人网页,创建个人网址。