多线程面试题

1、 为什么要使用线程池

  1. 有利于线程创建和销毁的开销,节约资源;
  2. 提高程序执行效率,线程的复用机制;
  3. 统一线程管理;

2、 线程池总共分为几种模式

  1. 固定数量的线程池 (newFixedThreadPool)
  2. 缓存线程池 (newCachedThreadPool)
  3. 并行线程池 (newWorkStealingPool)
  4. 定时线程池 (newScheduleThreadPool)
  5. 单一线程池 (newSingleThreadPool)

3、 你项目中哪些地方使用多线程

  • AOP日志拦截,使用多线程保存日志
  • 下载保存数据
  • 异步发送短信信息
  • 做一些耗时比较方法
  • 导出一些功能

4、 多线程会遇到哪些问题

  1. 多个线程共享同一个全局变量,变量的原子性不一致,可以用原子类解决。
  2. 程序死锁的问题,可以通过java检测工具判断jstack

5、 多线程生命周期

新建状态(NEW )

当程序使用 new 关键字创建了一个线程之后,该线程就处于新建状态,此时仅由 JVM 为其分配内存,并初始化其成员变量的值

就绪状态(RUNNABLE )

当线程对象调用了 start()方法之后,该线程处于就绪状态。Java 虚拟机会为其创建方法调用栈和程序计数器,等待调度运行

运行状态(RUNNING )

如果处于就绪状态的线程获得了 CPU执行权,开始执行 run()方法的线程执行体,则该线程处于运行态。

阻塞状态(BLOCKED )

阻塞状态是指线程因为某种原因放弃cpu使用权,也即让出了 cpu timeslice(时间片),暂时停止行。直到线程进入可运行(runnable)状态,才有机会再次获得 cpu timeslice 转到运行(running)状态。阻塞的情况分三种:

  • 等待阻塞 ( o.wait-> 等待对列 ) :

运行(running)的线程执行 o.wait()方法,JVM 会把该线程放入等待队列(waitting queue)中。

  • 同步阻塞 (lock-> 锁池 )

运行(running)的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则 JVM 会把该线程放入锁池(lock pool)中。

  • 其他阻塞 (sleep/join)

运行(running)的线程执行 Thread.sleep(long ms)或 t.join()方法,或者发出了 I/O 请求时,

JVM 会把该线程置为阻塞状态。当 sleep()状态超时、join()等待线程终止或者超时、或者 I/O

处理完毕时,线程重新转入可运行(runnable)状态。

线程死亡(DEAD )

线程会以下面三种方式结束,结束后就是死亡状态。正常结束

1. run()或 call()方法执行完成,线程正常结束。异常结束

2. 线程抛出一个未捕获的 Exception 或 Error。调用 stop

3. 直接调用该线程的 stop()方法来结束该线程—该方法通常容易导致死锁,不推荐使用。

6、 怎样保证线程顺序执行

1. 使用线程的 join 方法

join(): 是Theard的方法,作用是调用线程需等待该join()线程执行完成后,才能继续用下运行。

应用场景: 当一个线程必须等待另一个线程执行完毕才能执行时可以使用join方法。

2. 使用主线程的 join 方法

这里是在主线程中使用join()来实现对线程的阻塞。

3. 使用线程的 wait 方法

wait(): 是Object的方法,作用是让当前线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁。“直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法”,当前线程被唤醒(进入“就绪状态”)

notify()和notifyAll(): 是Object的方法,作用则是唤醒当前对象上的等待线程;notify()是唤醒单个线程,而notifyAll()是唤醒所有的线程。

wait(long timeout): 让当前线程处于“等待(阻塞)状态”,“直到其他线程调用此对象的notify()方法或 notifyAll() 方法,或者超过指定的时间量”,当前线程被唤醒(进入“就绪状态”)。

应用场景: Java实现生产者消费者的方式。

4. 使用线程池的线程池方法

JAVA通过Executors提供了四种线程池

  • 单线程化线程池(newSingleThreadExecutor);
  • 可控最大并发数线程池(newFixedThreadPool);
  • 可回收缓存线程池(newCachedThreadPool);
  • 支持定时与周期性任务的线程池(newScheduledThreadPool)。

单线程化线程池(newSingleThreadExecutor): 优点,串行执行所有任务。

submit(): 提交任务。

shutdown(): 方法用来关闭线程池,拒绝新任务。

应用场景: 串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行

5. 使用线程的 ConutDownLatch(倒计数) 方法

CountDownLatch: 位于java.util.concurrent包下,利用它可以实现类似计数器的功能。

应用场景: 比如有一个任务C,它要等待其他任务A,B执行完毕之后才能执行,此时就可以利用CountDownLatch来实现这种功能了。

/**
     * 用于判断线程一是否执行,倒计时设置为1,执行后减1
     */
    private static CountDownLatch c1 = new CountDownLatch(1);
               //对c1倒计时-1
                c1.countDown();
                //等待c1倒计时,计时为0则往下运行
                c1.await();

7. 使用 CyclicBarrier (回环栅栏)实现线程按顺序执行

CyclicBarrier(回环栅栏): 通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。我们暂且把这个状态就叫做barrier,当调用await()方法之后,线程就处于barrier了。

应用场景: 公司组织春游,等待所有的员工到达集合地点才能出发,每个人到达后进入barrier状态。都到达后,唤起大家一起出发去旅行。

8. 使用线程的 Sephmore(信号量) 实现线程按顺序执行

Sephmore(信号量): Semaphore是一个计数信号量,从概念上将,Semaphore包含一组许可证,如果有需要的话,每个acquire()方法都会阻塞,直到获取一个可用的许可证,每个release()方法都会释放持有许可证的线程,并且归还Semaphore一个可用的许可证。然而,实际上并没有真实的许可证对象供线程使用,Semaphore只是对可用的数量进行管理维护。

acquire(): 当前线程尝试去阻塞的获取1个许可证,此过程是阻塞的,当前线程获取了1个可用的许可证,则会停止等待,继续执行。

release(): 当前线程释放1个可用的许可证。

应用场景: Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

7、 线程池的拒绝策略

Java 线程池(ThreadPoolExecutor)原理分析与使用_Java仗剑走天涯的博客-优快云博客_threadpoolexecutor详解

这里提到了线程池的饱和策略,那我们就简单介绍下有哪些饱和策略:

AbortPolicy

为Java线程池默认的阻塞策略,不执行此任务,而且直接抛出一个运行时异常,

切记ThreadPoolExecutor.execute需要try catch,否则程序会直接退出。

DiscardPolicy

直接抛弃,任务不执行,空方法

DiscardOldestPolicy

从队列里面抛弃head的一个任务,并再次execute此task。

CallerRunsPolicy

在调用execute的线程里面执行此command,会阻塞入口

RejectedExecutionHandler

用户自定义拒绝策略(最常用)

实现RejectedExecutionHandler,并自己定义策略模式

一般都是 MyRejectHadler Impteper RejectedExecutionHandler 添加日志到数据表里面

public class MyRejectHandler implements RejectedExecutionHandler{

	@Override
	public void rejectedExecution(Runnable task, ThreadPoolExecutor executor) {
		// TODO Auto-generated method stub
		System.out.println("task rejected." + executor.toString());
	}

}

8、 线程池的核心参数有哪些?

  1. corePoolSize : 核心池的大小。
  2. maximumPoolSize: 池中允许的最大线程数,这个参数表示了线程池中最多能创建的线程数量。
  3. keepAliveTime: 当线程数大于corePoolSize时,终止前多余的空闲线程。
  4. workQueue: 当请求的线程数大于maximumPoolSize时,线程进入BlockingQueue阻塞队列。后续示例代码中使用的LinkedBlockingQueue是单向链表,使用锁来控制入队和出队的原子性,两个锁分别控制元素的添加和获取,是一个生产消费模列。
  5. threadFactory 执行程序创建新线程时使用的工厂
  6. RejectedExecutionHandler handler 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序

9、 线程池的的工作原理?

  1. 当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务。
  2. 当线程池大于等于corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行。
  3. workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任。
  4. 当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理。
  5. 当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程。
  6. 当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

10、notify和notifyAll的区别

notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。notifyAll 会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll 方法。比如在生产者-消费者里面的使用,每次都需要唤醒所有的消费者或是生产者,以判断程序是否可以继续往下执行。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值