Java多线程创建方式小结(含ThreadPoolExecutor使用)

本文原文所在:Java多线程小结

使用多线程能让我们更高效率的执行任务,是高并发的实现手段之一,以前基本一直都用的Runnable+Thread进行操作,现今做个小结。

线程的几种状态

大致分为如下几个状态:

  • 新建(New),使用 new 创建 Thread 实例后。
  • 运行(Runnable),执行了 run() 方法后。
  • 等待(wait),执行了wait、sleep等方法。
  • 阻塞(blocked),因为锁等原因阻塞。
  • 结束(terminated),任务执行完毕。
    线程间的状态转换示意图----摘自《深入理解 Java 虚拟机 第2版》
    threadStatus

常用的几种实现

  1. Thread:
  2. Runnable
  3. Callable
  4. Executors线程池

继承Thread类:

多线程编程中,最简单和直接的方式便是继承 Thread 类,重写 run() 方法即可。如下所示:

//继承 Thread 类
public class Threed01 extends Thread
{
    @Override
    public void run() {
        IntStream.range(10,50)
                .forEach(a->System.out.print(a+"\t"));
    }
}

//主类
public class Main
{
    public static void main(String[]args)
    {
        Threed01 threed01=new Threed01();
        threed01.start();
        System.out.println("启动完毕");
    }
}

实现Runnable接口:

说道 Java 多线程不得不说的就是Runnable接口,应该是接触多线程时最先接触的,该接口是一个函数式接口,只有一个方法run(),不需要参数,也没有返回值,我们在使用上面说的那种模式的时候就需要实现该接口,然后将其作为参数传递以创建 Thread 对象,然后启动线程。

  • Runnable接口定义:
@FunctionalInterface
public interface Runnable {
  public abstract void run();
}
  • 示例:
public class MyRunable implements Runnable
{
  @Override
  public void run()
  {
     System.out.println("a thread");    
  }
}
//then
new Thread(new MyRunable()).start();  //start a thread

//lambda形式
new Thread(()->System.out.println("a thred")).start();

实现Callable :

Callable :

Runnable 接口不能返回值,所以有个与之对应的Callable接口,只有一个 call() 方法,并能返回一个值,返回实现Callable接口时传入的泛型对象。

  • Callable 定义:
@FunctionalInterface
public interface Callable<V> {
  V call() throws Exception;
}
  • 示例:
   Callable<String> myCall=()->"hello ,boy!";
   FutureTask<String> ft = new FutureTask<>(myCall);
   new Thread(ft).start();
   System.out.println(ft.get());   //hello ,boy

通过上面的例子可以看出可以通过 Callable 的 call() 方法返回我们需要的值,不过上面这两种方式都需要我们手动创建去new Thread,很麻烦,而且不停的创建和销毁线程也很耗费资源,所以就有了线程池等解决方案。


Executor 线程池框架

1、通过 ExecutorService 创建线程池:

这是一个线程池管理工具,通过它我们可以提交 Runnable 或 Callable 对象。

涉及几大对象:
  • Executor 接口:只有一个 execute 方法,参数类型为 Runnable。用于执行线程。
  • Executors,封装了一系列静态方法,便于使用者轻松、简约的获取线程池,没一个获取线程池的静态方法的返回对象都是 ExecutorService 类型。因为具体实现类都是实现了 AbstractExecutorService 抽象类(实现了 ExecutorService)。
  • ExecutorService 接口,继承了 Executor 接口
    常用方法如下:
  • excute(Runnable run):用于提交Runnable对象.
  • submit(Runnable/Callable):用于提交 Runnable、Callable 对象,会返回一个Future对象,我们可以通过它获取 call 的返回值等。
  • shutdown():关闭提交,执行后则不能再向其中提交任务,等待已执行的任务运行结束。
  • shutdownNow():停止所有任务。
  • isTerminated():检测是否执行完所有任务。
    示例:
   //建立线程池,单线程的
   ExecutorService exec= Executors.newSingleThreadExecutor();
   //Runnable对象
   exec.execute(()->System.out.println("haha"));  //输出haha
   //Callable
   Future<String> future = exec.submit(()->"wanderer");
   System.out.println(future.get());  //阻塞直到返回值,输出wanderer
   //关闭
   exec.shutdown();//无法再提交任务

在上面的示例中,ExecutorService 我们是通过Executors获取的,通过名字我们可以知道获取的是只有一个工作线程的线程池,每个任务都只能依次执行,完成一个后再进行下一项,我们还可以获取别的种类的线程池,如 newCachedThreadPool、newScheduledThreadPool 等,具体详见源码或者api文档。

2、通过 ThreadPoolExecutor 类手动创建线程池:

在上面那种方式的创建中,我们打开 Executos 的源码,可以看到大多数线程池的实现类其实就是我们此处所说的 ThreadPoolExecutor 类,使用 ThreadPoolExecutor 创建线程池是很多大企业的编码规范,因为使用 Executos 创建线程池很有可能导致出现 OOM(内存溢出)。

Executors 创建线程池的部分方法截图:
threadpool

打开 ThreadPoolExecutor 类,我们可以看到它实现了 AbstractExecutorService 抽象类,该类是 ExecutorService 的一个实现类。此处介绍它的基本使用。

threadpool02

打开源码,可以看到 ThreadPoolExecutor 的一些构造方法,参数emmm,较为多,这也是使用该类进行线程池手动创建的重点所在。该类共4个构造函数,参数最多的共7个,所以就拿它说事情。

//参数最多的构造函数源码
public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) 
    {
      //*******************       
    }
  • corePoolSize:核心线池大小
  • maximumPoolSize:最大线程池大小
  • keepAliveTime:除去核心线程数以外的线程最大空闲存活时间,也就是说,如果线程池中的线程数超过了核心线程池的大小,那么别的线程如果空闲没事儿做,那么在这个时间过后就会被销毁,以减少系统资源消耗。
  • unit:一个枚举量,主要是指明上一个参数的时间单位,例如分钟、秒、小时之类的。
  • workQueue:阻塞队列,用于保存工作线程的。例如在核心线程数满了,并且最大线程数没满,那么就将新提交的任务放入该队列;如果队列满了,且没达到最大线程数,那么创建新的线程执行新提交的任务;如果队列满了,并且线程池达到了最大线程数,那么就按参数中设置的拒绝策略来处理新提交的线程。
  • threadFactory:线程工厂,主要用于定制化的去创建线程。
  • handler:线程池和队列都满了的情况下的拒绝策略。如下,几个策略均是 ThreadPoolExecutor 的静态内部类:
  • CallerRunsPolicy:使用调用者所在线程执行任务
  • AbortPolicy:直接拒绝任务,并且会抛出异常
  • DiscardPolicy:丢弃任务
  • DiscardOldestPolicy:丢弃队列中存在时间最长的任务

吧啦吧啦了半天,现在直接来个实例先,如下:

public class Main2
{
    public static void main(String[]args) throws InterruptedException {
       
        //在下面的创建中,我们使用的阻塞队列长度为2(改为4,则下方不会拒绝)
        //而最大线程池大小为6
        //拒绝策略为直接丢弃
        //而线程提交数为10,所以运行之后会发现输出只有8个任务的结果
        //说明多余的两个被直接丢弃了,具体情况看下方输出
        //如果拒绝策略换为直接拒绝,我们可以在输出看到异常
        //非核心线程的空闲存活时间为5s
        ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor
                (4,
                        6,
                        5,
                        TimeUnit.SECONDS,
                        new ArrayBlockingQueue<>(2),
                        new ThreadPoolExecutor.DiscardPolicy());
        for (int i=0;i<10;i++)
        {
            int finalI = i;
            poolExecutor.execute(()-> {System.out.println("heihei_"+ finalI);
                try {
                    Thread.sleep(1000*5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }

        while (!poolExecutor.isTerminated())
        {
            System.out.println("active:"+poolExecutor.getActiveCount()+"\t\tqueen:"+poolExecutor.getQueue().size());
            Thread.sleep(1000*2);
        }

    }
}
//输出
heihei_3
heihei_0
heihei_2
heihei_1
heihei_7
heihei_6
active:6		queen:2
active:6		queen:2
active:6		queen:2
heihei_4
heihei_5
active:2		queen:0
active:2		queen:0
active:0		queen:0
active:0		queen:0
active:0		queen:0
active:0		queen:0

以上就是关于 ThreadPoolExecutor 的一个简要使用介绍,只要线程池创建出来,其使用和 Executors 创建的线程池使用时类似的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值