Java多线程有多种实现方式,本文主要对以下四种实现方式进行详细说明:

  • 继承 Thread 类,重写run( )方法
  • 实现 Runnable 接口,重写run( )方法
  • 实现 Callable 接口,重写call( )方法并使用FutureTask获取call( )方法的返回结果
  • 使用线程池

一、继承 Thread 类,重写run( )方法

继承 Thread 类实现多线程的步骤主要为:

  1. 创建一个类,让其继承 Thread 类并重写 run() 方法。
  2. 创建该类的实例对象,即创建一个新线程。
  3. 调用 start() 方法,启动线程。
public class testThread {
	
	public static void main(String[] args) {
		MyThread myThread1 = new MyThread();
        myThread1.setName("Thread-1");
        MyThread myThread2 = new MyThread();
        myThread2.setName("Thread-2");
        MyThread myThread3 = new MyThread();
        myThread3.setName("Thread-3");

        myThread1.start();
        myThread2.start();
        myThread3.start();
	}

}
class MyThread extends Thread{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());	
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_任务队列

二、实现 Runnable 接口,重写run( )方法实现

实现Runnable接口来实现多线程的步骤主要为:

  1. 创建一个新类实现 Runnable 接口;
  2. 创建该类的实例对象;
  3. 利用Thread有参构造函数 public Thread(Runnable target) 和 Runnable 接口实现类对象创建线程实例;
  4. 调用 start() 方法,启动线程。
public class testRunnable {
	
	public static void main(String[] args) {
		MyRunnable first=new MyRunnable();
		Thread firstThread = new Thread(first);
		firstThread.start();
		
		MyRunnable second=new MyRunnable();
		Thread secondThread = new Thread(second);
		secondThread.start();
	}

}
class MyRunnable implements Runnable{

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());	
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_线程池_02

三、实现 Callable 接口,重写call( )方法并使用FutureTask获取call( )方法的返回结果

实现Callable接口来实现多线程的步骤主要为:

  1. 创建一个新类实现 Callable 接口;
  2. 创建该新类的实例对象;
  3. 使用Runnable子类FutureTask的有参构造函数public FutureTask(Callable< V > callable)和Callable接口实现类对象创建FutureTask实例
  4. 利用Thread有参构造函数public Thread(Runnable target)和FutureTask实例创建线程实例
  5. 调用线程实例的start( )方法启动线程
  6. 利用FutureTask的get( )方法获取子线程执行结果
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class testCallable {
	
	public static void main(String[] args) {
		
		MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        try {
			System.out.println("返回值为:" + futureTask.get());
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (ExecutionException e) {
			e.printStackTrace();
		}

	}

}
class MyCallable implements Callable<Object>{

	@Override
	public Object call() throws Exception {
		System.out.println(Thread.currentThread().getName());
		return 1+1;
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_线程池_03

四、使用线程池

1、使用线程池的好处:

在 Java 中构建一个新的线程需要一定的系统开销,如果等到有任务来了再去创建线程效率会很低,而且频繁的创建销毁线程也会给系统带来消耗,因此线程池应运而生。

线程池就是用来存储线程的池子。线程池中包含许多准备运行的线程,只需要为线程池提供一个个任务,线程池就会按照一定的规则去调用这些任务,当一个任务完成后,调用这个任务的线程不会死亡,而是留在线程池中准备为下一个任务提供服务。

总体来说,线程池有如下的优势:

  • 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
  • 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
  • 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2、线程池的核心参数
  • corePoolSize:核心线程数
  • maximumPoolSize:线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞
  • keepAliveTime:线程闲置超时时长,多余的空闲线程等待新任务的最长时间
  • unit:指定 keepAliveTime 参数的时间单位
  • workQueue:任务队列,存储还没来的及执行的任务
  • threadFactory:线程工厂,执行程序创建新线程时使用的工厂
  • handler:拒绝策略,当达到最大线程数时需要执行的饱和策略
3、饱和拒绝策略

当线程池的线程数达到最大线程数时,会执行自己定义好的拒绝策略。拒绝策略需要实现 RejectedExecutionHandler 接口,并实现 rejectedExecution(Runnable r, ThreadPoolExecutor executor) 方法。Executors 框架已经实现了 4 种拒绝策略:

  • AbortPolicy(默认):丢弃任务并抛出 RejectedExecutionException 异常。
  • CallerRunsPolicy:由调用线程处理该任务。
  • DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
  • DiscardOldestPolicy:丢弃队列最早的未处理任务,然后重新尝试执行任务。
4、workqueue阻塞队列

常用的阻塞队列:

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列
  • LinkedBlockingQueue: 由链表结构组成的阻塞队列,,队列可以有界,也可以无界
  • PriorityBlockingQueue:带优先级的无界阻塞队列,优先级由任务的Comparator决定
  • SynchronousQueue: 不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作将一直处于阻塞状态。Executors.newCachedThreadPool()的默认队列
  • 无界队列:队列没有上限的,当没有核心线程空闲的时候,新来的任务可以无止境的向队列中添加,不会创建临时线程。
  • 有界队列:当队列饱和时并超过最大线程数时就会执行拒绝策略

一般使用LinkedBlockingQueue和Synchronous。
线程池的排队策略与BlockingQueue有关。使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

5、线程池的处理流程

线程池的主要处理流程如下:

  1. 提交任务
  2. 判断线程数是否达到最大,是则进入判断任务队列,否则创建线程任务
  3. 判断任务队列是否已满,是则进入判断最大线程数,否就将任务加在任务队列
  4. 判断线程达到最大线程数,是则进入饱和策略,否则创建非核心线程执行任务
  5. 执行饱和策略

任务提交有两个方式,分别为execute()和submit()方法,两者区别如下:

  • execute()方法用于提交不需要返回值的任务,无法判断任务是否被线程池执行成功与否;
  • submit()方法用于提交需要返回值的任务。线程池会返回一个 Future 类型的对象,通过这个 Future 对象可以判断任务是否执行成功,并且可以通过 Future 的 get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成,而使用 get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。
6、功能线程池

JUC包下提供Executors类已经封装好 4 种常见的功能线程池:

  • 定长线程池(FixedThreadPool):只有核心线程,线程数量固定,执行完立即回收,任务队列为链表结构的有界队列,
  • 定时线程池(ScheduledThreadPool ):核心线程数量固定,非核心线程数量无限,执行完闲置 10ms 后回收,任务队列为延时阻塞队列。用于执行定时或周期性的任务。
  • 可缓存线程池(CachedThreadPool):无核心线程,非核心线程数量无限,执行完闲置 60s 后回收,任务队列为不存储元素的阻塞队列。用于执行大量、耗时少的任务
  • 单线程化线程池(SingleThreadExecutor):只有 1 个核心线程,无非核心线程,执行完立即回收,任务队列为链表结构的有界队列。用于不适合并发但可能引起 IO 阻塞性及影响 UI 线程响应的操作,如数据库操作、文件操作等。

上述这四种线程池,都是从 ThreadPoolExecutor 的构造函数创建出来的,只是传入的参数不同。阿里巴巴 Java 开发手册里明确不允许这样创建线程池,一定要通过 ThreadPoolExecutor(xx,xx,xx…) 来明确线程池的运行规则,指定更合理的参数。

7、创建线程池
(1)定长线程池(FixedThreadPool)
public class testPool {

	public static void main(String[] args) {
		
		//创建服务,创建线程池
		//newFixedThreadPool:参数为线程池大小
		ExecutorService service = Executors.newFixedThreadPool(10);
		  
		//执行
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		  
		//关闭连接
		service.shutdown();	 
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_阻塞队列_04

(2)定时线程池(ScheduledThreadPool )
public class testPool {

	public static void main(String[] args) {
		
		//创建服务,创建线程池 
		ExecutorService service = Executors.newScheduledThreadPool(5);
		  
		//执行
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		  
		//关闭连接
		service.shutdown();	 
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_任务队列_05

(3)可缓存线程池(CachedThreadPool)
public class testPool {

	public static void main(String[] args) {
		
		//创建服务,创建线程池 
		ExecutorService service = Executors.newCachedThreadPool();
		  
		//执行
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		  
		  //关闭连接
		service.shutdown();	 
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_阻塞队列_06

(4)单线程化线程池(SingleThreadExecutor)
public class testPool {

	public static void main(String[] args) {
		
		//创建服务,创建线程池 
		ExecutorService service = Executors.newSingleThreadExecutor();
		  
		//执行
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		service.execute(new MyRunnable());
		  
		//关闭连接
		service.shutdown();	 
	}
}

class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println(Thread.currentThread().getName());
	}
}

控制台输出:

java 多线程整理线程池 java线程池实现多线程_线程池_07