Bootstrap

Java面试题库——多线程

1.并行和并发有什么区别?

并行:是指两个或多个事件在同一时刻发生,是在不同实体上的多个事件;
并发:是指两个或多个事件在同一时间间隔发生,是同一实体上的多个事件。

2.线程和进程的区别?

根本区别:进程是资源分配的基本单位,线程是程序执行的基本单位。
资源开销:每个进程都有独立的代码和数据空间,程序之间的切换会有较大的开销;线程可以看作轻量级的进程,同一线程共享代码和数据空间,每个进程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。
包含关系:如果一个进程中有多个线程,则执行过程不是一条线的,而是多条线共同完成的;线程是进程的一部分,所以线程也被称为轻量级进程。
内存分配:同一进程的线程共享本进程的地址和资源,而进程之间的地址空间是相互独立的。
影响关系:一个进程崩溃后,在保护模式下不会对其它进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
执行过程:每个独立的进程有程序运行的入口、顺序执行的序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行。

3.守护线程是什么?

在Java线程开发中有两种线程:User Thread(用户线程)、Daemon Thread(守护线程)。
普通用户线程在JVM退出时依然会继续执行,导致JVM并不能退出。
普通线程可以通过setDaemon(true)方法设置为守护线程,守护线程在JVM退出时会自动结束运行。
当JVM所有的非守护线程结束运行时,JVM会自动退出。

4.创建线程有哪几种方式?

(1)继承Thread类并实现run方法,调用继承类的start方法开启线程;
(2)实现Runnable接口,重写run方法,调用线程对象的start方法开启线程;
(3)实现Callable接口,实现call方法,并用FutureTask类包装Callable对象开启线程。

推荐使用Runnable接口,因为Java 中的继承是单继承,一个类有一个父类,如果继承了 Thread 类就无法再继承其他类了,显然使用 Runnable接口更为灵活

5.说一下 runnable 和 callable 有什么区别?

实现Callable接口的任务能够返回执行结果,而实现Runnable接口的任务线程并不能返回执行结果。
Callable接口的call方法允许抛出异常,而Runnable接口的run方法的异常只能在内部消化,不能继续上抛。

6.线程的基本状态以及状态之间的关系?

在这里插入图片描述

基本状态:新建、就绪、运行、阻塞、死亡
其中Running表示运行状态,Runnable表示就绪状态,Blocked表示阻塞状态,阻塞状态又分多种情况,可能是因为调用了wait()方法进入等待池,也可能是执行同步方法或同步代码块进入等锁池,或者是调用sleep()方法或jion()方法等待休眠或其它线程结束,或是因为发生了I/O中断。

7.sleep()和wait()有什么区别?

sleep的类是Thread,wait的类是Object
sleep后程序不会释放同步锁,wait后程序会释放同步锁
sleep可以指定睡眠时间,自动唤醒,wait可以直接用notify唤醒

8.线程的 sleep()方法和 yield()方法有什么区别?

(1)sleep()方法给其它线程运行机会时不考虑线程的优先级,因此会给低优先级的线程以运行的机会;yield()方法只会给相同优先级或更高优先级的线程以运行的机会;
(2)线程执行 sleep()方法后转入阻塞(blocked)状态,而执行 yield()方法后转入就绪(ready)状态;
(3)sleep()方法声明抛出 InterruptedException,而 yield()方法没有声明任何异常;
(4)sleep()方法比 yield()方法(跟操作系统 CPU 调度相关)具有更好的可移植性。

9.notify()和notifyAll()有什么区别?

notify()方法会唤醒对象等待池中的一个线程,进入锁池;
notifyAll()方法会唤醒对象等待池中的所有线程,进入锁池。

10.线程中的run()和start()有什么区别?

调用start()方法是用来启动线程,轮到该线程执行时,会自动调用run方法;
直接运行run方法无法达到启动多线程的目的,相当于调用Thread对象的run方法;
一个线程的start方法只能调用一次,多次调用会抛出异常;而run方法可以调用多次。

11.什么是线程池?

在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源。在 Java 中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收。所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁,这就是”池化资源”技术产生的原因。
线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

12.创建线程池有哪几种方式?

主要使用Excutors提供的通用线程池创建方法,去创建不同配置的线程池。
newCachedThreadPool():用来处理大量短时间工作任务的线程池;
newFixedThreadPool(int nThread):重用指定数目的线程;
newSingleThreadExecutor():工作线程数目限制为1;
newSingleThreadScheduledExecutor()和newScheduledThreadPoll(int corePollSize):可以进行周期或定时性的工作调度;
newWorkStealingPool(int parallelism):JDK8以后才加入。

13.线程池有哪些状态?

RUNNING:线程池被创建,可以接收新线程;
SHUTDOEWN:线程池被关闭,不接收新线程,但是可以处理已有的线程。
STOP:线程池停止,不接受新线程,中断当前的线程,并且不会处理已有的线程。
TIDYING:线程池等待,当线程池处于SHUTDOWN或STOP状态,并且任务队列为空且执行中任务为空则会转变;
TERMINATED:线程池彻底终止,线程池在TIDYING状态中执行完terminated()方法后就会转变为此状态。

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

接收参数:execute()只能执行Runnable类型的任务,submit()可以执行Runnable和Callable类型的任务。
返回值:submit()方法可以返回持有计算结果的Future对象,而execute()没有。
异常处理:submit()的返回值Future调用get方法时,可以捕获处理异常。

15.在java程序中怎么保证多线程的运行安全?

解决原子性问题:JDK Atomic开头的原子类、synchronized、LOCK
解决可见性问题:synchronized、volatile、LOCK
解决有序性问题:Happens-Before规则

16.多线程锁的升级原理是什么?

锁的级别:无锁=>偏向锁=>轻量级锁=>重量级锁
无锁:没有对资源进行锁定,所有线程都可以访问,但是只有一个能修改成功,其他的线程会不断尝试,直至修改成功。
偏向锁:对象的代码一直被同一线程执行,不存在多个线程竞争,偏向锁,指的是偏向第一个加锁线程,该线程不会主动释放偏向锁,只有当其他线程尝试竞争偏向锁时才会被释放。
偏向锁的撤销,需要在某个时间点上没有字节码正在执行时,先暂停拥有偏向锁的线程,然后判断锁对象是否处于被锁定状态。如果线程不处于活动状态,则将对象设置成无锁状态,并撤销偏向锁。如果线程处于活动状态,升级为轻量级锁的状态。
轻量级锁:轻量级锁是指当锁是偏向锁时,被第二个线程B所访问,此时偏向锁会升级为轻量级锁,线程B会通过自旋的形式尝试获取锁,线程不会阻塞,从而提高性能。当前只有一个等待线程,则该线程将通过自旋进行等待。但是当自旋超过一定的次数时,轻量级锁便会升级为重量级锁;当一个线程已持有锁,另一个线程在自旋,而此时又有第三个线程来访时,轻量级锁也会升级为重量级锁。
重量级锁:指当有一个线程获取锁之后,其余所有等待获取该锁的线程都会处于阻塞状态。

17.什么是死锁?

死锁是指两个或两个以上的进程在竞争资源的过程中造成的不可解堵塞。两个线程都在相互等待。

18.怎么防止死锁?

预防:资源一次性分配;可剥夺资源;资源有序分配;超时放弃
避免:银行家算法;
检测:为每个进程和每个资源建立唯一的ID,建立资源分配表和进程等待表
解除:剥夺资源;撤销进程

19.ThreadLocal是什么?有哪些使用场景?

ThreadLocal是线程本地存储,在每个线程中都创建了一个ThresdLocalMap对象,每个线程可以访问自己内部ThreadLocal对象内的value。
经典使用场景是为每个线程分配一个JDBC连接的Connection,这样可以保证每个线程都在各自的Connection上进行数据库的操作,不会出现A线程关了B线程的Connection还有Session管理等问题。

20.说一下synchronized底层实现原理

同步代码块是通过monitorenter和monitorexit指令获取线程的执行权;
同步方法是通过加ACC_SYNCHRONIZED标识实现线程的执行权的控制。

21.synchronized关键字的用法

synchronized关键字可以将对象或方法标记为同步,以实现对对象和方法的互斥访问。可以用synchronized(对象){...}定义同步代码块,或者在声明方法时将synchronized作为方法的修饰符。

22.当一个线程进入一个对象的synchronized方法A之后,其它线程是否可进入此对象的synchronized方法B?

不能。其它线程只能访问该对象的非同步方法,同步方法则不能进入。因为非静态方法上的synchronized修饰符要求执行方法时要获得对象的锁,如果已经进入A方法说明对象的锁已经被取走,那么试图进入B方法的线程只能在等锁池中等待对象的锁。

23.synchronized和volatile的区别是什么?

volatile本质是在告诉vm当前变量在寄存器中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其它线程被阻塞。
volatile仅能实现变量的修改可见性,不能保证原子性;synchronized可以保证变量的修改可见性和原子性。
vilatile不会造成线程的阻塞;synchronized可能造成线程的阻塞。
volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

24.synchronized和Lock有什么区别?

synchronized是关键字,属于jvm层面;Lock是具体类,是api层面的锁。
synchronized无法获取锁的状态;Lock可以判断。
synchronized用于少量同步;Lock用于大量同步。

25.synchronized和ReentrantLock有什么区别?

synchronized代码执行后线程自动释放对锁的占用;ReentrantLock需要手动释放锁。
synchronized不可中断除非抛出异常或执行完成;Reentrantlock可中断。
synchronized非公平锁;Reentrantlock默认非公平锁,也可公平锁。
synchronized要么随机唤醒一个,要么唤醒全部线程;ReentrantLock用来实现分组唤醒需要唤醒的线程,可以精确唤醒。

26.举例说明同步和异步

如果系统中存在临界资源(资源数量少于竞争资源的线程数量),例如正在写的数据以后可能被另外一个线程读到,或者正在读的数据可能已经被另一个线程写过了,那么这些数据就必须进行同步存取(数据库中的排它锁就是最好的例子)。当应用程序在对象上调用了一个需要花费很长时间来执行的方法,并且不希望让程序等待方法的返回时,就应该使用异步编程,在很多情况下采用异步途径往往更有效率。
事实上,所谓的同步就是指阻塞式操作,而异步是非阻塞式操作。

27.说一下atomic的原理

作用:多线程下将属性设置为atomic可以保证读取数据的一致性。
CAS(Compare And Swap),乐观锁机制,先比较再交换,以实现原子性。

28.理解乐观锁和悲观锁

乐观锁:认为每次去拿数据的时候别人不会修改,所以不会上锁,但是每次要拿数据的时候都会先判断数据是否被别人修改。
悲观锁:认为每次去拿数据的时候别人都会修改,所以每次都上锁。
使用场景:乐观锁使用于多读少些的应用类型,这样可以提高吞吐量;相反的情况则使用悲观锁。
;