java并发学习--线程池、并发容器和应用

线程池

为什么要使用线程池

①:降低了资源的消耗,在不使用到线程池的地方,工作的时候线程会创建销毁,但是在线程池就减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

②:提高响应时间,因为线程池避免砸执行任务时的线程的创建和销毁,那么占的cpu时间会减少,那么响应时间就有所提升。

③:提高了线程的课管理性,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约 1MB 内存,线程开的越多,消耗的内存也就 越大,最后死机)

线程池的继承模型

 

 

线程池的顶级接口是Executor,顶级接口派生出两个接口,ExecutorService和ScheduleExecutorService(专注于定时任务),这两个接口才可以认为是线程池实现的接口,线程池的最终实现类是ThreadPoolExecutor, 它包含以下几种线程池:FixedThreadPool、SingleThreadExecutor、CacheThreadPool、ScheduleThreadPoolExecutor。但是线程池的创建一般使用Executors的工厂类方式创建。

查看ThreadPoolExecutor的源码,他的构造器如下:

public ThreadPoolExecutor(int corePoolSize,
             int maximumPoolSize,
             long keepAliveTime,
             TimeUnit unit,
             BlockingQueue<Runnable> workQueue,
             ThreadFactory threadFactory,
             RejectedExecutionHandler handler) {......}

corePoolSize:表示线程的核心数量。

maximumPoolSize:线程的最大线程数量。

keepAliveTime:空闲线程存活的时间,是指最大线程数量里的没有工作的线程的存活时间,核心线程空闲时不会被释放的。

unit:时间的单位。

workQueue:阻塞队列,在核心线程数量到达上限以后,往阻塞队列中存放任务,等待线程,一般分为有界队列还是无界队列。

threadFactory:线程工厂,缺省的线程命名规则:pool+数字 +thread+数字。

RejectedExecutionHandler:表示当线程池,阻塞队列满了并且已经没有空闲的工作线程的时候,这种称为饱和策略。

①:AbortPolicy:直接抛出异常,默认

②:CallerRunsPolicy:使用调用当前任务的线程去执行任务。

③:DiscardOldestPolicy:丢弃当前线程中最老的任务。

④:DiscardPolicy:直接丢弃

查看ThreadPoolExecutor的构造函数我们知道,我们可以创建我们自己适合的线程池,也可以使用Executors创建。

 

关于实现类ThreadPoolExecutor的几种线程的区别:

FixedThreadPool建时,核心线程数和最大线程数是一样的,是一个固定线程的线程池。常用于,需要限定线程使用的场景,负载比较大的服务器。他的阻塞队列是:LinkedBlockingQueue队列,这个队列是一个无界队列,容量没有限制,那么使用无界队列,当任务量多的时候,会导致阻塞队列量过多导致内存溢出,那么饱和策略也没有什么用。

SingleThreadExecutor创建时,核心线程数=最大线程数=1。说明这个线程池无论怎么样,他的线程都只有一个。阻塞队列也是无界队列。

CachedThreadPool缓冲线程池:创建时:他的核心线程数为0,最大线程数是 Integer.MAX_VALUE。说明这个线程池,使用到这个线程时,只要用任务,就会无上限的创建线程。而且他的阻塞队列是使用的是:SynchronousQueue,这个队列本身不会存储任何东西。缺点,大量提交任务时,创建太多线程消耗计算机资源。。
优点:每个任务的执行时间非常短,使用这个线程池就非常高效。

ScheduleThreadPoolExecutor定期执行任务的线程池。有两种 ScheduleTreadPoolExecutor.只包含多个线程的。

 

线程池的使用:

运行结果:

 

 1 public class UserThreadPool {
 2     public static class WorkThread implements Runnable {
 3         private String taskName;
 4         private Random r = new Random();
 5         public WorkThread(String taskName) {
 6             this.taskName = taskName;
 7         }
 8         public String getTaskName() {
 9             return taskName;
10         }
11         @Override
12         public void run() {
13             System.out.println(Thread.currentThread().getName()
14                     + "  process the task : " + taskName);
15             try {
16                 Thread.sleep(2);
17             } catch (InterruptedException e) {
18                 e.printStackTrace();
19             }
20         }
21     }
22 
23     public static void main(String[] args) {
24         //创建自定义线程池,
25         ExecutorService  threadPool=new ThreadPoolExecutor(2,4,3,
26                 TimeUnit.SECONDS,new ArrayBlockingQueue<Runnable>(4),new ThreadPoolExecutor.DiscardPolicy());
27        /* ExecutorService threadPool = Executors.newCachedThreadPool();//使用Executors线程池创建。*/
28         for (int i = 0; i <= 6; i++) {
29             WorkThread workThread = new WorkThread("work " + i);
30             System.out.println("A new Task has been added: " + workThread.getTaskName());
31             threadPool.execute(workThread);
32         }
33         threadPool.shutdown();
34     }
35 }
线程池的使用

 

ScheduledThreadPoolExecutor

 

ScheduledThreadPoolExecutor。包含若干个线程的ScheduledThreadPoolExecutor。

 

SingleThreadScheduledExecutor。只包含一个线程的ScheduledThreadPoolExecutor。

 

有关提交定时任务的四个方法:

 

//提交一个仅仅执行一次的任务

 

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)

 

//提交一个仅仅执行一次的任务,有返回值

 

public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);

 

// 固定时间间隔循环的执行

 

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay,     long period, TimeUnit unit)

 

// 固定延时间隔的循环的执行

 

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay,  long delay, TimeUnit unit);

 

 1 **
 2  * @author yangxin
 3  * @time 2019/3/17  10:15
 4  *
 5  * 类说明:执行我们的定时任务
 6  */
 7 public class ScheduleCase {
 8     public static void main(String[] args){
 9         ScheduledThreadPoolExecutor schedule=new ScheduledThreadPoolExecutor(1);
10         schedule.schedule(new Runnable() {
11             @Override
12             public void run() {
13                 System.out.println("Task run one");
14             }
15         },3000, TimeUnit.MICROSECONDS);
16 
17         schedule.scheduleAtFixedRate(new Runnable() {
18             @Override
19             public void run() {
20                 System.out.println("FixedRate start:"+ScheduleWorker.format.format(new Date()));
21                 try {
22                     Thread.sleep(2);
23                 } catch (InterruptedException e) {
24                     e.printStackTrace();
25                 }
26                 System.out.println("FixedRate end "+ScheduleWorker.format.format(new Date()));
27             }
28         },1000,3000,TimeUnit.MICROSECONDS);
29 
30         schedule.scheduleAtFixedRate(new ScheduleWorker(ScheduleWorker.Normal)
31                 ,1000,3000,TimeUnit.MICROSECONDS);
32     }
33 
34 }
ScheduleThreadPoolExecutor

 

 

 

 1 /**
 2  * @author yangxin
 3  * @time 2019/3/17  10:06
 4  *
 5  * 类说明:定时任务的工作
 6  */
 7 public class ScheduleWorker implements Runnable {
 8     public static final int Normal=0;
 9     public static final int HasException=-1;
10     public static final int ProcessException=1;
11 
12     public static SimpleDateFormat format=new SimpleDateFormat("YYYY-MM-DD HH:mm:ss");
13 
14     private int taskType;
15 
16     public ScheduleWorker(int taskType) {
17         this.taskType = taskType;
18     }
19 
20     @Override
21     public void run() {
22         if(taskType==HasException){
23             System.out.println(format.format(new Date())+"Exception be made");
24             throw new RuntimeException("ExceptionHappen");
25         }else if(taskType==ProcessException){
26             try{
27                 System.out.println(format.format((new Date()))+"Exception be made"+"will be catch");
28                 throw new RuntimeException("ExceptionHappen");
29             }catch(Exception e){
30                 System.out.println("we catch Exception!");
31             }
32         }else{
33             System.out.println(format.format(new Date())+"Normal");
34         }
35     }
36 }
Test

 

 

 

线程池之间的工作机制:线程池核心线程,最大线程,阻塞队列,饱和策略之间的关系

  线程池的运行顺序:一开始任务进入线程池,首先用核心线程去执行任务,当无核心线程的时候,进来的任务会进入阻塞队列中,等待核心线程的调度,但是当阻塞队列都满了,那么最大线程数开始创建新的线程执行任务;如果线程创建到最大值,这个时候线程池称为饱和状态,于是就开始饱和策略的执行。

将上面的代码改动一下:

我们将核心线程设置为2,最大线程设置为4,阻塞队列是有界队列并且大小为3;模拟7个线程运行,,我们可以看到,线程最多就创建了4个线程来执行任务,因为我们的阻塞队列大小是3.当核心线程满了时,阻塞队列无法装下,就需要新建线程来执行任务,所有就只创建了4个线程。

 

 我们当把队列设置为10后,发现线程就创建了两个,因为核心线程创建的时候,阻塞队列能够装下所有的任务,所以就不会创建新的任务。

 

 

 

并发容器

ConcurrentHashMap他的原型就是HashMap在线程安全的一个版本;我们都知道在Map中有HashMap和HashTable两种Map,一个是线程安全,一个是线程不安全的;但是为什么还会有一个ConcurrentHashMap的这样一个类呢???因为HashTable尽管是线程安全的,但是在并发性很高的情况下,HashTable的效率低下,不能满足开发的需要,HashMap的效率就比较高,但是不是线程安全的,所以就会衍生出ConcurrentHashMap这个类;使用方式和HashMap差别不大,不过有一个新的函数,putifAbsent()这个函数,功能是如果插入失已经存在就返回其值,没有就和put功能一样。

 

在1.7以前HashMap的结构就是一个Hash表,有一个Segment、Table、HashEntry的一个结构,Segment主要是来加锁使用的;在1.8以后,HashMap的结构是hash表+红黑树结构的,当一个桶上的链表个数超过8个的时候,这时候Map的结构就变成红黑树,1.8以后的HashMap加锁是锁的单元数据。

ConcurrentSkipListMap(TreeMap)、ConcurrentSkipListSet(TreeSet)Skip:简称是跳表,如下图,跳表的结构就是建立很多个索引,使得查找的效率提升,能够和红黑树媲美,如下,当我们要查找50的时候,第一个索引最大的值是20,50要大一下,那么就跳到下一条索引中,依次比较,最后找到50。这就是跳表。

 

ConcurrentLinkedQueue(LinkedList)linkedList一个并发版本;

 

add()将元素插入队列的头部

 

offer() 将元素插入队列的尾部

 

peek()  检索队列的头部拿到元素,不移除元素

 

poll()  检索队列的头部拿到元素,移除元素

CopyOnWriteArrayList(ArrayList)这是一个写时拷贝,在读的时候不会加锁,在更新的时候重新将整个数据复制。并发的读,读写分离。最适用于读多写少的场景。

阻塞队列:当插入或者移除元素的时候,当条件不足时,让工作线程阻塞的队列。

ArrayBlockingQueue:数组结构,是一个有界的阻塞队列,锁只有一把,生产和消费者是同一把锁,定义时需要指定大小。

LinkedBlockingQueue:是一个链表结构,是一个有界阻塞队列,锁是分离的,在元素推入队列的时候会包装成Node放入,定义时不需要确定大小。
PriorityBlockingQueue:支持优先级排序的无界队列。

DelayQueue:延时获取元素,但是使用必须实现Delayed接口。

SynchronousQueue:不存储任何元素的无界队列,有插入元素的操作就必须有取出元素的操作。

LinkedTransferQueue:链表结构的无界队列。

LinkedBlockingDeque:链表结构,双向队列。

 

线程池的实际应用

需求:A系统有个批量任务执行的功能,A系统的使用者在使用的过程中整个任务的执行情况,包括有多少任务成功了,多少任务做失败了失败的原因是什么?并且当前任务的处理进度的需求,并且其他的业务也有类似的需求,甚至有的要求同时处理多个任务不同的批量任务,给这一个场景提供一个业务框架,给这些业务场景使用,并且对于使用者(开发人员的友好)更好的友好。

分析: ①:批量的任务框架,并且可以看到任务的执行情况。

    ②:要对开发使用人员要友好。

需要做些什么

  ①:提高性能,采用多线程,并且屏蔽细节(使用者不需要知道底层是怎么样的,比如什么并发容器、阻塞队列、异步任务啥的)。

  ②:每个批量任务拥有自己的上下文环境。

  ③:定期的清除已经完成的任务

项目开始

1.首先我们需要检测线程任务的执行情况,成功?失败?还是其他,失败的原因是什么,那么我们先定义一个枚举类:

2.封装一个数据类型,表示每个任务返回的结果

3.这是一个给使用者提供的接口,使用者使用这个框架要实现这个接口。

4.工作任务的处理

 

 

项目地址:https://github.com/fireshoot/LearningAndTest

 

 

 

 

 

 

 

转载标明地址

 

posted @ 2019-03-17 15:29  轻抚丶两袖风尘  阅读(226)  评论(0编辑  收藏  举报