Java面试知识点3——线程、多线程与线程池

Java面试知识点3——线程、多线程与线程池

1.简述线程以及多线程。

答:线程指线程是程序的执行路径,或者可以说是程序的控制单元。
线程的执行方式大致可分为就绪(wait),执行(run),阻塞(block)三个状态,多个线程在运行中相互抢夺资源,造成线程在上述的三个状态之间不断的相互转换。

多线程:一个进程可能包含一个或多个进程,当一个进程存在多条执行路径时,就可以将该执行方式称为多线程。

线程的创建方式
(1)继承Thread类,重写父类run()方法
(2)实现runnable接口
(3)使用ExecutorService、Callable、Future实现有返回结果的多线程。(JDK5.0以后)

多线程的创建方式
(1)自定义线程类继承Thread类,重写run()方法,main方法实例化自定义线程类,通过对象调用start()开启线程;
(2)自定义任务类实现Runnable接口,重写run()方法,main方法实例化自定义任务类,实例化Thread类并在构造方法中传递任务类的对象,通过Thread类对象调用start()开启线程。
(3)自定义类实现Callable接口,重写call(),拥有返回值。
(4)通过线程池启动线程

2.线程的生命周期?

答:①就绪(Runnable):线程准备运行,不一定立马就能开始执行;
②运行中(Running):进程正在执行线程的代码;
③等待中(Waiting):线程处于阻塞的状态,等待外部的处理结束;
④睡眠中(Sleeping):线程被强制睡眠;
⑤I/O阻塞(Blocked on I/O):等待I/O操作完成;
⑥同步阻塞(Blocked on Synchronization):等待获取锁;
⑦死亡(Dead):线程完成执行。

3.什么是线程池?如何创建线程池?

答:线程池其实就是一种多线程处理形式,处理过程中可以将任务添加到队列中,然后在创建线程后自动启动这些任务。
创建线程池:一种是 通过 ThreadPoolExecutor 创建的线程池;另一种是通过 Executors 创建的线程池。

  	1. Executors.newFixedThreadPool:创建⼀个固定⼤⼩的线程池,可控制并发的线程数,超出的线程会在队列中等待;
    2. Executors.newCachedThreadPool:创建⼀个可缓存的线程池,若线程数超过处理所需,缓存⼀段时间后会回收,若线程数不够,则新建线程;
    3. Executors.newSingleThreadExecutor:创建单个线程数的线程池,它可以保证先进先出的执⾏顺序;
    4. Executors.newScheduledThreadPool:创建⼀个可以执⾏延迟任务的线程池;
    5. Executors.newSingleThreadScheduledExecutor:创建⼀个单线程的可以执⾏延迟任务的线程池;
    6. Executors.newWorkStealingPool:创建⼀个抢占式执⾏的线程池(任务执⾏顺序不确定)【JDK1.8 添加】。
    7. ThreadPoolExecutor:最原始的创建线程池的⽅式,它包含了 7 个参数可供设置

创建线程池的7大参数依次为:
corePoolSize 核心线程池大小
maximumPoolSize 线程池最大线程数量
keepAliveTime 空闲线程存活时间(值)
unit 空闲线程存活时间(单位)
workQueue 工作队列
threadFactory 线程工厂
handler 拒绝策略

详见 http://t.csdn.cn/d98Om

4.为什么要用线程池?

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

5.线程的run()和start()区别是什么?

答:run()调用Thread中定义的方法;start()启动线程。

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

答:(1)使用安全类,比如:java.util.concurrent下的类等;
(2)使用自动锁synchronized;
(3)使用手动锁Lock。

7.说说synchronized关键字的使用。

答:在使用中可以将synchronized添加到三个位置:修饰实例方法,修饰静态方法,修饰代码块
项目中一般很少使用,因为效率较低,会阻塞,在单例模式中会使用synchronized实现双重检测锁。

8.synchronized与Lock的区别?

答:(1)synchronized是Java内置关键字,在JVM层面;Lock是个Java类;
(2)synchronized可以给类、方法、代码块加锁,而lock只能给代码块加锁;
(3)synchronized不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而lock需要自己加锁和释放锁,如果使用不当没有unLock()去释放锁就会造成思索。通过Lock可以知道有没有成功获取锁,而synchronized做不到。

9.简述一下线程池的执行流程。

答:当提交一个任务到线程池中:
第一步,判断线程池里的核心线程是否都在执行任务,如果不是,则创建一个新的工作线程来执行;如果核心线程都在执行任务,则进入下个流程。
第二步,线程池判断工作队列是否已满,如果工作队列没有慢,则将新提交的任务存储在这个工作队列里,如果工作队列也满了,则进入下个流程。
第三步:判断线程池的线程是否都处于工作状态,如果没有,则创建一个新的工作线程来执行任务,如果已经满了,则交给拒绝(饱和)策略来处理这个任务。

10.线程中多种锁的解释。

答:(1)乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人是否更新这个数据,可以使用版本号等机制。
先进行业务操作,不会对数据加锁,在最后更新数据时检查数据。
(2)悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿到数据的时候都会上锁,这样别人想拿到这个数据就会阻塞直到它拿到锁。
先获取锁,在进行业务操作,必须在事务中使用。
(3)同步锁:使用synchronized关键字将一段代码逻辑,用一把锁给锁起来,只有获得了这把锁的线程才访问。并且同一时刻,只有一个线程能持有这把锁,这样就保证了同一时刻只有一个线程能执行被锁住的代码,从而确保代码的线程安全。
(4)偏向锁:偏向于第一个访问锁的线程,如果在运行过程中,同步锁只有一个线程访问,不存在多线程争用的情况,则线程是不需要触发同步的,减少加锁/解锁的一些CAS操作,就会给线程加一个偏向锁。如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会消除它身上的偏向锁,将锁恢复到标准的轻量级锁。
(5)轻量级锁:由偏向锁升级而来,偏向锁运行在一个线程进入同步块的情况下,当第二个线程加入锁争用的时候,轻量级锁就会升级为重量级锁。
(6)重量级锁:是synchronized,是Java虚拟机中最为基础的锁实现,在这种状态下,Java虚拟机会阻塞加锁失败的线程,并且在目标锁被释放的时候,会唤醒这些线程。
(7)死锁:当两个进程都在等待对象执行完毕才能继续往下执行的时候就会发生死锁。
避免发生死锁的方式:指定获取锁的顺序,并强制线程按照指定的顺序获取锁。
(8)自旋锁:很多synchronized里的代码只是一些很简单的代码,执行时间非常快,此时等待的线程都加锁,可能不是一种值得的操作,因为线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里的代码执行的非常快,不如让等待锁的线程不要被阻塞,而是在synchronized的边界边忙循环。

注:了解自旋锁中提到的用户态内核态:当程序运行在3级特权级上时,就可以称之为运行在用户态,大部分用户直接面对的程序都是运行在用户态;当程序运行在0级特权级上时,就可以称之为运行在内核态。
两种状态的主要差别是:
处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理器是可被抢占的处于内核态执行时,则能访问所有的内存空间和对象,且所占有的处理器是不允许被抢占的。

11.什么是CAS?

答:CAS是一种基于锁的操作,而且是乐观锁。
CAS是通过无限循环来获取数据的,如果在第一轮循环中,a线程获取地址里的值被b线程修改了,那么a线程需要自旋到下次循环才可能有机会执行。
由于CAS操作的就是乐观锁,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试知道成功为止,可以理解为一个无阻塞多线程争抢资源的模型

12.形成死锁的四个必要条件?

答:(1)互斥条件:在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,就只能等 待,直至占有资源的进程用毕释放。
(2)占有且等待条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进 程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
(3)不可抢占条件:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过 来。
(4)循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。(比如一个进程集合,A在 等B,B在等C,C在等A)

13.sleep方法和wait方法的区别??

答:①来自不同的类:wait()方法是Object类的方法,sleep方法是Thread类的方法。
对于锁的占用情况不同:sleep方法没有释放锁,而 wait 方法释放了锁。

②使用范围: wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用。

③唤醒方式:sleep()方法的线程通常是睡眠一定时间后,自动醒来。调用wait()方法,必须采用notify()或者notifyAll()方法唤醒。

14.线程局部变量ThreadLocal的作用以及应用场景?

答:ThreadLocal 的作用和目的:
用于实现线程内的数据共享,相同程序代码下多个模块在同一个线程中运行时要共享一份数据

ThreadLocal 的应用场景:
订单处理包含一系列操作
银行转账包含一系列操作
同一段代码被不同的线程调用运行时

15.如何唤醒一个阻塞的线程?

答:notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,
并不能确切的唤醒某一 个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与优先级无关;

notifyAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所有线程,
而是让它 们竞争,只有获得锁的线程才能进入就绪状态

16.线程锁对象详解

答:当多个线程同时访问共享数据时,可能导致数据不同步,甚至错误!适用于密集型操作 需要资源保持同步,需要用到线程锁。

优点:保证资源同步
缺点:有等待肯定会慢

17.什么是守护线程?

答:守护线程(即daemon thread),是个服务线程,服务于其他的用户线程
1、守护线程,比如垃圾回收线程,就是最典型的守护线程。
2、用户线程,就是应用程序里的自定义线程

用户自定义线程
1、应用程序里的线程,一般都是用户自定义线程。
2、用户也可以自定义守护线程,只需要调用Thread类的设置方法设置一下即可。

18.有关阻塞的概念解释

答:同步:执行一个操作之后,等待结果,然后才继续执行后续的操作。
异步:执行一个操作后,可以去执行其他的操作,然后等待通知再回来执行刚才没执行完的操作。
阻塞:进程给CPU传达一个任务之后,一直等待CPU处理完成,然后才执行后面的操作。
非阻塞:进程给CPU传达任务后,继续处理后续的操作,隔断时间再来询问之前的操作是否完成。这样的过程其实也叫轮询。
同步阻塞:在需要某资源时马上发起请求,并暂停本线程之后的程序,直至获得所需的资源。
同步非阻塞:在需要某资源时马上发起请求,且可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求。
异步阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,将暂停本线程之后的程序,直至获得所需的资源。
异步非阻塞:在需要某资源时不马上发起请求,而安排一个以后的时间再发起请求。当到了那时发出请求时,可以马上得到答复,然后继续执行之后的程序。但如果得到的不是完整的资源,之后将周期性地的请求。

下一篇:Java面试知识点4——JavaWeb

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值