Java多线程有多种实现方式,本文主要对以下四种实现方式进行详细说明:
- 继承 Thread 类,重写run( )方法
- 实现 Runnable 接口,重写run( )方法
- 实现 Callable 接口,重写call( )方法并使用FutureTask获取call( )方法的返回结果
- 使用线程池
一、继承 Thread 类,重写run( )方法
继承 Thread 类实现多线程的步骤主要为:
- 创建一个类,让其继承 Thread 类并重写 run() 方法。
- 创建该类的实例对象,即创建一个新线程。
- 调用 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());
}
}
控制台输出:
二、实现 Runnable 接口,重写run( )方法实现
实现Runnable接口来实现多线程的步骤主要为:
- 创建一个新类实现 Runnable 接口;
- 创建该类的实例对象;
- 利用Thread有参构造函数 public Thread(Runnable target) 和 Runnable 接口实现类对象创建线程实例;
- 调用 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());
}
}
控制台输出:
三、实现 Callable 接口,重写call( )方法并使用FutureTask获取call( )方法的返回结果
实现Callable接口来实现多线程的步骤主要为:
- 创建一个新类实现 Callable 接口;
- 创建该新类的实例对象;
- 使用Runnable子类FutureTask的有参构造函数public FutureTask(Callable< V > callable)和Callable接口实现类对象创建FutureTask实例
- 利用Thread有参构造函数public Thread(Runnable target)和FutureTask实例创建线程实例
- 调用线程实例的start( )方法启动线程
- 利用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;
}
}
控制台输出:
四、使用线程池
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、线程池的处理流程
线程池的主要处理流程如下:
- 提交任务
- 判断线程数是否达到最大,是则进入判断任务队列,否则创建线程任务
- 判断任务队列是否已满,是则进入判断最大线程数,否就将任务加在任务队列
- 判断线程达到最大线程数,是则进入饱和策略,否则创建非核心线程执行任务
- 执行饱和策略
任务提交有两个方式,分别为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());
}
}
控制台输出:
(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());
}
}
控制台输出:
(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());
}
}
控制台输出:
(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());
}
}
控制台输出: