理解Java中的线程组、线程池和Callable接口

线程组

线程组概述

  • Java中用ThreadGroup来表示线程组,它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

  • 默认情况下,所有的线程都属于主线程组。

    • public final ThreadGroup getThreadGroup() 通过线程对象获取所属的线程组
    • public final String getName() 通过线程组对象获取线程组的名字
    MyRunnable mr = new MyRunnable();
    Thread t1 = new Thread(mr, "张三");
    Thread t2 = new Thread(mr, "李四");
    //获取线程组
    // 线程类里面的方法:public final ThreadGroup getThreadGroup()
    ThreadGroup tg1 = t1.getThreadGroup();
    ThreadGroup tg2 = t2.getThreadGroup();
    // 线程组里面的方法:public final String getName()
    String name1 = tg1.getName();
    String name2 = tg2.getName();
    System.out.println(name1);
    System.out.println(name2);
    // 通过结果我们知道了:线程默认情况下属于main线程组
    // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组
    System.out.println(Thread.currentThread().getThreadGroup().getName());
  • 给线程设置线程组

    • ThreadGroup(String name) 线程组对象的构造器并赋值名字
    • Thread(ThreadGroup group, Runnable target,String name) 线程对象的构造器,直接设置线程组
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("这是一个新的组");
    
        MyRunnable mr = new MyRunnable();
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, mr, "张三");
        Thread t2 = new Thread(tg, mr, "李四");
    
        System.out.println(t1.getThreadGroup().getName());
        System.out.println(t2.getThreadGroup().getName());
    
        //通过组名称设置后台线程,表示该组的线程都是后台线程
        tg.setDaemon(true);

线程池

为什么会有线程池?(线程池概述)

  • 程序创建一个新的线程成本较高,因为它涉及到要与操作系统进行交互。频繁的线程创建和销毁,大大消耗时间和降低系统的效率。
  • 线程池的使用解决了这个问题,它使得多个线程能够一次创建完,放在线程池中,执行完后并不会被销毁,而是再次回到线程池中变成空闲状态,等待下一个对象来使用。并且即拿即用,不用每次都创建,大大提高了线程的复用性,提高系统效率。
  • JDK1.5开始,Java有了内置的线程池。Executors工厂类

内置线程池

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法:

  • public static ExecutorService newFixedThreadPool(int nThreads)

    创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。

    参数: nThreads - 池中的线程数

    返回: 新创建的线程池

  • public static ExecutorService newSingleThreadExecutor()

    创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程

  • Executors.newCachedThreadPool()

    创建一个缓冲池,缓冲池容量大小为Integer.MAX_VALUE

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程

  • 下面是这三个静态方法的具体实现:

    public static ExecutorService newFixedThreadPool(int nThreads) {
      return new ThreadPoolExecutor(nThreads, nThreads,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>());
    }
    public static ExecutorService newSingleThreadExecutor() {
      return new FinalizableDelegatedExecutorService
          (new ThreadPoolExecutor(1, 1,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>()));
    }
    public static ExecutorService newCachedThreadPool() {
      return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
    }

    根据源码的具体实现来看,它们实际上也是调用了ThreadPoolExecutor,只不过参数都已配置好了。

    ThreadPoolExecutor构造器如下:

    public ThreadPoolExecutor(int corePoolSize,
                            int maximumPoolSize,
                            long keepAliveTime,
                            TimeUnit unit,
                            BlockingQueue<Runnable> workQueue)

    参数含义:

    • corePoolSize - 池中所保存的线程数,包括空闲线程
    • maximumPoolSize - 池中允许的最大线程数。
    • keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
    • unit - keepAliveTime 参数的时间单位。
    • workQueue - 执行前用于保持任务的队列。此队列仅由保持 execute 方法提交的 Runnable 任务。
    • handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。
  • 方法源码分析:

    • newFixedThreadPool创建的线程池corePoolSizemaximumPoolSize值是相等的,它使用的LinkedBlockingQueue
    • newSingleThreadExecutorcorePoolSizemaximumPoolSize都设置为1,也使用的LinkedBlockingQueue
    • newCachedThreadPoolcorePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程
    • 实际中,如果Executors提供的三个静态方法能满足要求,就尽量使用它提供的三个方法,因为自己去手动配置ThreadPoolExecutor的参数有点麻烦,要根据实际任务的类型和数量来进行配置。
    • 另外,如果ThreadPoolExecutor达不到要求,可以自己继承ThreadPoolExecutor类进行重写。
  • ExecutorService提供了如下方法:

    • Future<?> submit(Runnable task)

    提交一个 Runnable 任务用于执行,并返回一个表示该任务的 Future。该 Future 的 get 方法在成功 完成时将会返回 null。 参数: task - 要提交的任务

    返回: 表示任务等待完成的 Future

    • <T> Future<T> submit(Callable<T> task)

    提交一个返回值的任务用于执行,返回一个表示任务的未决结果的 Future。该 Future 的 get 方法在成功完成时将会返回该任务的结果

    • void shutdown()

    启动一次顺序关闭,执行以前提交的任务,但不接受新任务。如果已经关闭,则调用没有其他作用。

  • 代码示例:

ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象
//或者Callable对象代表的线程
pool.submit(new MyRunnable());
pool.submit(new MyRunnable());
//结束线程池
pool.shutdown();

Future 接口

Future 表示异步计算的结果。

  • 它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果
  • 计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。
  • 取消则由 cancel 方法来执行。
  • 还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。
  • 如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。
  • FutureTask 是其实现类
方法摘要
boolean cancel(boolean mayInterruptIfRunning) 试图取消对此任务的执行。
V get() 如有必要,等待计算完成,然后获取其结果。
V get(long timeout, TimeUnit unit) 如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。
boolean isCancelled() 如果在任务正常完成前将其取消,则返回 true
boolean isDone() 如果任务已完成,则返回 true

实现多线程的第三种方式

  • 步骤:
    1. 创建实体类,实现Callable接口
    2. 实现接口中的call()方法
    3. 利用 ExecutorService线程池对象 的 <T> Future<T> submit(Callable<T> task()方法提交该Callable接口的线程任务。
// 创建线程池
ExecutorService pool = Executors.newFixedThreadPool(2);
// 可以执行Runnable对象或者Callable对象代表的线程
Future<Integer> f1 = pool.submit(new MyCallable(100));
Future<Integer> f2 = pool.submit(new MyCallable(200));

// V get()
Integer i1 = f1.get();
Integer i2 = f2.get();

System.out.println(i1);
System.out.println(i2);
// 结束
pool.shutdown();
public class MyCallable implements Callable<Integer> {

    private int number;
    public MyCallable(int number) {
        this.number = number;
    }
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int x = 1; x <= number; x++) {
            sum += x;
        }
        return sum;
    }

}
  • 利用匿名内部类方式:
ExecutorService service = Executors.newSingleThreadExecutor();
         Future<String> future = service.submit(new Callable() {
             @Override
             public String call() throws Exception {
                 return "通过实现Callable接口";
             }
         });
         try {
             String result = future.get();
             System.out.println(result);
         } catch (InterruptedException e) {
             e.printStackTrace();
         } catch (ExecutionException e) {
             e.printStackTrace();
         }
  • Lambda表达式方式:
public class CallableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //使用Executors工厂类创建一个单线程池
        ExecutorService es =  Executors.newSingleThreadExecutor();
        //使用这个单线程提交一个Callable接口线程服务,返回值为String
        //Callable接口是一个函数式接口,Java8开始可以直接使用Lambda表达式表示
        //其内部实现了call()方法  V call() throws Exception;
        //并得到该结果值打印
        System.out.println( es.submit(()->"使用lambda表达式的Callable接口").get());
        es.shutdown(); //关闭该线程池
    }

}
  • 实现callable接口,提交给ExecutorService返回值异步执行的。
  • 该方式的优缺点:
    • 优点:
    • 有返回值
    • 可以抛出异常
    • 缺点:
    • 代码较复杂,需要利用线程池

猜你喜欢

转载自blog.csdn.net/hxhaaj/article/details/81087954