【并发编程面试题】

并发编程面试题

1.什么是多线程?

多线程是指程序中包含多个执行流,即在一个程序中可以同时运行多个不同的线程来执行不同的任务, 也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。

2.开发过程中什么时候会用到多线程?

异步处理的时候需要多线程。
场景:记录日志、发短信。

  • 发短信:app或网站发送消息提醒等场景。
    如:在营销活动中,用户中奖了,需要短信提醒。可以将发短信的业务放入到另外一个线程中执行,用户晚一会收到短信对整体的业务流程也不会受到影响,反而提升了用户体验。
  • 推送场景:
    如:有一个业务场景,有一个报表监管系统,当收到数据之后,需要将这些数据发送给第三方的监管系统,数据量有百万之多,一条数据按照一秒计算,那需要经过百万秒,200多个小时才能处理完,考虑引入多线程进行并发操作,降低数据推送时间,提高数据推送的实时性。

3.java创建线程的几种方式

(1)继承Thread类
(2)实现Runnable接口
(3)实现Callable接口
通过ExecutorService和Callable实现有返回值的线程
(4)基于线程池创建线程

3.1继承Thread类创建线程

Thread类实现了Runnable接口并定义了操作线程的一些方法,我们可以通过继承Thread类的方式创建一个线程。

(1)具体的实现过程:创建一个类并继承Thread类,然后实例化线程对象并调用start方法启动线程。start方法是一个native方法,通过在操作中系统上启动一个新线程,并最终执行run方法来启动一个线程。run方法内的代码是线程类的具体实现逻辑。
(2)具体的实现代码:

//1.通过继承Thread类创建NewThread线程
public class NewThread extends Thread{
	@Override
	public void run(){
		System.out.println("create a thread by extends Thread");
	}
}
//2.实例化一个NewThread线程对象
NewThread newThread=new NewThread();
//3.调用start方法启动NewThread线程
newThread.start();

以上的代码定义了一个名为NewThread的线程类,该类继承了Thread,run方法内的代码为县城的具体执行逻辑,在使用该线程时新建一个该线程的对象并调用气start方法即可。

3.2实现Runnable接口创建线程

基于Java编程规范,如果子类已经继承(extends)了一个类的话就不能在直接继承Thread类了。此时可以通过实现Runnable接口创建线程。
(1)具体实现过程:通过实现Runnable接口创建ChildrenClassThread线程,实例化名称为childrenThread的线程实例,创建Thread类的实例并传入childrenThread的线程实例,调用线程的start方法启动线程。
(2)具体实现代码:

//1.通过事先Runnable接口创建ChildrenClassThread线程
public class ChildrenClassThread extends SuperClass implements Runnable{
	@Override
	public void run(){
		System.out.println("create a thread by implements Runnable");
	}
//2.实例化一个ChildrenClassThread对象
ChildrenClassThread childrenClassThread=new ChildrenClassThread();
//3.创建一个线程对象并为其传入已经实例化好的childrenClassThread的线程实例
Thread thread=new Thread(childrenClassThread);
//4.调用start方法启动线程
thread.start();

事实上,在传入一个实现了Runnable的线程实例target给Thread后,Thread的run方法在执行时就会调用target.run方法并执行该线程具体的实现逻辑,在JDK源码中run方法的实现代码如下:

@Override
public void run(){
	if(target!=null){
		target.run();
	}

3.3通过ExecutorService和Callable接口实现有返回值的线程

当我们需要在主线程中开启多个子线程并发执行一个任务,然后收集各个线程返回的接货并将最终结果汇总起来,这时就需要用到Callable接口。
(1)具体的实现过程:
创建一个类并实现Callable接口,在call方法中实现具体的运算逻辑并返回计算结果。具体的调用过程:创建一个线程池、一个用于接收返回结果的FutureList及Callable线程实例,使用线程池提交任务并将线程执行后的结果保存在FutureList中,在线程执行结束后遍历FutureList中的Future对象,在该对象上调用get方法就可以获取Callable线程任务返回的数据并汇总结果。
(2)具体的实现代码:

//1.通过实现Callable接口创建MyCallable线程
public class MyCallable implements Callable<String>{
	private String name;
	//通过构造函数为线程传递参数,以定义线程的名称
	public MyCallable(String name){
		this.name=name;
	}

	@Override
	public String call()throw Exception{
	//call方法内为线程的实现逻辑
		return name;
}
//2.创建一个固定大小为5的线程池
ExecutorService pool=ExecutorService.newFixedThreadPool(5);
//3.创建多个有返回值的任务列表List
List<Future> list=new ArrayList<Future>();
for(int i=0;i<5;i++){
	//4.创建一个有返回值的线程实例
	Callable c=new MyCallable(i+ " ");
	//5.提交线程,获取Future对象并将其保存到FutureList中
	Future future=pool.submit(c);
	System.out.println("submit a callable thread:" +i);
	list.add(future);
}
//6.关闭线程池,等待线程执行结束
pool.shutdown();
//7.遍历所有的线程运行结果
for(Future future:list){
	//从Future对象上获取任务的返回值,并将结果输出到控制台
	System.out.println("get the result from callable thread:"+f.get().toString());
}

3.4基于线程池

线程是非常宝贵的计算资源,在每次需要创建并运行结束后销毁是非常浪费系统资源的。我们可以使用缓存策略并通过线程池来创建线程。
(1)具体实现过程:创建一个线程池并用该线程池提交线程任务。
(2)具体实现代码:

//1.创建大小为10的线程池
ExecutorService threadPoll=ExecutorService.newFixedThreadPool(10);
for(int i=0;i<10;i++){
	//2.提交多个线程任务并执行
	threadPoll.execute(new Runnable){
		@Override
		public void run(){
			System.out.println(Thread.currentThread().getName  + "is running");
		});
	}
}

4.Thread类中的start()和run()有什么区别?

(1)通过调用Thread类的start()方法来启动一个线程,这时此线程是处于就绪状态,并没有运行。
(2)run方法是thread的一个普通方法调用。
(3)调用start方法后,一旦得到cpu时间片,就开始执行run()方法。

5.使用线程池的优势是什么?

(1)降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

(2)提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

(3)提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

6.线程池的原理

JVM先根据用户的参数创建一定数量的可运行的线程任务,并将其放入队列中,在线程创建后启动这些任务,如果正在运行的线程数量超过了最大线程数量(用户设置的线程池大小),则超出数量的线程排队等候,再有任务执行完毕后,线程池调度器会发现可用的线程,进而再次从队列中取出任务并执行。

7.线程池的主要作用

(1)线程复用(高效)
(2)线程资源管理
(3)控制操作系统的最大并发数(安全)
以保证系统高效且安全的运行。

8.线程复用怎么实现的?(可简化回答)

在Java中,每个Thread类中都有一个start方法。在程序调用start方法启动线程时,Java虚拟机会调用该类的run方法。在Thread类的run方法中其实调用了Runnable对象的run方法,因此可以继承Thread类,在start方法中的不断循环调用传递进来的Runnable对象,程序就会不断执行run方法中的代码。可以将循环方法中不断获取的Runnable对象存放在队列中,当前线程获取下一个Runnable对象之前可以是阻塞的,这样既能有效控制正在执行的线程个数,也能保证系统中正在等待执行的其他线程有序执行。就简单实现了一个线程池,达到了线程复用的效果。

9.线程池的核心组件有哪些?

Java线程池主要有四个核心组件。
(1)线程池管理器:用于创建并管理线程池。
(2)工作线程:线程池中执行具体任务的线程。
(3)任务接口:用于定义工作线程的调度和执行策略,只有线程实现了该接口,线程中的任务才能够被线程池调度。
(4)任务队列:存放待处理的任务,新的任务将不断被加入队列中,执行完成的任务将被从队列中移除。

10.线程池的核心类

Java中的线程池是通过Executor框架实现的,在该框架中用到了
Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future、FutureTask这几个核心类。

11.构建线程的核心方法及七大参数

(1)核心方法:

public ThreadPoolExecutor(int corePoolSize,int maximumPollSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue){
	this(corePoolSize,maximumPollSize,keepAliveTime,unit,workQueue,Executors.defaultThreadFactory,defaultHandler);
}

(2)七大参数:

序号参数说明
1corePoolSize线程池中核心线程数量
2maximumPollSize线程池中最大线程数量
3keepAliveTime当前线程数量超过corePoolSize(核心线程数量)时,空闲线程的存活时间
4unitkeepAliveTime(空闲线程存活时间)的单位
5workQueue任务队列,被提交但尚未被执行的任务存放的地方
6threadFactory线程工厂,用于创建线程,可使用默认的线程工厂或自定义线程工厂
7handler由于任务过多或其他原因导致线程池无法处理是的任务拒绝策略

12.Java线程池的工作流程

Java线程池的工作流程:线程池刚被创建时,只是向系统申请一个用于执行线程队列和管理线程池的线程资源。在调用execute()添加一个任务时,线程池会按照如下流程执行任务。
工作流程:
(1)如果正在执行的线程数量少于corePoolSize(用户定义的核心线程数量),线程池就会立刻创建线程并执行该线程任务。
(2)如果正在运行的线程数量大于或等于corePoolSize用户定义的核心线程数量),该任务就将被放入阻塞队列中。
(3)在阻塞队列已满且正在运行的线程数量少于maximumPollSize时,线程池会创建非核心线程立刻执行该线程任务。
(4)在阻塞队列已满且正在运行的线程数量大于或等于maximumPollSize时,线程池将拒绝执行该线程任务并抛出RejectExecutionException异常。
(5)在线程任务执行完毕后,该任务将被从线程池队列中移除,线程池将从队列中取下一个线程任务继续执行。
(6)在线程处于空闲状态的时间超过keepAliveTime(线程存活)时间时,正在运行的线程数量超过corePoolSize(核心线程数量),该线程将会被认定为空闲线程并停止。因此在线程池中的所有线程任务都执行完毕后,线程池会收缩到corePoolSize大小。

图例说明:
在这里插入图片描述
若图片不可见则访问地址:https://www.processon.com/view/link/64786dc8b463350a792dc4de

13.线程池的拒绝策略

四种拒绝策略
AbortPolicy 直接丢弃任务,抛出RejectedExecution异常,是默认策略
DiscardPolicy 直接丢弃任务,但不抛出异常
DiscardOldestPolicy 丢弃等待队列中最旧的任务,并执行当前任务
CallerRunsPolicy 用调用者所在的线程处理任务

14.进程和线程的区别?

(1)本质区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位。

(2)包含关系:一个进程至少有一个线程,线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

(3)资源开销:每个进程都有独立的地址空间,进程之间的切换会有较大的开销;线程可以看做轻量级的进程,同一个进程内的线程共享进程的地址空间,每个线程都有自己独立的运行栈和程序计数器,线程之间切换的开销小。

(4)影响关系:一个进程崩溃后,在保护模式下其他进程不会被影响,但是一个线程崩溃可能导致整个进程被操作系统杀掉,所以多进程要比多线程健壮。

15.五种常用的线程池?

(1)newCachedThreadPool:缓存线程池
(2)newFixedThreadPool:固定数量的线程池
(3)newScheduledThreadPool:可定时调度的线程池
(4)newSingleThreadExecutor:保证线程池中有且只有一个可用的线程
(5)newWorkStealingPool:创建持有足够现成的线程池

16.线程的生命周期

线程的生命周期有六种状态:新建、可运行、阻塞、等待、超时等待、终止。

17.线程的几种状态解释?

(1)新建(New):调用new方法新建了一个线程,这时线程处于新建状态。

(2)可运行(Runnable):调用start方法启动了一个线程,这时线程处于可运行状态。
可运行状态又分就绪(Ready)和运行中(Running)两种状态。处于就绪状态的线程等待线程获取CPU资源,在获取CPU资源后,线程会调用run方法进入运行中状态;处于运行中状态的线程在调用yeild方法或丢失处理器资源时,会再次进入就绪状态。

(3)阻塞(Blocked):处于运行中状态的线程在执行sleep方法、I/O阻塞、等待同步锁、等待通知、suspend方法等后,会挂起并进入阻塞状态。
处于阻塞状态的线程由于出现sleep时间已到、I/O方法返回、获得同步锁、收到通知、调用resume方法等情况,会再次进入可运行状态中的就绪状态,继续等待CPU时间片的轮询。该线程在获取CPU资源后,会再次进入运行中状态。

(4)等待(Waiting):线程在调用Object.wait()、Object.join()、LockSupport.park()后会进入等待状态。
处于等待状态的线程在调用Object.notify()、Object.notifyAll()、LockSupport.unpark(Thread)后会再次进入可运行状态。

(5)超时等待(Timed_Waiting):处于可运行状态额线程在调用Thread.sleep(long)、Object.wait(long)、Thread.join(long)、LockSupport.parkNanos()、LockSupport.parkUntil()后会进入超时等待状态。
处于超时等待状态的县城出现超时时间到、等待进入synchronized方法、等待进入synchronized块或者调用Object.notify()、Object.notifyAll()、LockSupport.unpark(Thread)后会再次进入可运行状态。

(6)终止(Terminated):处于可运行状态的线程,在调用run方法或call方法正常执行完成、调用stop方法停止线程或者程序执行错误导致异常退出时,会进入终止状态。

线程的生命周期图例;
在这里插入图片描述
若图片不可见则访问地址:https://www.processon.com/view/link/64799d1197dfee0f88682689

18.线程的基本方法

线程相关的基本方法有wait、notify、notifyAll、setDaemon、sleep、join、yield、interrupt等这些方法控制线程的运行并影响线程的状态变化。

19.sleep和wait的区别

(1)sleep方法属于Thread类,wait方法属于Object类。
(2)sleep方法暂停执行指定的时间,让出CPU给其他线程,但其监控状态依然保持,在指定的时间过后又会自动恢复运行状态。
(3)在调用sleep方法时,线程不会释放对象锁。
(4)在调用wait方法时,线程会放弃对象锁,进入等待锁池。只有针对此对象调用notify方法后,该线程才能进入对象锁池准备获取对象锁,并进入运行状态。

20.start和run方法的区别

【 线程状态:新建、就绪、阻塞、等待、超时等待、终止】。
(1)start方法用于启动线程,真正实现了多线程运行。在调用了线程的start方法后,线程会在后台执行,无需等待run方法体的代码执行完毕,就可以继续执行下面的代码。
(2)在通过调用Thread类的start方法启动一个线程时,此线程处于就绪状态,并没有在运行。
(3)run方法也叫做线程体,包含了要执行的线程的逻辑代码,在调用run方法后,线程会进入运行状态,开始运行run方法中的代码。在run方法运行结束后,该线程终止,CPU再次调度其他线程。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值