zoukankan      html  css  js  c++  java
  • 多线程基础【面试题形式】

    1、多线程和单线程的区别和联系?

    • 在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制。
    • 多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。

    结论:采用多线程不会提高程序的执行速度,反而会降低程序的执行速度,但是对于用户来说,可以减少用户的响应时间。

    2、多线程的优势?

    (1)资源利用率更好

      想象一下,一个应用程序需要从本地文件系统中读取和处理文件的情景。比方说,从磁盘读取一个文件需要5秒,处理一个文件需要2秒。处理两个文件则需要:

       从磁盘中读取文件的时候,大部分的CPU时间用于等待磁盘去读取数据。在这段时间里,CPU非常的空闲。它可以做一些别的事情。通过改变操作的顺序,就能够更好的使用CPU资源。看下面的顺序:

      CPU等待第一个文件被读取完。然后开始读取第二个文件。当第二文件在被读取的时候,CPU会去处理第一个文件。记住,在等待磁盘读取文件的时候,CPU大部分时间是空闲的。

      总的说来,CPU能够在等待IO的时候做一些其他的事情。这个不一定就是磁盘IO。它也可以是网络的IO,或者用户输入。通常情况下,网络和磁盘的IO比CPU和内存的IO慢的多。

    (2)程序设计在某些情况下更简单

      在单线程应用程序中,如果你想编写程序手动处理上面所提到的读取和处理的顺序,你必须记录每个文件读取和处理的状态。相反,你可以启动两个线程,每个线程处理一个文件的读取和操作。线程会在等待磁盘读取文件的过程中被阻塞。在等待的时候,其他的线程能够使用CPU去处理已经读取完的文件。其结果就是,磁盘总是在繁忙地读取不同的文件到内存中。这会带来磁盘和CPU利用率的提升。而且每个线程只需要记录一个文件,因此这种方式也很容易编程实现。

    (3)程序响应更快

      可以使用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验。

    多线程还有一些优势也显而易见:

    • 进程之间不能共享内存,而线程之间共享内存(堆内存)则很简单。
    • 系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.
    • Java语言本身内置多线程功能的支持,而不是单纯地作为底层系统的调度方式,从而简化了多线程编程.

    3、多线程一定快么?

    不一定。

    • 数据量小的时候,并发比串行慢,因为涉及到线程的创建和上下文切换的开销
    • 数据量大的时候,并发能更好地利用计算机性能,这时并发比串行更快

    4、什么是同步,什么是异步?

    同步和异步通常用来形容一次方法调用。

    同步方法调用

      同步方法调用一旦开始,调用者必须等到方法返回后,才能继续后续的行为

      这就好像是我们去商城买一台空调,你看中了一台空调,于是就跟售货员下了单,然后售货员就去仓库帮你调配物品,这天你热的实在不行,就催着商家赶紧发货,于是你就在商店里等着,知道商家把你和空调都送回家,一次愉快的购物才结束,这就是同步调用。

    异步方法调用

      异步方法更像是一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。

      回到刚才买空调的例子,我们可以坐在里打开电脑,在网上订购一台空调。当你完成网上支付的时候,对你来说购物过程已经结束了。虽然空调还没有送到家,但是你的任务都已经完成了。商家接到你的订单后,就会加紧安排送货,当然这一切已经跟你无关了,你已经支付完成,想什么就能去干什么了,出去溜达几圈都不成问题。等送货上门的时候,接到商家电话,回家一趟签收即可。这就是异步调用。

    5、并发(Concurrency)和并行(Parallelism)的区别

    • 并行性是指两个或多个事件在同一时刻发生
    • 并发性是指两个或多个事件在同一时间间隔内发生

      在多道程序环境下,并发性是指在一段时间内宏观上有多个程序在同时运行,但在单处理机环境下(一个处理器),每一时刻却仅能有一道程序执行,故微观上这些程序只能是分时地交替执行。例如,在1秒钟时间内,0-15ms程序A运行;15-30ms程序B运行;30-45ms程序C运行;45-60ms程序D运行,因此可以说,在1秒钟时间间隔内,宏观上有四道程序在同时运行,但微观上,程序A、B、C、D是分时地交替执行的。

      如果在计算机系统中有多个处理机,这些可以并发执行的程序就可以被分配到多个处理机上,实现并发执行,即利用每个处理机处理一个可并发执行的程序。这样,多个程序便可以同时执行。以此就能提高系统中的资源利用率,增加系统的吞吐量。

    6、线程和进程的区别

    • 进程是一个 “执行中的程序”,是系统进行资源分配和调度的一个独立单位。
    • 线程是进程的一个实体,一个进程中拥有多个线程,线程之间共享地址空间和其它资源(所以通信和同步等操作线程比进程更加容易)。
    • 线程上下文的切换比进程上下文切换要快很多。
      • (1)进程切换时,涉及到当前进程的 CPU 环境的保存和新被调度运行进程的 CPU 环境的设置。
      • (2)线程切换仅需要保存和设置少量的寄存器内容,不涉及存储管理方面的操作。

    7、进程间如何通讯?线程间如何通讯?

    • 进程间通讯依靠 IPC 资源,例如管道(pipes)、套接字(sockets)等。
    • 线程间通讯依靠 JVM 提供的 API,例如 wait()、notify()、notifyAll() 等方法,线程间还可以通过共享的主内存来进行值的传递。

    8、阻塞(Blocking)和非阻塞(Non-Blocking)?

    阻塞和非阻塞通常用来形容多线程间的相互影响。

    阻塞

      比如一个线程占用了临界区资源,那么其他所有需要这个资源的线程就必须在这个临界区中进行等待。等待会导致线程挂起,这种情况就是阻塞。此时,如果占用资源的线程一直不愿意释放资源,那么其他所有阻塞在这个临界区上的线程都不能工作。

    非阻塞

      非阻塞的意思与之相反,它强调没有一个线程可以妨碍其他线程执行。所有的线程都会尝试不断前向执行。

    9、临界区是什么?

      临界区用来表示一种公共资源或者说是共享资源,可以被多个线程使用,但是每一次,只能有一个线程使用它。一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。

      比如,在一个办公室里有一台打印机,打印机一次只能执行一个任务。如果小王和小明同时需要打印文件,很显然,如果小王先下发了打印任务,打印机就开始打印小王的文件了,小明的任务就只能等待小王打印结束后才能打印,这里的打印机就是一个临界区的例子。

      在并行程序中,临界区资源是保护的对象,如果意外出现打印机同时执行两个打印任务,那么最可能的结果就是打印出来的文件就会是损坏的文件,它既不是小王想要的,也不是小明想要的。

    10、什么是死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)?

      死锁、饥饿和活锁都属于多线程的活跃性问题,如果发现上述几种情况,那么相关线程可能就不再活跃,也就说它可能很难再继续往下执行了。

    死锁

      死锁应该是最糟糕的一种情况了,它表示两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

    饥饿

      饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行。比如:

    • (1)它的线程优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级的线程无法工作。在自然界中,母鸡喂食雏鸟时,很容易出现这种情况,由于雏鸟很多,食物有限,雏鸟之间的食物竞争可能非常厉害,小雏鸟因为经常抢不到食物,有可能会被饿死。线程的饥饿也非常类似这种情况。
    • (2)另外一种可能是,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。与死锁相比,饥饿还是有可能在未来一段时间内解决的(比如高优先级的线程已经完成任务,不再疯狂的执行)

    活锁

      活锁是一种非常有趣的情况。不知道大家是不是有遇到过这样一种情况,当你要坐电梯下楼,电梯到了,门开了,这时你正准备出去,但不巧的是,门外一个人挡着你的去路,他想进来。于是你很绅士的靠左走,避让对方,但同时对方也很绅士,但他靠右走希望避让你。结果,你们又撞上了。于是乎,你们都意识到了问题,希望尽快避让对方,你立即向右走,他也立即向左走,结果又撞上了!不过介于人类的智能,我相信这个动作重复 2、 3 次后,你应该可以顺利解决这个问题,因为这个时候,大家都会本能的对视,进行交流,保证这种情况不再发生。

      但如果这种情况发生在两个线程间可能就不会那么幸运了,如果线程的智力不够,且都秉承着 “谦让” 的原则,主动将资源释放给他人使用,那么就会出现资源不断在两个线程中跳动,而没有一个线程可以同时拿到所有的资源而正常执行。这种情况就是活锁。

    11、多线程产生死锁的 4 个必要条件?

    • 互斥条件:一个资源每次只能被一个线程使用;
    • 请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
    • 不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
    • 循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

    12、如何避免死锁?

    指定获取锁的顺序

    • 获得锁的顺序是一定的,比如规定,只有获得 A 锁的线程才有资格获取 B 锁,按顺序获取锁就可以避免死锁。

    13、如何指定多个线程的执行顺序?

    1. 设定一个 orderNum,每个线程执行结束之后,更新 orderNum,指明下一个要执行的线程。并且唤醒所有的等待线程。
    2. 在每一个线程的开始,要 while 判断 orderNum 是否等于自己的要求值!!不是,则 wait,是则执行本线程。

    14、Java 中线程有几种状态?

    六种(查看 Java 源码也可以看到是 6 种),并且某个时刻 Java 线程只能处于其中的一个状态

    新建(NEW)状态

      表示新创建了一个线程对象,而此时线程并没有开始执行。

    可运行(RUNNABLE)状态

      线程对象创建后,其它线程(比如 main 线程)调用了该对象的 start() 方法,才表示线程开始执行。当线程执行时,处于 RUNNBALE 状态,表示线程所需的一切资源都已经准备好了。该状态的线程位于可运行线程池中,等待被线程调度选中,获取 cpu 的使用权。

    阻塞(BLOCKED)状态

      如果线程在执行过程终于到了 synchronized 同步块,就会进入 BLOCKED 阻塞状态,这时线程就会暂停执行,直到获得请求的锁。

    等待(WAITING)状态

      当线程等待另一个线程通知调度器时,它自己进入等待状态。在调用Object.wait方法或Thread.join方法,或者是等待java.util.concurrent库中的Lock或Condition时,就会出现这种情况。

    计时等待(TIMED_WAITING)状态

      Object.wait、Thread.join、Lock.tryLock和Condition.await 等方法有超时参数,还有 Thread.sleep 方法、LockSupport.parkNanos 方法和 LockSupport.parkUntil 方法,这些方法会导致线程进入计时等待状态,如果超时或者出现通知,都会切换会可运行状态。

    终止(TERMINATED)状态

      当线程执行完毕,则进入该状态,表示结束。

    PS:从 NEW 状态出发后,线程不能再回到 NEW 状态,同理,处于 TERMINATED 状态的线程也不能再回到 RUNNABLE 状态。

  • 相关阅读:
    算法作业实验三
    牛客练习赛53 B 美味果冻
    牛客练习赛53 C 富豪凯匹配串
    bitmat
    牛客挑战赛33 B 鸽天的放鸽序列
    树状数组
    线段树
    2019牛客国庆集训派对day7 A 2016
    背包
    作业三 -并查集
  • 原文地址:https://www.cnblogs.com/riches/p/11771189.html
Copyright © 2011-2022 走看看