欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JUC——线程池

程序员文章站 2022-05-05 16:27:49
线程池本质的概念就是一堆线程一起完成一件事情。 Executor ExecutorService ScheduledExecutorService Executors 线程池分类 创建线程池使用类:java.util.concurrent.Executors 创建无大小限制的线程池 public s ......

线程池本质的概念就是一堆线程一起完成一件事情。

JUC——线程池

Executor

package java.util.concurrent;
public interface Executor {
    void execute(Runnable command);
}

ExecutorService

package java.util.concurrent;
public interface ExecutorService extends Executor

ScheduledExecutorService

package java.util.concurrent;
public interface ScheduledExecutorService extends ExecutorService

Executors

package java.util.concurrent;
public class Executors

 

线程池分类

  创建线程池使用类:java.util.concurrent.Executors

  • 创建无大小限制的线程池
    public static ExecutorService  newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                    60L, TimeUnit.SECONDS,
                                    new SynchronousQueue<Runnable>());
    }
  • 创建固定大小的线程池
    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 ScheduledExecutorService  newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }

 

创建四种线程池

当Executors创建完成了线程池之后可以返回“ExecutorService”接口对象,而这个对象里面有两个方法来接收线程的执行:

接收Callable:

public <T> Future<T> submit(Callable<T> task);

接收Runnable:

public Future<?> submit(Runnable task);

  

范例:创建无限量线程池

package so.strong.mall.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool(); //创建一个线程池
        for (int i = 0; i < 5; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行操作");
                }
            });
        }
        service.shutdown(); //线程池执行完毕后需要关闭
    }
}

  无限量大小的线程池会根据内部线程的执行状况来进行线程对象个数的控制。

 

范例:创建有限量的线程池

package so.strong.mall.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newFixedThreadPool(3); //创建一个线程池
        for (int i = 0; i < 5; i++) {
            service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行操作");
                }
            });
        }
        service.shutdown(); //线程池执行完毕后需要关闭
    }
}

 

 范例:创建单线程池

package so.strong.mall.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo { public static void main(String[] args) { ExecutorService service = Executors.newSingleThreadExecutor(); //创建一个线程池 for (int i = 0; i < 5; i++) { service.submit(new Runnable() { //线程池会负责启动 @Override public void run() { System.out.println(Thread.currentThread().getName()+"执行操作"); } }); } service.shutdown(); //线程池执行完毕后需要关闭 } }

 

除了以上的三种线程池之外还可以创建一个定时调度池,这个调度池主要是以时间间隔调度为主。如果要创建调度池则使用ScheduledExecutorService接口完成,该接口之中包含有如下的两个方法:

  • 延迟启动
    public ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit);
  • 间隔调度
    public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimeUnit unit);

范例:创建调度池

package so.strong.mall.concurrent;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecutorDemo {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        for (int i = 0; i < 5; i++) {
            service.schedule(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行操作");
                }
            },2, TimeUnit.SECONDS);
        }
        service.shutdown();
    }
}

 

范例:观察间隔调度

package so.strong.mall.concurrent;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class ExecutorDemo {
    public static void main(String[] args) {
        ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
        for (int i = 0; i < 5; i++) {
            service.scheduleAtFixedRate(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行操作");
                }
            },2,3, TimeUnit.SECONDS);
        }
    }
}

 

 ExecutorService线程池处理方法

通过之前的线程池的演示可以发现,整个线程池的处理里面都是以ExecutorService接口的方法为核心展开的。所以如果要想理解线程池,还需要对整个接口的方法做一个小小的说明。

 1、在Executor接口里面定义了有execute()方法:

public interface Executor {
    void execute(Runnable command);
}

这个方法接收的是Runnable,因为Runnable没有返回值,所以该方法的返回值为void。

范例:使用execute()方法来代替之前的submit():

package so.strong.mall.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"执行操作");
                }
            });
        }
        service.shutdown();
    }
}

 

2、在ExecutorService接口里面的确提供有接收Runnable接口对象的方法,但是这个方法为了统一使用的是submit()。submit()重载了许多次,可以接收Runnable:

public Future<?> submit(Runnale task) 

 范例:使用ExecutorService接口的submit:

package so.strong.mall.concurrent;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            Future<?> future = service.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "执行操作");
                }
            });
            System.out.println(future.get()); //Runnable接口没有返回值,所以永恒为null
        }
        service.shutdown();
    }
}

 

3、 submit()方法是可以接收Callable接口对象的

package so.strong.mall.concurrent;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            Future<?> future = service.submit(new Callable<Object>() {
                @Override
                public Object call() throws Exception {
                    return Thread.currentThread().getName() + "执行操作";
                }
            });
            System.out.println(future.get()); 
        }
        service.shutdown();
    }
}

 

 查看execute()方法的源码:

public void execute(Runnable command) {
    if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}

在这个方法里面主要区分三个概念:

  • task:是具体的线程执行任务,线程在追加线程池的时候没有进行启动;
  • worker:任务的执行需要worker来支持的,可以运行的worker受到“corePoolSize”限制;
  • reject:如果现在线程池已经满了或者关闭了,那么就会出现拒绝新线程加入的可能性。

 

4、Future线程模型设计的优势在于:可以进行线程数据的异步控制,但是在之前编写的过程严格来讲并不好,相当于启动了一个线程就获得了一个返回值,于是为了方便这些线程池中线程对象的管理,可以使用如下方法进行统一返回:

public interface ExecutorService extends Executor {
      public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException;
}

范例:使用invokeAny()方法

package so.strong.mall.concurrent;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        Set<Callable<String>> tasks = new HashSet<>(); //所有任务
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            tasks.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return Thread.currentThread().getName() + "执行任务,i=" + temp;
                }
            });
        }
        ExecutorService service = Executors.newCachedThreadPool(); //创建一个线程池
        String invokeAny = service.invokeAny(tasks); //执行任务
        System.out.println("返回结果:" + invokeAny);
        service.shutdown();

    }
}
返回结果:pool-1-thread-2执行任务,i=4

 使用invokeAny()方法只会返回一个任务的执行操作

 

5、对于多个线程任务的执行结果,可以以list形式返回

public interface ExecutorService extends Executor {
      public  <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;
}

范例:使用invokeAll()方法

package so.strong.mall.concurrent;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        Set<Callable<String>> tasks = new HashSet<>(); //所有任务
        for (int i = 0; i < 10; i++) {
            final int temp = i;
            tasks.add(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return Thread.currentThread().getName() + "执行任务,i=" + temp;
                }
            });
        }
        ExecutorService service = Executors.newCachedThreadPool(); //创建一个线程池
        List<Future<String>> invokeAll = service.invokeAll(tasks); //执行任务
        for (Future<String> future : invokeAll) {
            System.out.println("返回结果:" + future.get());
        }
        service.shutdown();

    }
}

 

CompletionService线程池异步交互

package java.util.concurrent; 
public interface CompletionService<V> {
    Future<V> submit(Callable<V> task);
    Future<V> submit(Runnable task, V result);
    Future<V> take() throws InterruptedException;
    Future<V> poll();
    Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException;
}

线程池异步交互:CompletionService

  • 将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者submit()执行的任务,使用者take()已完成的任务,并按照完成任务的顺序处理它们的结果。
  • CompletionService依赖于一个单独的Executor来实际执行任务,在这种情况下,CompletionService只管理一个内部完成队列,在CompletionService接口里面提供有如下两个方法:
    •  设置Callable: 
      public Future<V> submit(Callable<V> task);
    •     设置Runnable:
      public Future<V> submit(Runnable task, V result); 

CompletionService是一个接口,如果要想使用这个接口可以采用ExecutorCompletionService这个子类

public class ExecutorCompletionService<V> implements CompletionService<V>

ExecutorCompletionService的构造方法:

public ExecutorCompletionService(Executor executor) {
    if (executor == null)
        throw new NullPointerException();
    this.executor = executor;
    this.aes = (executor instanceof AbstractExecutorService) ?
        (AbstractExecutorService) executor : null;
    this.completionQueue = new LinkedBlockingQueue<Future<V>>();
}

 

CompletionService来控制所有线程池的操作以及数据返回,则应该使用这个类来进行线程池的提交处理。

  • 提交线程
    Future<V> submit(Callable<V> task);
  • 获取返回内容
    Future<V> take() throws InterruptedException;  

范例:使用CompletionService工具类

package so.strong.mall.concurrent;
import java.util.concurrent.*;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        ExecutorService service = Executors.newCachedThreadPool();
        CompletionService<String> completions = new ExecutorCompletionService<>(service);
        for (int i = 0; i < 5; i++) {
            final int temp = i;
            completions.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    return Thread.currentThread().getName() + "- i =" + temp;
                }
            });
        }
        for (int i = 0; i < 5; i++) {
            System.out.println(completions.take().get());
        }
        service.shutdown();
    }
}

CompletionService操作接口的主要目的是可以去隐藏ExecutorService接口执行线程池的处理,不在需要关心novkeAny(), invokeAll()的执行方法了。

 

ThreadPoolExecutor线程池执行者

线程池的创建主要依靠的是一个类完成的:ThreadPoolExecutor,下面以无限量线程池为例做一个说明:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                60L, TimeUnit.SECONDS,
                                new SynchronousQueue<Runnable>());
}     

观察ThreadPoolExecutor类的构造方法:

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize, //最大的线程大小
                          long keepAliveTime, //存活的时间
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue, //工作队列
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    if (corePoolSize < 0 ||
        maximumPoolSize <= 0 ||
        maximumPoolSize < corePoolSize ||
        keepAliveTime < 0)
        throw new IllegalArgumentException();
    if (workQueue == null || threadFactory == null || handler == null)
        throw new NullPointerException();
    this.corePoolSize = corePoolSize;
    this.maximumPoolSize = maximumPoolSize;
    this.workQueue = workQueue;
    this.keepAliveTime = unit.toNanos(keepAliveTime);
    this.threadFactory = threadFactory;
    this.handler = handler;
}

 

范例:创建属于自己的线程池

package so.strong.mall.concurrent;
import java.util.concurrent.*;

public class ExecutorDemo {
    public static void main(String[] args) throws Exception {
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
        ThreadPoolExecutor pool = new ThreadPoolExecutor(2, 5, 6L, TimeUnit.SECONDS, queue);
        for (int i = 0; i < 2; i++) {
            final int temp = i;
            pool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("[" + Thread.currentThread().getName() + "]任务开始执行-" + temp);
                    try {
                        TimeUnit.SECONDS.sleep(2);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("[" + Thread.currentThread().getName() + "]任务结束执行-" + temp);
                }
            });
        }
        pool.shutdown();
    }
}

在线程池中存在拒绝策略的概念,所谓的拒绝策略指的是线程池满了之后的其他等待线程的处理状态。在ThreadPoolExecutor类里面提供有一些“RejectedExecutorHandler”子类。如果现在被拒绝会出现“拒绝异常”(默认AbortPolicy)、对于给出的几种拒绝策略如下:

  • AbortPolicy:默认实现,当任务添加到线程池中被拒绝时,会抛出拒绝异常"RejectedExecutionException";
  • DiscardPolicy:当将任务添加到线程池中被拒绝的时候,线程池将直接丢弃该拒绝的任务;
  • DiscardOldestPolicy:当被拒绝时,线程池会放弃等待队列中时间最长的未被处理的任务,然后将被拒绝的任务添加到队列之中;
  • CallerRunsPolicy:当任务被拒绝的时候,会在线程池当前正在运行的Thread线程之中处理该任务(加塞)。

范例:修改一下侧率

package so.strong.mall.concurrent;
import java.util.concurrent.*;

public class ExecutorDemo {
   public static void main(String[] args) throws Exception {
      BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(2);
      ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 6L, TimeUnit.SECONDS, queue, Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
      for (int i = 0; i < 5; i++) {
         final int temp = i;
         pool.execute(new Runnable() {
            @Override
            public void run() {
               System.out.println("[" + Thread.currentThread().getName() + "]任务开始执行-" + temp);
               try {
                  TimeUnit.SECONDS.sleep(2);
               } catch (Exception e) {
                  e.printStackTrace();
               }
               System.out.println("[" + Thread.currentThread().getName() + "]任务结束执行-" + temp);
            }
         });
      }
      pool.shutdown();
   }
}
Exception in thread "main" [pool-1-thread-1]任务开始执行-0
[pool-1-thread-2]任务开始执行-3
java.util.concurrent.RejectedExecutionException: Task so.strong.mall.concurrent.ExecutorDemo$1@6da13f3d rejected from java.util.concurrent.ThreadPoolExecutor@45d45d18[Running, pool size = 2, active threads = 2, queued tasks = 2, completed tasks = 0]
	at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2048)
	at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:821)
	at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1372)
	at so.strong.mall.concurrent.ExecutorDemo.main(ExecutorDemo.java:15)

  

由于只设置了一个CorePoolSize,所以当多余的任务出现之后将采用设置的默认的拒绝策略:“new ThreadPoolExecutor.AbortPolicy()”会出现“RejectedExecutionException"异常,如果出现拒绝之后丢弃该拒绝的任务,那么也可以将拒绝策略修改为:

ThreadPoolExecutor pool = new ThreadPoolExecutor(1, 2, 6L, TimeUnit.SECONDS, queue, Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy());

对于拒绝策略最简单的解释就是:线程池中的corePoolSize满了,再追加的任务的处理方案。

 

PS:阿里巴巴Java开发手册——并发处理第4条

【强制】线程池不允许使用Executors创建,而是通过ThreadPoolExecutor的方式创建,这样的处理方式能让编程代码的工程师更加明确线程池的运行规则,规避资源耗尽的风险。

 说明:Executors返回的线程池对象的弊端如下:

  1. FixedThreadPool和SingleTheadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM;
  2. CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。