2019 Java面试问题及答案详解(三)Java多线程篇

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

并行:是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU) 并发:指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。(表面看是CPU在同时执行多个任务,其实实际上是因为CPU瞬间切换到其他任务的速度特别快,在不同的任务之间一直在不停的切换,给不同的任务分配了不同的时间。)

2. 线程和进程的区别?

(1)进程是一段正在执行的程序,是资源分配的基本单元,而线程是CPU调度的基本单元。 (2)进程间相互独立进程,进程之间不能共享资源,一个进程至少有一个线程,同一进程的各线程共享整个进程的资源(寄存器、堆栈、上下文)。 3、线程的创建和切换开销比进程小

3. 守护线程是什么?

java提供了俩类的线程:用户线程和守护线程(user thread and Daemon thread)。

用户线程是高优先级的线程。JVM虚拟机在结束一个用户线程之前,会先等待该用户线程完成它的task。

在另一方面,守护线程是低优先级的线程,它的作用仅仅是为用户线程提供服务。

正是由于守护线程是为用户线程提供服务的,仅仅在用户线程处于运行状态时才需要守护线程。

另外,一旦所有的用户线程都运行完毕,那么守护线程是无法阻止JVM退出的。

这也是存在于守护线程中的无限循环不会产生问题的原因,因为包括finally 块的任何代码都不会被执行,一旦所有的用户线程结束运行之后。

出于这个原因,我们不推荐使用守护线程处理I/O任务。(原为来源:https://www.jianshu.com/p/6a4e303d9f42)

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

(1)继承Thread类

(2)实现Runnable接口

一个类如果实现了Runnable接口或者继承了Thread类,那么它就是一个多线程类,如果是要实现多线程,还需要重写run()方法,所以run() 方法是多线程的入口

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

两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;而实现Runnable接口的任务线程不能返回结果;

Callable接口的call()方法允许抛出异常;而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;

Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取结果;当不调用此方法时,主线程不会阻塞!

6. 线程有哪些状态?

线程状态有 5 种,新建,就绪,运行,阻塞,死亡。 如图所示:

2019 Java面试问题及答案详解(三)Java多线程篇

2019 Java面试问题及答案详解(三)Java多线程篇

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

这两个方法来自不同的类分别是Thread和Object 最主要是sleep方法没有释放锁,而wait方法释放了锁,使得其他线程可以使用同步控制块或者方法(锁代码块和方法锁)。

wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用(使用范围) sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

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

notify只会随机选取一个处于等待池中的线程进入锁池去竞争获取锁的机会

notifyAll会让所有处于等待池的线程全部进入锁池去竞争获取锁的机会

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

run()方法:是在主线程中执行方法,和调用普通方法一样;(按顺序执行,同步执行)

start()方法:是创建了新的线程,在新的线程中执行;(异步执行)

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

Java通过Executors提供四种线程池,分别为:

* newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

* newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

* newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。

* newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。

11.线程池都有哪些状态?

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

2019 Java面试问题及答案详解(三)Java多线程篇

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

接收的参数不一样

返回值不一样

submit方便Exception处理

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

线程的安全性问题体现在:

原子性:一个或者多个操作在 CPU 执行的过程中不被中断的特性

可见性:一个线程对共享变量的修改,另外一个线程能够立刻看到

有序性:程序执行的顺序按照代码的先后顺序执行 导致原因:

缓存导致的可见性问题

线程切换带来的原子性问题

编译优化带来的有序性问题 解决办法:

JDK Atomic开头的原子类、synchronized、LOCK,可以解决原子性问题

synchronized、volatile、LOCK,可以解决可见性问题

Happens-Before 规则可以解决有序性问题 Happens-Before 规则如下:

程序次序规则:在一个线程内,按照程序控制流顺序,书写在前面的操作先行发生于书写在后面的操作

管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作

volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作

线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作

线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测

线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生

对象终结规则:一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始

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

先说说为什么会有锁升级 因为Sycronized是重量级锁(也是悲观锁),每次在要进行锁的请求的时候,如果当前资源被其他线程占有要将当前的线程阻塞加入到阻塞队列,然后清空当前线程的缓存,等到锁释放的时候再通过notify或者notifyAll唤醒当前的线程,并让其处于就绪状态。这样线程的来回切换是非常消耗系统资源的,而且有的时候,线程刚挂起资源就释放了。而Java的线程是映射到操作系统的原生线程之上的每次线程的阻塞或者唤醒都要经过用户态到核心态或者核心态到用户态的转化,这样是十分浪费资源的,这样就会造成性能上的降低,因此JVM对Sychronized进行了优化,将Sycronized分为三种锁的级别:偏向锁,轻量级锁,重量级锁。 很多的时候,对于一个可能发生并发访问的对象而言,其实很少会被竞争,就算有些资源存在竞争也是在很少的一段时间资源就会被释放,而这样的情况下将线程挂起是十分浪费性能的。 偏向锁(乐观锁): 当锁对象第一次被线程获取的时候,虚拟机会将锁对象的对象头中的锁标志位设置成为01,并将偏向锁标志设置为1,线程通过CAS的方式将自己的ID值放置到对象头中(因为在这个过程中有可能会有其他线程来竞争锁,所以要通过CAS的方式,一旦有竞争就会升级为轻量级锁了),如果成功线程就获得了该轻量级锁。这样每次再进入该锁对象的时候不用进行任何的同步操作,直接比较当前锁对象的对象头是不是该线程的ID,如果是就可以直接进入。偏向锁升级为轻量级锁 偏向锁是一种无竞争锁,一旦出现了竞争大多数情况下就会升级为轻量级锁。现在我们假设有线程1持有偏向锁,线程2来竞争偏向锁会经历以下几个过程: 1. 首先线程2会先检查偏向锁标记,如果是1,说明当前是偏向锁,那么JVM会找到线程1,看线程1是否还存活着2 2. 如果线程1已经执行完毕,就是说线程1不存在了(线程1自己是不会去释放偏向锁的),那么先将偏向锁置为0,对象头设置成为无锁的状态,用CAS的方式尝试将线程2的ID放入对象头中,不进行锁升级,还是偏向锁 3. 如果线程1还活着,先暂停线程1,将锁标志位变成00(轻量级锁)然后在线程1的栈帧中开辟出一块空间(Display Mark Word)将对象头的Mark Word置换到线程一的栈帧当中,而对象头中此时存储的是指向当前线程栈帧的指针。此时就变成了轻量级锁。继续执行线程1,然后线程2采用CAS的方式尝试获取锁。轻量级锁与偏向锁最大的不同之处 轻量级锁和偏向锁的不同之处就在于轻量级锁对于获取锁对象采用CAS的同步方式而偏向锁直接是把整个同步过程给取消。轻量级锁(乐观锁) 轻量级锁如何创建在上面已经讲过了,接下来说说轻量级锁如何获取锁对象,轻量级锁是通过CAS也就是自旋的方式尝试获取锁对象,一旦失败会先检查,对象头中存储的是否是指向当前线程栈帧的指针,如果是,就可以获取对象,如果不是说明存在竞争那么就要膨胀为重量级锁。轻量级锁的解锁也是通过CAS的方式尝试将对象头的Mark Word和线程中的Display Mark Word替换回来,如果成功,就释放锁,如果失败说明还有许多其他等待锁的线程(说明此时已经不是轻量级锁而是重量级锁了),会将这些线程唤醒,然后释放锁。轻量级锁膨胀为重量级锁 一旦有两条以上的线程竞争锁,轻量级锁膨胀为重量级锁,锁的状态变成10,此时对象头中存储的就是指向重量级锁的栈帧的指针。而且其他等待锁的线程要进入阻塞状态,等待重量级锁释放再来被唤醒然后去竞争。

15. 什么是死锁?

是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去

16. 怎么防止死锁? 1、尽量使用tryLock(long timeout, TimeUnit unit)的方法(ReentrantLock、ReentrantReadWriteLock),设置超时时间,超时可以退出防止死锁。 2、尽量使用java.util.concurrent(jdk 1.5以上)包的并发类代替手写控制并发,比较常用的是ConcurrentHashMap、ConcurrentLinkedQueue、AtomicBoolean等等,实际应用中java.util.concurrent.atomic十分有用,简单方便且效率比使用Lock更高 。 3、尽量降低锁的使用粒度,尽量不要几个功能用同一把锁 。 4、尽量减少同步的代码块。

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

共性:volatile与synchronized都用于保证多线程中数据的安全

区别:

(1)volatile修饰的变量,jvm每次都从主存(主内存)中读取,而不会从寄存器(工作内存)中读取。

而synchronized则是锁住当前变量,同一时刻只有一个线程能够访问当前变量

(2)volatile仅能用在变量级别,而synchronized可用在变量和方法中

(3)volatie仅能实现变量的修改可见性,无法保证变量操作的原子性。而synchronized可以实现变量的修改可见性与原子性

(4)volatile不需要加锁,因此不会造成线程的阻塞,而且比synchronized更轻量级,而synchronized可能导致线程的阻塞

18. synchronized 和 Lock 有什么区别?

2019 Java面试问题及答案详解(三)Java多线程篇

总结:关于多线程的常规面试问题大致如上。由于白天需要上班,所以这种文字描述的问题整理起来既浪费时间又有点枯燥无味。但是,又是我们必须理解必不可少的。整理完,一套问题后,以后后面大部分时间都用来详细针对各个知识点做对应的demo。期待您的加入,一起学习一起巩固,一起good good study,day day up!


分享到:


相關文章: