多线程是我们学习当中的一大难点,正因如此,在面试中面试官会经常问到多线程相关的问题,如果你都对答如流是非常加分的。那么就总结下在面试中常被问到的多线程面试题:
1、什么是线程?线程和进程的关系?
⼀般我们可以这么回答:
线程是操作系统中能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。
进程:进程是程序的一次执行过程,系统运行是一个程序即是一个进程从创建到运行再到消亡的过程。
线程:线程是一个比进程更小的执行单元,一个进程在执行过程中可以产生多个线程。
2 、实现线程有哪几种方式?
⼀般我们可以这么回答:
(1)进程Thread类创建多线程;
(2)实现Runnable接口,实现run()方法;
(3)实现Callable接口创建多线程;
(4)通过线程池创建线程
3、说下并发和并行的区别?
⼀般我们可以这么回答:
并发:同一时间段内,多个任务都在执行。(单位时间不一定同时执行)
并行:单位时间内,多个任务同时执行。
举个例⼦帮助⼤家理解:
你吃饭吃到⼀半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你⽀持并发。 你吃饭吃到⼀半,电话来了,你⼀边打电话⼀边吃饭,这说明你⽀持并⾏。
4、 说说线程的生命周期有哪几个阶段?
⼀般我们可以这么回答:
- 线程的生命周期包含5个阶段,包括:新建、就绪、运行、阻塞、销毁。
- 新建:就是刚使用new方法,new出来的线程;
- 就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU 资源,谁开始执行;
- 运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
- 阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如
sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,
比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进⼊运行状态;
- 销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
5、 什么是线程安全?能说一个业务场景需要线程安全吗?
⼀般我们可以这么回答:
线程安全指的是当⼀个线程在操作⼀个⽅法或者语句时,其它线程不能对其进行操作,只能 等到该线程结束后才可以进行访问。
比如生产者和消费者模式中,库存的数量就需要保证线程安全,否则当多个线程进行操作 时,就可能导致脏读的发生,因为多个线程可能读到的是相同的值,从而导致库存总量超出。
6、多线程并发 或线程安全问题如何解决?
⼀般我们可以这么回答:
(1)通过synchronized锁(任意对象)来实现线程同步,⾃动锁的思想。
底层实现原理:当有线程进⼊同步代码块之后,利⽤jvm的计数器将锁的标记置为1,当别的线程再想进⼊的时候,发现锁的标记为1, 该线程就去锁池等待,当第⼀个线程出来之后,锁的标记会置为0,之后cpu会随机分配⼀个线程再次进⼊同步代码块.
(2) 通过lock锁的机制,进行手动lock,和unlock,但是这种很容易出现死锁。注意加锁以及解锁的顺序,就可以避免死锁。
(3)通过线程安全的集合类,可以解决并发问题
ConcurrentHashMap CopyonWriteArrayList
(4)使⽤线程池来创建和管理线程,也可以⼀定程度上解决并发问题
7、什么是线程死锁?如何避免死锁
⼀般我们可以这么回答:
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若五外力作用,它们都将无法推进下去。这是⼀个严重的问题,因为死锁会让你的程序挂起无法完成任务,死锁的发生必须满足以下四个条件:
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位、排序,规定所有的进程申请 资源必须以⼀定的顺序(升序或降序)做操作来避免死锁。
- 互斥条件:⼀个资源每次只能被⼀个进程使用。
- 请求与保持条件:⼀个进程因请求资源而阻塞时,对已获得的资源保持不放。
- 不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。
- 循环等待条件:若干进程之间形成⼀种头尾相接的循环等待资源关系。
避免死锁最简单的方法就是阻止循环等待条件,将系统中所有的资源设置标志位,排序,规定所有的进程申请资源必须以一定的顺序(升序或降序)做操作来避免死锁。
8、 synchronized,Lock 有什么区别?
⼀般我们可以这么回答:
- Lock 是⼀个接⼝,而synchronized 是 Java 中的关键字,synchronized 是内置的语言实现;
- Lock 在发生异常时,如果没有主动通过 unLock() 去释放锁,很可能会造成死锁现象,因此使用Lock 时需要在 finally 块中释放锁;synchronized 在发生异常时,会自动释放锁,因此不会导致死锁现象发生;
- Lock 的使用更加灵活,可以有响应中断、有超时时间等;而synchronized 却不行,使用 synchronized 时,等待的线程会⼀直等待下去,直到获取到锁;
9、说说你对乐观锁和悲观锁的理解?
⼀般我们可以这么回答:
乐观锁和悲观锁是两种思想,用于解决并发场景下的数据竞争问题:
- 乐观锁:乐观锁在操作数据时非常乐观,认为别⼈不会同时修改数据。因此乐观锁不会上锁,只是在执行更新的时候判断⼀下在此期间别⼈是否修改了数据:如果别人修改了数据则放弃操作,否则执行操作。
- 悲观锁:悲观锁在操作数据时⽐较悲观,认为别⼈会同时修改数据。因此操作数据时直接把数据锁住, 直到操作完成后才会释放锁;上锁期间其他⼈不能修改数据。
10、什么是线程池?
⼀般我们可以这么回答:
线程池是指在初始化应用程序的过程中创建的一个线程集合,之后每次执行新的任务的时候重用这些线程而非新建一个线程。作用就是提高资源的利用率,提高线程的可管理性。
11、Runnable接口和Callable接口有何区别?
⼀般我们可以这么回答:
相同点:
- Runnable和Callable都是接口
- 都可以编写多线程程序
- 都采用Thread.start()启动线程
不同点:
- Runnable接口run方法无返回值,Callable接口call方法有返回值,是个泛型,和Futrue和 FutureTask配合用来获取异步执行结果。
- Runable接口run方法只能抛出运行时的异常,且无法捕获处理;Callable接口call方法允许抛出异常,可以获取异常信息。