java 多线程面试题及答案

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

并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。并行没有对 CPU 资源的抢占;并发执行的线程需要对 CPU 资源进行抢占。
并行执行的线程之间不存在切换;并发操作系统会根据任务调度系统给线程分配线程的 CPU 执行时间,线程的执行会进行切换。

2:线程和进程的区别?

1、进程是资源分配的最小单位,线程是程序执行的最小单位(资源调度的最小单位)一个程序至少有一个进程,一个进程至少有一个线程。
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。

3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。

4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

3:创建线程有哪几种方式

(1):继承Thread类

这是最直观的一种方式,让一个类继承Thread重写run方法,然后把它new出来,这便是创建了一个新线程。

java 多线程面试题及答案_第1张图片

 (2):实现Runnable接口

通过实现Runnable接口的run方法,可以得到一个“可被执行的任务”,然后在new Thread的时候将这个任务传进去。

java 多线程面试题及答案_第2张图片

 (3):Callable+FutureTask

        1:首先让一个类实现Callable(泛型)接口的call方法,这一步是写一个“可被调用的任务”;
        2:再new一个FutureTask(”未来的任务“),同时将上一步的Callable传进去;
        3:最后new一个Thread,同时将Future传进去。
        4:请注意这种方式与上面两种方式的区别,此方式可以让你的任务返回一个返回值,类型任              你定,都是可以的。

java 多线程面试题及答案_第3张图片

4:说一下 runnable 和 callable 有什么区别?

Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。

5:线程有哪些状态? 

线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程                       序 还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待                        或 者睡眠中回来之后,也会处于就绪状态。
运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开                 始运行run函数当中的代码。
阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个时间的发生(比如说某项资源就绪)                  之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死                 亡的线程,无法再使用start方法令其进入就绪

java 多线程面试题及答案_第4张图片

6:谈谈什么是守护线程以及做用 ?

Java线程分为用户线程和守护线程。
守护线程是程序运行的时候在后台提供一种通用服务的线程。所有用户线程停止,进程会停掉所有守护线程,退出程序。
Java中把线程设置为守护线程的方法:在 start 线程之前调用线程的 setDaemon(true) 方法,否则会抛出IllegalThreadStateException异常,该线程仍默认为用户线程

守护线程创建的线程也是守护线程,守护线程不应该访问、写入持久化资源,如文件、数据库,因为它会在任何时间被停止,导致资源未释放、数据写入中断等问题

7:乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。Java里面的同步原语synchronized关键字的实现是悲观锁。

乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。在Java中j原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

乐观锁的实现方式: 使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。

java中的Compare and Swap即CAS(比较和交换) ,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。

8:什么是CAS操作,缺点是什么?

CAS的基本思路就是,如果这个地址上的值和期望的值相等,则给其赋予新值,否则不做任何事儿,但是要返回原值是多少。每一个CAS操作过程都包含三个运算符:一个内存地址V,一个期望的值A和一个新值B,操作的时候如果这个地址上存放的值等于这个期望的值A,则将地址上的值赋为新值B,否则不做任何操作。

CAS缺点:

ABA问题:比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。

循环时间长开销大: 对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。

只能保证一个共享变量的原子操作: 当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。

9:在java中wait和sleep方法的不同?

(1):在java.lang.Thread类中,提供了sleep(),
             而java.lang.Object类中提供了wait(), notify()和notifyAll()方法来操作线程


(2):sleep()可以将一个线程睡眠,参数可以指定一个时间。
             而wait()可以将一个线程挂起,直到超时或者该线程被唤醒。 
             wait有两种形式wait()和wait(milliseconds).

(3):最大的不同是在等待时wait会释放锁,而sleep一直持有锁。Wait通常被用于线程间交互,               sleep通常被用于暂停执行。

(4):sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常

(5):wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,

             而sleep可以在任何地方使用

 synchronized(x){
      x.notify()
     //或者wait()
   }

10:notify()和notifyAll()有什么区别?

当一个线程进入wait之后,就必须等其他线程notify/notifyall,使用notifyall,可以唤醒所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。如果没把握,建议notifyAll,防止notigy因为信号丢失而造成程序异常。

11:为什么wait, notify 和 notifyAll这些方法不在thread类里面?

JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

12:怎么检测一个线程是否拥有锁?

在java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁。

13:synchronized与Lock两者区别

java 多线程面试题及答案_第5张图片

区别:

  1:Lock是一个接口,而Synchronized是关键字。

  2:Synchronized会自动释放锁,而Lock必须手动释放锁。

  3:Lock可以让等待锁的线程响应中断,而Synchronized不会,线程会一直等待下去。

  4:通过Lock可以知道线程有没有拿到锁,而Synchronized不能。

  5:Lock能提高多个线程读操作的效率。

  6:Synchronized能锁住类、方法和代码块,而Lock是块范围内的
 

14:Synchronized实现原理

synchronized可以让代码同步,所谓同步代码就是同一时刻只能一个线程执行这段代码,synchronized可以锁对象,可以锁方法,可以锁class对象。synchronized就是锁对象头,java中每个对象都有固定格式的对象头,对象头中有一个mark word,64位虚拟机中mark word有64个bit,在对象头中有两个bit是用来标志锁的,有一个bit标志是否偏向锁,还有一个bit是锁标志位,所以synchronized给对象加锁就是就是修改对象这两个锁标志位的数值,

一个bit:   1偏向锁 ,0非偏向锁

另一个bit:  01无锁  ,  00轻量锁   , 10重量级锁   ,11GC标记

synchronized关键字加到static静态方法和非static静态方法区别

synchronized关键字加到static静态方法上是给Class类上锁,简称类锁(锁的事当前类的字节码)
而加到非static静态方法是给对象加锁

lock实现原理

  • lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)

  • lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。lock的基本操作还是通过乐观锁来实现的

  • lock释放锁的过程:修改状态值,调整等待链表。

  • lock大量使用CAS+自旋。因此根据CAS特性,lock建议使用在低锁冲突的情况下。目前java1.6以后,官方对synchronized做了大量的锁优化(偏向锁、自旋、轻量级锁)。因此在非必要的情况下,建议使用synchronized做同步操作。

15:JVM中哪个参数是用来控制线程的栈堆栈大小的

答:-Xss

16:为什么wait, notify 和 notifyAll这些方法不在thread类里面?

JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。

17:为什么需要线程池?

(1):减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

(2):可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

常见线程池

①newSingleThreadExecutor
单个线程的线程池,即线程池中每次只有一个线程工作,单线程串行执行任务
②newFixedThreadExecutor(n)
固定数量的线程池,每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行
③newCacheThreadExecutor(推荐使用)
可缓存线程池,当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
④newScheduleThreadExecutor
大小无限制的线程池,支持定时和周期性的执行线程
 

ThreadPoolExecutor 有哪些常用的方法?

submit()/execute():执行线程池

shutdown()/shutdownNow():终止线程池

isShutdown():判断线程是否终止

getActiveCount():正在运行的线程数

getCorePoolSize():获取核心线程数

getMaximumPoolSize():获取最大线程数

getQueue():获取线程池中的任务队列

allowCoreThreadTimeOut(boolean):设置空闲时是否回收核心线程这些方法可以用来终止线程池、线程池监控等。

说说线程池创建需要的那几个核心参数的含义

ThreadPoolExecutor 最多包含以下七个参数:

corePoolSize:线程池中的核心线程数

maximumPoolSize:线程池中最大线程数

keepAliveTime:闲置超时时间

unit:keepAliveTime 超时时间的单位(时/分/秒等)

workQueue:线程池中的任务队列

threadFactory:为线程池提供创建新线程的线程工厂

rejectedExecutionHandler:线程池任务队列超过最大值之后的拒绝策略

说说submit(和 execute两个方法有什么区别?

submit() 和 execute() 都是用来执行线程池的,

execute() 执行线程池不能有返回方法

submit() 可以使用 Future 接收线程池执行的返回值。

shutdownNow() 和 shutdown() 两个方法有什么区别?

shutdownNow() 和 shutdown() 都是用来终止线程池的。

shutdown(): 程序不会报错,也不会立即终止线程,它会等待线程池中的缓存任务执行完之后再退出,执行了 shutdown() 之后就不能给线程池添加新任务了;

shutdownNow() :会试图立马停止任务,如果线程池中还有缓存任务正在执行,则会抛出 java.lang.InterruptedException: sleep interrupted 异常。

线程池的工作原理

当线程池中有任务需要执行时,线程池会判断如果线程数量没有超过核心数量就会新建线程池进行任务执行,如果线程池中的线程数量已经超过核心线程数,这时候任务就会被放入任务队列中排队等待执行;如果任务队列超过最大队列数,并且线程池没有达到最大线程数,就会新建线程来执行任务;如果超过了最大线程数,就会执行饱和策略。

java 多线程面试题及答案_第6张图片

线程池为什么需要使用(阻塞)队列?

(1):因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换。

(2):创建线程池的消耗较高。

你可能感兴趣的:(面试,java,面试,多线程)