Java面试题(十六)多线程面试题

本文介绍Java中创建多线程的多种方法,包括继承Thread类、实现Runnable接口、实现Callable接口以及使用Executors创建线程池。详细阐述了线程池的原理、优点、核心组件、常见类型及其配置参数。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一.java创建多线程的几种常用方式

1.继承Thread类

创建一个线程类并继承Thread ,并覆盖run方法,然后在启动线程的时候调用start

public class MyThread extends Thread {
	public MyThread() {}
	
	public void run() {
		for(int i = 0; i < 10; i ++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread();
		MyThread myThread2 = new MyThread();
		MyThread myThread3 = new MyThread();
		myThread1.start();
		myThread2.start();
		myThread3.start();
	}
}

2.实现Runnable接口

创建一个线程类并实现Runnable接口 ,并实现run方法,然后在启动线程的时候调用start

public class MyRunable implements Runnable{

	public void run() {
		for(int i = 0; i < 10; i ++) {
			System.out.println(Thread.currentThread().getName() + ":" + i);
		}
	}
	public static void main(String[] args) {
		MyRunable myRun = new MyRunable();
        MyThread myThread1 = new Thread(myRun);
        MyThread myThread2 = new Thread(myRun);
        MyThread myThread3 = new Thread(myRun);
        myThread1.start();
		myThread2.start();
		myThread3.start();
    }
}

3.实现Callable接口

创建一个线程类,实现Callable接口,并实现call方法,然后在启动线程的时候调用start
callable接口是有返回值的,借助FutureTask类能够实现判断任务是否中断,中断任务以及任务是否完成



public class MyCallnable implements Callable<String> {

    public String call() throws Exception {
        for (int i = 0; i < 10; i++) {
            System.out.println("myCallable线程正在执行:"+i);
        }
        return "MyCallabe线程执行完毕";
    }

    public static void main(String[] args) {
        //创建futuretask对象
        FutureTask<String> futureTask = new FutureTask<String>(new MyCallnable());
        //创建Thread对象,传入futureTask
        Thread thread1 = new Thread(futureTask);
        Thread thread2 = new Thread(futureTask);
        Thread thread3 = new Thread(futureTask);
        thread1.start();
        thread2.start();
        thread3.start();

    }
}

4.Executors利用线程池来创建线程

Executors提供五种线程池,分别为:

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

下面我们就来重点说下线程池

二.单线程的弊端

  • 每次创建线程,销毁线程性能差,占用系统资源。
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断、线程状态返回等等。

三.线程池

1.什么是线程池

通常我们使用线程的时候就去创建一个线程,执行完了然后再回收释放资源,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程释放线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务,其他的任务就可以复用现成的线程资源,不必再创建销毁,浪费系统资源,那么就有了线程池这个东西。

2.线程池有哪些优点

  • 降低系统资源消耗,通过重用已存在的线程,降低线程创建和销毁造成的消耗;
  • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行;
  • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))。
  • 根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。

3.Executor中的几个核心类

  • Executor接口:声明了execute(Runnable runnable)方法,执行任务代码
  • ExecutorService接口:继承Executor接口,声明方法:submit、invokeAll、invokeAny以及shutDown等
  • AbstractExecutorService抽象类:实现ExecutorService接口,基本实现ExecutorService中声明的所有方法
  • ScheduledExecutorService接口:继承ExecutorService接口,声明定时执行任务方法
  • ThreadPoolExecutor类:继承类AbstractExecutorService,实现execute、submit、shutdown、shutdownNow方法
  • ScheduledThreadPoolExecutor类:继承ThreadPoolExecutor类,实现ScheduledExecutorService接口并实现其中的方法
  • Executors类:提供快速创建线程池的方法

4.常用线程池

Executors提供五种线程池,分别为:

  • newCachedThreadPool:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  • newFixedThreadPool:创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  • newScheduledThreadPool:创建一个定长线程池,支持定时及周期性任务执行。
  • newSingleThreadExecutor:创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
  • newWorkStealingPool:创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

5.线程池中的参数

  • corePoolSize(线程池基本大小):当向线程池提交一个任务时,若线程池已创建的线程数小于corePoolSize,即便此时存在空闲线程,也会通过创建一个新线程来执行该任务,直到已创建的线程数大于或等于corePoolSize时,(除了利用提交新任务来创建和启动线程(按需构造),也可以通过 prestartCoreThread() 或 prestartAllCoreThreads() 方法来提前启动线程池中的基本线程。)

  • maximumPoolSize(线程池最大大小):线程池所允许的最大线程个数。当队列满了,且已创建的线程数小于maximumPoolSize,则线程池会创建新的线程来执行任务。另外,对于无界队列,可忽略该参数。

  • keepAliveTime(线程存活保持时间)当线程池中线程数大于核心线程数时,线程的空闲时间如果超过线程存活时间,那么这个线程就会被销毁,直到线程池中的线程数小于等于核心线程数。

  • workQueue(任务队列):用于传输和保存等待执行任务的阻塞队列。

  • threadFactory(线程工厂):用于创建新线程。threadFactory创建的线程也是采用new Thread()方式,threadFactory创建的线程名都具有统一的风格:pool-m-thread-n(m为线程池的编号,n为线程池内的线程编号)。

  • handler(线程饱和策略):当线程池和队列都满了,再加入线程会执行此策略。

6.任务执行流程

1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。

2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行

3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务

4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理

5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,释放空闲线程

6.当设置allowCoreThreadTimeOut(true)时,该参数默认false,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭

7.execute和submit的区别

  • execute(),执行一个任务,没有返回值。
  • submit(),提交一个线程任务,有返回值。

8.线程池中的工作队列

  • ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。

  • LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列

  • SynchronousQueue:是一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。

  • PriorityBlockingQueue:是一个具有优先级的无限阻塞队列。

9.线程任务的拒绝策略

当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略,通常有以下四种策略:

  • ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
  • ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  • ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  • ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

珍妮玛•黛金

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值