Java面试手册:线程专题 ①

1、 什么是线程?

  • 线程是操作系统能够进行运算的最小单位,他包含在实际的运作单位里面,是进程中的实际运作单位。
  • 程序员可以通过它进行多处理器编程,你可以使用多线程对运算密集型任务提速。比如,如果一个线程完成一个任务要100毫秒,那么用十个线程完成改任务只需10毫秒。Java在语言层面对多线程提供了卓越的支持,它也是一个很好的卖点
  • 它可与同属一个进程的其它线程共享进程所拥有的全部资源。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。线程也有就绪、阻塞和运行三种基本状态。我们通过多线程编程,能更高效的提高系统内多个程序间并发执行的程度,从而显著提高系统资源的利用率和吞吐量。
2、进程调度算法

  • 实时系统:FIFO(First Input First Output,先进先出算法),SJF(Shortest Job First,最短作业优先算法),SRTF(Shortest Remaining Time First,最短剩余时间优先算法)。
  • 互式系统:RR(Round Robin,时间片轮转算法),HPF(Highest Priority First,最高优先级算法),多级队列,最短进程优先,保证调度,彩票调度,公平分享调度。

3、线程和进程有什么区别?

  • 线程是进程的子集,一个进程可以有很多线程,每条线程并行执行不同的任务。不同的进程使用不同的内存空间,而所有的线程共享一片相同的内存空间。别把它和栈内存搞混,每个线程都拥有单独的栈内存用来存储本地数据
  • 通信:不同进程之间通过IPC(进程间通信)接口进行通信。同一进程的线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。
  • 调度和切换:线程上下文切换比进程上下文切换要快得多。
4、多线程编程的好处是什么?

  • 在多线程程序中,多个线程被并发的执行以提高程序的效率,CPU不会因为某个线程需要等待资源而进入空闲状态(提高CPU的利用率)。
  • 多个线程共享堆内存(heap memory),因此创建多个线程去执行一些任务会比创建多个进程更好。举个例子,Servlets比CGI更好,是因为Servlets支持多线程而CGI不支持。
5、如何在java中实现多线程

  • 在语言层面有两种方式。可以继承java.lang.Thread线程类,但是它需要调用java.lang.Runnable接口来执行。由于线程类本身就是调用的Runnable接口,所以你可以继承java.lang.Thread类或者直接调用Runnable接口来重写run()方法实现线程。
  • 还可以实现callable接口,和实现 Runnable接口一样。
  • 那么选择哪个更好?
    • 由于Java不支持类的多重继承,但允许调用多个接口。因此我们建议调用Runnable接口来创建线程.
6、Thread 类中的start() 和 run() 方法有什么区别?

  • start()方法被用来启动新创建的线程,而且start()内部调用了run()方法,这和直接调用run()方法的效果不一样
    • 首先,start方法内部会调用run方法。
    • start与run方法的主要区别在于当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行。
    • 然而在你直接调用run方法的时候,程序并不会创建新线程,run方法内部的代码将在当前线程上运行。大多数情况下调用run方法是一个bug或者变成失误。因为调用者的初衷是调用start方法去开启一个新的线程,这个错误可以被很多静态代码覆盖工具检测出来,比如与fingbugs. 如果你想要运行需要消耗大量时间的任务,你最好使用start方法,否则在你调用run方法的时候,你的主线程将会被卡住。
    • 还有一个区别在于,一但一个线程被启动,你不能重复调用该thread对象的start方法,调用已经启动线程的start方法将会报IllegalStateException异常, 而你却可以重复调用run方法。
7、notify()和notifyAll()有什么区别?

  • 两者最大的区别:
    • notifyAll使所有原来在该对象上等待被notify的线程统统退出wait的状态,变成等待该对象上的锁,一旦该对象被解锁,他们就会去竞争。
    • notify他只是选择一个wait状态线程进行通知,并使它获得该对象上的锁,但不惊动其他同样在等待被该对象notify的线程们,当第一个线程运行完毕以后释放对象上的锁,此时如果该对象没有再次使用notify语句,即便该对象已经空闲,其他wait状态等待的线程由于没有得到该对象的通知,继续处在wait状态,直到这个对象发出一个notify或notifyAll,它们等待的是被notify或notifyAll,而不是锁。
  • notify()和notifyAll()都是Object对象用于通知处在等待该对象的线程的方法。
  • void notify(): 唤醒一个正在等待该对象的线程。
  • void notifyAll(): 唤醒所有正在等待该对象的线程。
8、请说出与线程同步以及线程调度相关的方法。

  • wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;
  • sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要处理InterruptedException异常;
  • notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且与优先级无关;
  • notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;
9、java如何实现多线程之间的通讯和协作?

  • Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。
  • 它们都是Object类的最终方法,因此每一个类都默认拥有它们。虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。这些方法在Object类中声明的语法格式如下所示:
    • final void wait() throws InterruptedException
    • final void notify()
    • final void notifyAll()
10、为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里,为什么不放在Thread类里面?

  • 不把它放在Thread类里的原因,++一个很明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得++,简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象
  • Java的每个对象中都有一个锁(monitor,也可以成为监视器)并且wait(),notify()等方法用于等待对象的锁或者通知其他线程对象的监视器可用
  • 在Java的线程中并没有可供任何对象使用的锁和同步器。这就是为什么这些方法是Object类的一部分,这样Java的每一个类都有用于线程间通信的基本方法
  • Java API 的设计人员提供了一些方法当等待条件改变的时候通知它们,但是这些方法没有完全实现。notify()方法不能唤醒某个具体的线程,所以只有一个线程在等待的时候它才有用武之地,而notifyAll()唤醒所有线程并允许他们争夺锁确保了至少有一个线程能继续运行.
11、为什么wait(),notify()和notifyAll()必须在同步方法或者同步块中被调用?

  • 当一个线程需要调用对象的wait()方法的时候,这个线程必须拥有该对象的锁,接着它就会释放这个对象锁并进入等待状态直到其他线程调用这个对象上的notify()方法。同样的,当一个线程需要调用对象的notify()方法时,它会释放这个对象的锁,以便其他在等待的线程就可以得到这个对象锁。
  • 由于所有的这些方法都需要线程持有对象的锁,这样就只能通过同步来实现,所以他们只能在同步方法或者同步块中被调用。
  • 主要是因为Java API强制要求这样做,如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。还有一个原因是为了避免wait和notify之间产生竞态条件。

12、进程间的通信方式

  • 管道( pipe):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  • 有名管道 (named pipe) : 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
  • 信号量( semophore ) : 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 消息队列( message queue ) : 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  • 信号 ( sinal ) : 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
  • 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  • 套接字( socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
13、sleep()和wait()有什么区别?

  • sleep是线程类(Thread)的方法,导致此线程暂停执行指定时间,给执行机会给其他线程,但是监控状态依然保持,到时后会自动恢复。调用sleep不会释放对象锁。
  • wait是Object类的方法,对此对象调用wait方法导致本线程放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象发出notify方法(或notifyAll)后本线程才进入对象锁定池准备获得对象锁进入运行状态。
14、为什么Thread类的sleep()和yield()方法是静态的?

  • Thread类的sleep()和yield()方法将在当前正在执行的线程上运行。所以在其他处于等待状态的线程上调用这些方法是没有意义的。这就是为什么这些方法是静态的。
  • 它们可以在当前正在执行的线程中工作,并避免程序员错误的认为可以在其他非运行线程调用这些方法。
15、Java中CyclicBarrier 和 CountDownLatch有什么不同?

  • CyclicBarrier 和 CountDownLatch 都可以用来让一组线程等待其它线程。与 CyclicBarrier 不同的是,CountdownLatch 不能重新使用
16、为什么需要并行设计?

  • 业务需求:业务上需要多个逻辑单元,比如多个客户端要发送请求
  • 性能需求:在多核OS中,使用多线程并发执行性能会比单线程执行的性能好很多
17、并发和并行的区别:

  • 举例:
    • 你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行。
    • 你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发。
    • 你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行。
  • 并发的关键是有处理多个任务的能力,但是不一定同时处理,而并行表示同一个时刻处理多个任务,两者的关键点就是是否同时。
    • 解释一:并行是指两个或者多个线程在同一时刻发生;而并发是指两个或多个线程在同一时间间隔发生(交替运行)
    • 解释二:并行是在不同实体上的多个事件(多个JVM),并发是在同一实体上的多个事件(一个JVM)。
    • 并行又分在一台处理器上同时处理多个任务,在多台处理器上同时处理多个任务。如hadoop分布式集群
18、什么是Daemon(守护)线程?它有什么意义?

  • 在Java中有两类线程:用户线程 (User Thread)、守护线程 (Daemon Thread)。
  • 所谓后台(daemon)线程,是指在程序运行的时候在后台提供一种通用服务的线程,并且这个线程并不属于程序中不可或缺的部分。因此,当所有的非后台线程介绍时,程序也就终止了,同时会杀死进程中的所有后台线程。反过来说,只要有任何非后台线程还在运行,程序就不会终止。必须在线程启动之前调用setDaemon()方法,才能把它设置为后台线程。注意:后台进程在不执行finally子句的情况下就会终止其run()方法。
  • 守护线程和用户线程的区别在于:守护线程依赖于创建它的线程,而用户线程则不依赖。举个简单的例子:如果在main线程中创建了一个守护线程,当main方法运行完毕之后,守护线程也会随着消亡。而用户线程则不会,用户线程会一直运行直到其运行完毕。在JVM中,像垃圾收集器线程就是守护线程。
  • 守护线程必须在用户线程执行前调用,它是一个后台服务线程,一个守护线程创建的子线程依然是守护线程。
19、如何创建守护线程?

  • 使用Thread类的setDaemon(true)方法可以将线程设置为守护线程,需要注意的是,需要在调用start()方法前调用这个方法,否则会抛出IllegalThreadStateException异常。

20、 如何停止一个线程

  • Java提供了很丰富的API但没有为停止线程提供API。JDK1.0本来有一些像++stop(), suspend() 和 resume()的控制方法但是由于潜在的死锁威胁因此在后续的JDK版本中他们被弃用了++,之后JavaAPI的设计者就没有提供一个兼容且线程安全的方法来停止一个线程。
  • ==当run()或者call()方法执行完的时候线程会自动结束,如果要手动结束一个线程,可以用volatile布尔变量来退出run()方法的循环或者是取消任务来中断线程。==
  • 当不阻塞时候设置一个标志位,让代码块正常运行结束并停止线程。
  • 如果发生了阻塞,用interupt()方法,Thread.interrupt()方法不会中断一个正在运行的线程。这一方法实际上完成的是,在线程受到阻塞时抛出一个中断信号,这样线程就得以退出阻塞的状态。
21、什么是Thread Group?为什么不建议使用它?

  • ThreadGroup是一个类,它的目的是提供关于线程组的信息。
  • hreadGroup API比较薄弱,它并没有比Thread提供了更多的功能。它有两个主要的功能:一是获取线程组中处于活跃状态线程的列表;二是设置为线程设置未捕获异常处理器(ncaughtexceptionhandler)。但在Java1.5中Thread类也添加了setUncaughtExceptionHandler(UncaughtExceptionHandlereh)方法,所以ThreadGroup是已经过时的,不建议继续使用。
22、 什么是Java线程转储(Thread Dump),如何得到它?

  • 线程转储是一个JVM活动线程的列表,它对于分析系统瓶颈和死锁非常有用。>> - 有很多方法可以获取线程转储——使用Profiler,Kill-3命令,jstack工具等等。我更喜欢jcmd命令(jdk1.8以上)。
23、什么是FutureTask?

  • 在Java并发程序中FutureTask表示一个可以取消的异步运算。它有启动和取消运算、查询运算是否完成和取回运算结果等方法。只有当运算完成的时候结果才能取回,如果运算尚未完成get方法将会阻塞。一个FutureTask对象可以对调用了Callable和Runnable的对象进行包装,由于FutureTask也是调用了Runnable接口所以它可以提交给Executor来执行。
24、Java中interrupted 和 isInterruptedd方法的区别?

  • interrupted() :会将中断状态清除,Java多线程的中断机制是用内部标识来实现的,调用Thread.interrupt()来中断一个线程就会设置中断标识为true。当中断线程调用静态方法Thread.interrupted()来检查中断状态时,中断状态会被清零。
  • isInterruptedd : 不会将中断状态清除,非静态方法isInterrupted()用来查询其它线程的中断状态且不会改变中断状态标识。
  • 简单的说就是任何抛出InterruptedException异常的方法都会将中断状态清零。无论如何,一个线程的中断状态有有可能被其它线程调用中断来改变。
25、为什么你应该在循环中检查等待条件?

  • 处于等待状态的线程可能会收到错误警报和伪唤醒,如果不在循环中检查等待条件,程序就会在没有满足结束条件的情况下退出。因此,当一个等待线程醒来时,不能认为它原来的等待状态仍然是有效的,在notify()方法调用之后和等待线程醒来之前这段时间它可能会改变。这就是在循环中使用wait()方法效果更好的原因。
26、Java中的同步集合与并发集合有什么区别?

  • 同步集合与并发集合都为多线程和并发提供了合适的线程安全的集合,
  • 同步集合:在Java1.5之前程序员们只有同步集合来用且在多线程并发的时候会导致争用,阻碍了系统的扩展性
  • 并发集合: 可扩展性更高,Java5介绍了并发集合像ConcurrentHashMap,不仅提供线程安全还用锁分离和内部分区等现代技术提高了可扩展性

原文发布于微信公众号 - Java大联盟(javaunion)

原文发表时间:2018-10-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏python学习路

八、线程和进程 什么是线程(thread)?什么是进程(process)? 线程和进程的区别?Python GIL(Global Interpreter Lock)全局解释器锁

什么是线程(thread)? 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一...

51170
来自专栏python3

python3--os模块,模块和包(import... , from..import...)

常见的场景:一个模块就是一个包含了python定义和声明的文件,文件名就是模块名字加上.py的后缀

9310
来自专栏JavaQ

Java多线程编程笔记之多线程技能

1.进程和线程的概念及线程的优点 进程是程序在一个数据集合上运行的过程,是系统进行资源分配和调度的一个独立单位。 线程可以理解成是进程中独立运行的子任务。 线程...

33870
来自专栏开源优测

RobotFramework怎么写好用例

github地址:https://github.com/robotframework/HowToWriteGoodTestCases/blob/master/H...

11120
来自专栏FreeBuf

如何进行Linux平台共享库替换

*本文原创作者:gaearrow,本文属FreeBuf原创奖励计划,未经许可禁止转载 。 共享库基础知识 程序由源代码变成可执行文件,一般可以分解为四个步骤...

30780
来自专栏Android 研究

Android跨进程通信IPC之2——Bionic

Bionic库是Android的基础库之一,也是连接Android系统和Linux系统内核的桥梁,Bionic中包含了很多基本的功能模块,这些功能模块基本上都是...

26940
来自专栏黑泽君的专栏

c语言基础学习02_helloworld

============================================================================= 涉及...

20010
来自专栏我的博客

awk第二课

使用方法:awk ‘{pattern + action}’ {filenames} 尽管操作可能会很复杂,但语法总是这样,其中 pattern 表示 AWK 在...

362100
来自专栏Java帮帮-微信公众号-技术文章全总结

01.线程状态/创建/启动

01.线程状态/创建/启动 多线程作为Java中很重要的一个知识点,在此还是有必要总结一下的。 一.线程的生命周期及五种基本状态 关于Java中线程的生命周期,...

40770
来自专栏PHP在线

PHP10个实用函数

介绍PHP中的10个实用函数 [导读] PHP的功能越来越强大,里面有着非常丰富的内置函数。资深的PHP程序员对它们可能都很熟悉,但很多的PHP学习者,仍然对...

37840

扫码关注云+社区

领取腾讯云代金券