zoukankan      html  css  js  c++  java
  • 使用Callable接口创建线程和使用线程池的方式创建线程

    1、使用Callable接口的方式实现多线程,这是JDK5.0新增的一种创建多线程的方法

     1 package com.baozi.java2;
     2 
     3 import java.util.concurrent.Callable;
     4 import java.util.concurrent.ExecutionException;
     5 import java.util.concurrent.FutureTask;
     6 
     7 public class ThreadNew {
     8     public static void main(String[] args){
     9         //3、创建callable接口实现类的对象
    10         NewThread newThread= new NewThread();
    11         //4、将此callable接口实现类的对象作为参数传递到FutureTask类的构造器中创建出一个FutureTask的实现类
    12         FutureTask futureTask = new FutureTask(newThread);
    13         //5、将FutureTask类的对象作为参数传递给Thread类的构造器创建一个Thread对象然后调用start()方法启动该线程
    14         new Thread(futureTask).start();
    15         try {
    16             //6、可以通过futureTask.get()方法获取call()方法中的返回值
    17             System.out.println(futureTask.get());
    18         } catch (InterruptedException e) {
    19             e.printStackTrace();
    20         } catch (ExecutionException e) {
    21             e.printStackTrace();
    22         }
    23     }
    24 
    25 }
    26 //1、创建一个类实现callable接口
    27 class NewThread implements Callable<Integer> {
    28     //2、实现该接口中的call()方法
    29     @Override
    30     public Integer call() {
    31         int sum = 0;
    32         for (int i = 1; i <= 100; i++) {
    33             if (i % 2 == 0) {
    34                 sum += i;
    35             }
    36         }
    37         return sum;
    38     }
    39 }

    2、使用Callable接口创建多线程和使用Runnable接口创建多线程的异同

    相比较Runnable接口,Callable接口的功能更加强大。

    • 相比较Runnable接口中需要重写的run()方法,Callable接口需要重写的call()方法有返回值
    • 该方法可以抛出异常,外边的程序可以利用这个异常获取异常信息
    • 支持泛型的返回值
    • 需要借助FutureTask类,获取该线程的返回值

    Future接口:

    • 可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等操作
    • FutureTask是Future接口的唯一实现类
    • FutureTask同时实现了Runnable、Callable接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

    3、使用线程池的方法创建多线程

     1 package com.baozi.java2;
     2 
     3 import java.util.concurrent.ExecutorService;
     4 import java.util.concurrent.Executors;
     5 import java.util.concurrent.ThreadPoolExecutor;
     6 
     7 public class ThreadPool {
     8     public static void main(String[] args) {
     9         //1、先创建一个线程池
    10         ExecutorService service = Executors.newFixedThreadPool(10);
    11         //3、为线程池中线程分配任务并执行
    12         ThreadPoolExecutor service1=(ThreadPoolExecutor)service;
    13         System.out.println(service.getClass());
    14         service.execute(new NumberThread1());
    15         service.execute(new NumberThread2());
    16         service.execute(new NumberThread3());
    17         //4、关闭线程池
    18         service.shutdown();
    19     }
    20 }
    21 //2、创建要执行某种操作的线程
    22 class NumberThread1 implements Runnable {
    23     @Override
    24     public void run() {
    25         for (int i = 0; i <= 100; i++) {
    26             if (i % 2 == 0) {
    27                 System.out.println(Thread.currentThread().getName() + ":" + i);
    28             }
    29         }
    30     }
    31 }
    32 
    33 class NumberThread2 implements Runnable {
    34     @Override
    35     public void run() {
    36         for (int i = 0; i <= 100; i++) {
    37             if (i % 2 != 0) {
    38                 System.out.println(Thread.currentThread().getName() + ":" + i);
    39             }
    40         }
    41     }
    42 }
    43 
    44 class NumberThread3 implements Runnable {
    45     @Override
    46     public void run() {
    47         for (int i = 0; i <= 100; i++) {
    48             System.out.println(Thread.currentThread().getName() + ":" + i);
    49         }
    50     }
    51 }

    4、为什么要使用线程池的方法创建多线程

      传统的方法创建线程,当程序需要使用多线程的时候进行创建,用完之后就会立即销毁,如果频繁的进行这样的操作会消耗大量的系统资源,严重影响程序的性能。

    5、线程池的任务处理策略:

    • 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会分配一个线程去执行这个任务;
    • 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
    • 如果线程池中的线程数量大于 corePoolSize时,此时若某线程空闲时间超过keepAliveTime,该线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。

     

     1 public void execute(Runnable command) {
     2         if (command == null)
     3             throw new NullPointerException();
     4         /*
     5          * Proceed in 3 steps:
     6          *
     7          * 1. If fewer than corePoolSize threads are running, try to
     8          * start a new thread with the given command as its first
     9          * task.  The call to addWorker atomically checks runState and
    10          * workerCount, and so prevents false alarms that would add
    11          * threads when it shouldn't, by returning false.
    12          * 如果正在运行的线程数小于corePoolSize,那么将调用addWorker 方法来创建一个新的线程,并将该任务作为新线程的第一个任务来执行。
    13        当然,在创建线程之前会做原子性质的检查,如果条件不允许,则不创建线程来执行任务,并返回false.  
    14 
    15          * 2. If a task can be successfully queued, then we still need
    16          * to double-check whether we should have added a thread
    17          * (because existing ones died since last checking) or that
    18          * the pool shut down since entry into this method. So we
    19          * recheck state and if necessary roll back the enqueuing if
    20          * stopped, or start a new thread if there are none.
    21          * 如果一个任务成功进入阻塞队列,那么我们需要进行一个双重检查来确保是我们已经添加一个线程(因为存在着一些线程在上次检查后他已经死亡)或者
    22        当我们进入该方法时,该线程池已经关闭。所以,我们将重新检查状态,线程池关闭的情况下则回滚入队列,线程池没有线程的情况则创建一个新的线程。
    23          * 3. If we cannot queue task, then we try to add a new
    24          * thread.  If it fails, we know we are shut down or saturated
    25          * and so reject the task.
    26        如果任务无法入队列(队列满了),那么我们将尝试新开启一个线程(从corepoolsize到扩充到maximum),如果失败了,那么可以确定原因,要么是
    27        线程池关闭了或者饱和了(达到maximum),所以我们执行拒绝策略。
    28 
    29          */
    30     
    31     // 1.当前线程数量小于corePoolSize,则创建并启动线程。
    32         int c = ctl.get();
    33         if (workerCountOf(c) < corePoolSize) {
    34             if (addWorker(command, true))
    35         // 成功,则返回
    36 
    37 return;
    38             c = ctl.get();
    39         }
    40     // 2.步骤1失败,则尝试进入阻塞队列,
    41         if (isRunning(c) && workQueue.offer(command)) {
    42        // 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务
    43             int recheck = ctl.get();
    44             if (! isRunning(recheck) && remove(command))
    45                 reject(command);
    46        // 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null
    47             else if (workerCountOf(recheck) == 0)
    48                 addWorker(null, false);
    49         }
    50     // 3. 步骤1和2失败,则尝试将线程池的数量有corePoolSize扩充至maxPoolSize,如果失败,则拒绝任务
    51         else if (!addWorker(command, false))
    52             reject(command);
    53     }
    线程池中关于任务分配策略的源码分析

     

    6、线程池的关闭

    ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:

    •  shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
    • shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务

    7、使用线程池创建多线程的优点

    • 减少了创建新线程的时间,提高程序的响应速度
    • 重复利用线程池中的线程,不需要每次都创建新的线程降低资源消耗
    • 便于线程的管理:
      • corePoolSize:表示允许线程池中允许同时运行的最大线程数
      • maximumPoolSize:线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize
      • KeepAliveTime:表示线程没有任务时最多保持多久然后停止
      • ...
  • 相关阅读:
    解决“在多字节的目标代码页中,没有此Unicode字符可以映射到的字符”
    实际遭遇并解决:类型“ASP.global_asax”同时存在的问题
    ASP.NET最误导人的错误提示:“未预编译文件,因此不能请求该文件”
    用AutoHotKey彻底解决“Ctrl键+鼠标滚动”时的缩放问题
    .NET Core与.NET Framework、Mono之间的关系
    初识IStructuralEquatable接口
    SQL Server中DateTime与DateTime2的区别
    用word-break: break-all解决不正确换行问题
    Helios与Katana的区别
    简单理解在Mac OS X上运行ASP.NET程序
  • 原文地址:https://www.cnblogs.com/BaoZiY/p/10727180.html
Copyright © 2011-2022 走看看