Java多线程与高并发(三)

为什么要使用线程池?

原因:线程不断创建和销毁过程很占用系统资源,如果管理不善很容易导致系统一系列问题发生,因此大多并发框架中都会使用线程池。

好处:

  1. 使用线程池可以重复使用已有得线程继续执行任务,避免线程创建和销毁时造成的消耗。
  2. 由于没有线程创建和销毁的消耗,可以极大提高系统响应速度。
  3. 通过线程池可以很好的对线程合理的管理,根据系统的承受能力调整可运行线程数量大小等,因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并且会造成cpu过度切换(cpu切换线程是有时间成本的(需要保持当前执行线程的现场,并恢复要执行线程的现场))
  4. 线程池提供更强大的功能,延时定时线程池。 

线程和队列的关系

线程池执行所提交的任务过程

先判断线程池中的核心线程池所有的线程是否都在执行任务。如果不是,则新建一个线程执行刚提交的任务,否则,在判断当前阻塞队列是否已满,如果未满,则将提交的任务放置在阻塞队列中,否则,在判断线程池所有的线程是否都在执行任务如果没有,则创建一个新的线程来执行任务,否则,则交由饱和策略进行处理

 

线程池的分类

1.ThreadPoolExecutor

  • newCachedThreadPool(无限线程池,具有自动线程回收)

创建一个可根据需要创建的线程池。但是在以前构造的线程可用时将重用它,需要喜欢创建新的线程ThreadFactory创建。

特点:

  1. 线程池中的数量没有固定,可达最大Integer.Max_VALUE.
  2. 线程池中的线程可重复利用和回收【回收默认时间为一分钟】
  3. 当线程池没有线程可用时,会创建一个新的线程来执行提交任务。

代码示例:

线程类.Java

package com.juc.pool;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 18:57
 * @Description 线程
 */
public class Task implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "----running");
    }
}
package com.juc.pool;
import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.*;
/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 17:04
 * @Description 无限线程池,具有自动线程回收
 * newCachedThreadPool()
 * 创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程。
 */
public class CacheThreadPoolDemo {
    public static void main(String[] args) {
        //
        ExecutorService executorService = newCachedThreadPool();
        for (int i = 1; i <=20; i++) {
            executorService.execute(new Task());
        }

        //关闭线程池
        executorService.shutdown();
    }
}
  • newFixedThreadPool(固定大小的线程池
package com.juc.pool;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 19:16
 * @Description创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。 在任何时候,最多nThreads线程将处于主动处理任务。
 * 如果所有线程处于活动状态时都会提交其他任务,则它们将等待队列中直到线程可用。
 * 如果任何线程由于在关闭之前的执行期间发生故障而终止,则如果需要执行后续任务,则新线程将占用它。
 * 池中的线程将存在,直到它明确地为shutdown 。
 */
public class FixedThreadPoolDemo {
    public static void main(String[] args) {
        //nThreads 指定线程大小
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 20; i++) {
            //执行任务
            executorService.execute(new Task());
        }
        //关闭线程池
        executorService.shutdown();
    }
}

创建一个可重复固定的线程池,以共享的无界队列来运用这些线程,在任意点,在大多数。nThreads 线程会处于处理任务的活动状态,如果在线程处于活动状态提交附加任务,则在可用线程之前,附加任务在队列中等待,如果在关闭执行任务期间由于失败而导致任何线程终止,那么新的一个线程将代替它执行后续任务,在某个线程被显式地关闭之前,线程池的的线程一直存在。

特点:

      1.线程池中的线程处于一定的量,可以很好的控制线程的并发量。

      2.线程可以重复被使用,在显式关闭之前,都将一直存在。

      3.超出一定量的线程被提交的时候需要队列中等待。

  • newSingleThreadPool(单个后台线程)

package com.juc.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/30 19:24
*@Description创建一个使用从无界队列运行的单个工作线程的执行程序。
 *  (请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,
 *  则如果需要执行后续任务,则新的线程将占用它。)
 * 任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。
 * 与其他等效的newFixedThreadPool(1) newFixedThreadPool(1) ,
 * 返回的执行器保证不被重新配置以使用额外的线程。
*/
public class SingleThreadPoolDemo {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
    for (int i = 0; i <20 ; i++) {
            //执行任务
        executorService.execute(new Task());
        }
        //关闭线程池
        executorService.shutdown();
    }
}

创建一个使用单个的Worker线程的Executor,以无界队列方式来运行该线程。(注意:如果因为再关闭之前的执行期间出现失败而终止此单个线程,那么如果需要,一个新线程将代替它执行后续任务),可保证顺序低执行各个任务,并且在任意给定的时间不会有多个线程是活动的,与其他等效的newFixedThreadPool(1)不同,可保证无需要重新配置此方所返回的执行线程程序可使用其他的线程。’

特点:

        1.线程池最多执行一个线程,之后所提交的线程活动将会排在队列中此执行。

2.ScheduledThreadPoolExecutor

  • newSingleThreadScheduledExecutor
package com.juc.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/30 19:36
*@Description创建一个单线程执行器,可以调度命令在给定的延迟之后运行,或定期执行。
 * (请注意,如果这个单个线程由于在关闭之前的执行过程中发生故障而终止,则如果需要执行后续任务,则新的线程将占用它。)
 * 任务保证顺序执行,并且不超过一个任务将被激活在任何给定的时间。
 * 与其他等效的newScheduledThreadPool(1) newScheduledThreadPool(1) ,
 * 返回的执行器保证不被重新配置以使用额外的线程。
 *
 */
public class SingleThreadScheduledExecutorDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
        System.out.println("执行前当前时间:" + System.currentTimeMillis());
        //延迟三秒执行
        scheduledExecutorService.schedule(()->  System.out.println("延迟三秒执行"), 3, TimeUnit.SECONDS);
        scheduledExecutorService.shutdown();
        System.out.println("执行后当前时间:" + System.currentTimeMillis());
    }
}

创建一个线程池,它可安排在给定延迟运行命令或者定期地执行。

特点:

         1.线程池中具有指定数量的线程,即使时空线程也将保留。

          2.可定时或者延迟执行线程活动。

  • newScheduledThreadPool
package com.juc.pool;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import static java.util.concurrent.Executors.*;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 19:36
 * @Description创建一个线程池,可以调度命令在给定的延迟之后运行,或定期执行。 
 * scheduleAtFixedRate  延时和定时使用这个方法
 * schedule 延迟使用这个方法
 * 注意scheduleAtFixedRate使用不要关掉shutdown线程池,不然就不会延时和定时执行
 * 多任务执行的时候,随机抢占资源
 */
public class ScheduledThreadPoolDemo {
    public static void main(String[] args) {
        ScheduledExecutorService scheduledExecutorService = newScheduledThreadPool(3);
        System.out.println("执行前当前时间:" + System.currentTimeMillis());
        //延迟三秒执行
        scheduledExecutorService.scheduleAtFixedRate(() -> {
            System.out.println("延迟一秒执行,每三秒执行");
            System.out.println("执行后当前时间:" + System.currentTimeMillis());
        }, 1, 3, TimeUnit.SECONDS);
//        scheduledExecutorService.shutdown();

    }
}

创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期地执行。

特点:

         1.线程池中最多执行一个线程,之后提交的线程活动将会排在队列中执行。

          2.可定时或者延迟线程活动。

3.ForkJoinPool

  • newWorkStealingPool

任务代码

package com.juc.pool.fork;

import java.util.concurrent.RecursiveAction;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 20:57
 * @Description 拆分任务
 */
public class PrintTask extends RecursiveAction {
    /**
     *最多打印50次
     */
    private static final int THRESHOLD = 50;
    private int start;
    private int end;

    public PrintTask(int start, int end) {
        super();
        this.start = start;
        this.end = end;
    }

    @Override
    protected void compute() {
        if (end - start < THRESHOLD) {
            for (int i = start; i < end; i++) {
                System.out.println(Thread.currentThread().getName() + "的i值:" + i);
            }
        } else {
            int middle = (start + end) / 2;
            PrintTask left = new PrintTask(start, middle);
            PrintTask right = new PrintTask(middle, end);
            //并行执行两个小任务
            left.fork();
            right.fork();
        }
    }
}

调用Task

 

package com.juc.pool.fork;

import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 20:50
 * @Description创建一个维护足够的线程以支持给定的并行级别的线程池,并且可以使用多个队列来减少争用。 并行级别对应于主动参与或可以从事任务处理的最大线程数。 线程的实际数量可以动态增长和收缩。
 * 工作窃取池不保证执行提交的任务的顺序。
 * 打印0~300 的数 切分50次
 */
public class WorkStealingPoolDemo {
    public static void main(String[] args) {
        PrintTask task = new PrintTask(0, 300);
        //创建示例,并执行分割任务
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        forkJoinPool.submit(task);
        //线程阻塞,等待任务执行完成
        forkJoinPool.awaitQuiescence(2, TimeUnit.SECONDS);
        //关闭线程
        forkJoinPool.shutdown();
    }
}

核心线程池和最大线程池大小

ThreadPoolExecutor将自动调整池大小 getCorePoolSize()maximumPoolSize。 当方法execute(Runnable)中提交了新任务,并且运行的corePoolSize线程少于一个,即使其他工作线程处于空闲状态,也会创建一个新的线程来处理该请求。 如果超过corePoolSize但小于maximumPoolSize线程运行,则仅当队列已满时才会创建一个新线程。 通过将corePoolSizemaximumPoolSize设置为相同,您将创建一个固定大小的线程池。 通过将maximumPoolSize设置为本质上无限制的值(如Integer.MAX_VALUE ,您可以允许池容纳任意数量的并发任务。 最典型的是,核心线程池和最大线程池大小只能在构建时进行设置,但也可以使用setCorePoolSize(int)setMaximumPoolSize(int)进行动态更改 。 

默认情况下,即使核心线程最初创建并且只有在新任务到达时才启动,但是可以使用方法prestartCoreThread()或prestartAllCoreThreads()动态地覆盖 。 如果您使用非空队列构建池,则可能需要预先提供线程。

创建新线程

新线程使用ThreadFactory创建。 如果没有另外指定,则使用Executors.defaultThreadFactory() ,它创建所有线程与所有相同的ThreadGroup并且具有相同的优先级和非守护进程状态NORM_PRIORITY 。 通过提供不同的ThreadFactory,您可以更改线程的名称,线程组,优先级,守护进程状态等。如果ThreadFactory在从newThread返回null请求时无法创建线程,则执行程序将继续,但可能无法执行任务 线程应该拥有“modifyThreadRuntimePermission 。 如果使用池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且关闭池可能仍处于可能终止但未完成的状态。

如果池当前具有多于corePoolSize线程,则如果空闲超过keepAliveTime,则多余的线程将被终止。 这提供了当线程池未被主动使用时减少资源消耗的方法。 如果稍后线程池变得更加活跃,将构建新的线程。 此参数也可以使用方法setKeepAliveTime(long, TimeUnit)动态更改 。 使用值Long.MAX_VALUE TimeUnit.NANOSECONDS有效地禁用空闲线程在关闭之前终止。 默认情况下,仅当存在多corePoolSize线程时,保持活动策略才适用。 但是方法allowCoreThreadTimeOut(boolean)也可以用于将这个超时策略应用于核心线程,只要值不为零。keepAliveTime

排队

任何BlockingQueue可用于传送和保留提交的任务。 这个队列的使用与池大小相互作用:

  • 如果少于corePoolSize线程正在运行,Executor总是喜欢添加一个新线程,而不是排队。
  • 如果corePoolSize或更多的线程正在运行,Executor总是喜欢排队请求而不是添加一个新的线程。
  • 如果请求无法排队,则会创建一个新线程,除非这将超出maximumPoolSize,否则任务将被拒绝。

排队的三种策略:
        1.直接切换 一个工作队列的一个很好的默认选择是一个SynchronousQueue ,将任务交给线程,无需另外控制。 在这里,如果没有线程可以立即运行,那么尝试排队任务会失败,因此将构建一个新的线程。 处理可能具有内部依赖关系的请求集时,此策略可避免锁定。 直接切换通常需要无限制的maximumPoolSizes,以避免拒绝新提交的任务。 这反过来允许无限线程增长的可能性,当命令继续以平均速度比他们可以处理的速度更快地到达时。
        2.无界队列 使用无界队列(LinkedBlockingQueue没有预定容量)会导致新的任务,在队列中等待,当所有corePoolSize线程都很忙。 因此,不会再创建corePoolSize线程。 (因此,最大值大小的值没有任何影响。)每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响其他执行; 例如,在网页服务器中。 虽然这种排队风格可以有助于平滑瞬态突发的请求,但是当命令继续达到的平均速度比可以处理的速度更快时,它承认无界工作队列增长的可能性。
       3.有边界的队列。 有限队列 ArrayBlockingQueue )有助于在使用有限maxPoolSizes时防止资源耗尽,但可能更难调整和控制。 队列大小和最大池大小可能彼此交易:使用大队列和小型池可以最大限度地减少CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务频繁阻塞(如果它们是I / O绑定),则系统可能能够安排比您允许的更多线程的时间。 使用小型队列通常需要较大的线程池大小,这样可以使CPU繁忙,但可能会遇到不可接受的调度开销,这也降低了吞吐量。

拒绝策略

方法execute(Runnable)中提交的新任务将在执行程序关闭时被拒绝 ,并且当执行程序对最大线程和工作队列容量使用有限边界并且饱和时。 在任何一种情况下, execute方法调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)其的方法RejectedExecutionHandler 。 提供了四个预定义的处理程序策略: 
    1、在默认ThreadPoolExecutor.AbortPolicy ,处理程序会引发运行RejectedExecutionException后排斥反应。 
    2、ThreadPoolExecutor.CallerRunsPolicy中,调用execute本身的线程运行任务。 这提供了一个简单的反馈控制机制,将降低新务   提交的速度。 
    3、ThreadPoolExecutor.DiscardPolicy中 ,简单地删除无法执行的任务。 
    4、ThreadPoolExecutor.DiscardOldestPolicy中 ,如果执行程序没有关闭,则工作队列头部的任务被删除,然后重试执行(可能会再次失败,导致重复)。 
   5、可以定义和使用其他类型的RejectedExecutionHandler类。 这样做需要特别注意,特别是当策略被设计为仅在特定容量或排队策略下工作时。 

钩子方法 
该类提供了在每个任务执行之前和之后调用的protected覆盖的(TbeforeExecutehread, Runnable)afterExecute(Runnable, Throwable)方法。 这些可以用来操纵执行环境; 例如,重新初始化ThreadLocals,收集统计信息或添加日志条目。 另外,方法terminated()可以被覆盖,以执行执行程序完全终止后需要执行的任何特殊处理。 
如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然终止。 

队列维护 
方法getQueue()允许访问工作队列以进行监视和调试。 强烈不鼓励将此方法用于任何其他目的。 当提供大量排队任务被取消时,两种提供的方法remove(Runnable)purge()可用于协助进行存储回收。 
即不再在程序中引用, 并没有剩余的线程将成为线程池shutdown自动。 如果希望确保未引用的线程池被回收,即使忘记调用shutdown() 则必须安排未使用的线程最终死机,通过设置适当的保持活动时间,使用零个核心线程的下限和/或设置allowCoreThreadTimeOut(boolean) 。 

线程池的生命周期

RUNNING :能接受新提交的任务,并且也能处理阻塞队列中的任务;
SHUTDOWN:关闭状态,不再接受新提交的任务,但却可以继续处理阻塞队列中已保存的任务。
STOP:不能接受新任务,也不处理队列中的任务,会中断正在处理任务的线程。
TIDYING:如果所有的任务都已终止了,workerCount (有效线程数) 为0,线程池进入该状态后会调用 terminated() 方法进入TERMINATED 状态。
TERMINATED:在terminated() 方法执行完后进入该状态,默认terminated()方法中什么也没有做。

线程池的创建

  •  corePoolSize:核心线程池的大小
  •  maximumPoolSize:线程池能创建线程的最大个数
  •  keepAliveTime:空闲线程存活时间
  •  unit:时间单位,为keepAliveTime指定时间单位
  •  workQueue:阻塞队列,用于保存任务的阻塞队列
  •  threadFactory:创建线程的工程类
  •  handler:饱和策略(拒绝策略)

阻塞队列

  •  ArrayBlockingQueue

一个有限的BlockingQueue由数组支持。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。

返回值 方法名 描述
Boolean add(E e) 在插入此队列的尾部,如果有可能立即这样做不超过该队列的容量,返回指定的元素 true成功时与抛出 IllegalStateException如果此队列已满。
void clear() 从这个队列中原子地删除所有的元素。
Boolean remove(Object o)  从该队列中删除指定元素的单个实例(如果存在)。 
int size() 返回此队列中的元素数
E task() 检索并删除此队列的头,如有必要,等待元素可用。
Object[] toArray() 以适当的顺序返回一个包含此队列中所有元素的数组
<T> T[] toArray(T[] a)  以适当的顺序返回包含此队列中所有元素的数组; 返回的数组的运行时类型是指定数组的运行时类型
boolean offer(E e)  如果可以在不超过队列容量的情况下立即将其指定的元素插入该队列的尾部,则在成功时 false如果该队列已满,则返回 true 。
boolean offer(E e, long timeout, TimeUnit unit)  在该队列的尾部插入指定的元素,等待指定的等待时间,以使空间在队列已满时变为可用。 
E poll()

检索并删除此队列的头,如果此队列为空,则返回 null 。 

E poll(long timeout, TimeUnit unit)  检索并删除此队列的头,等待指定的等待时间(如有必要)使元素变为可用。 
void put(E e)  在该队列的尾部插入指定的元素,如果队列已满,则等待空间变为可用。
int remainingCapacity()  返回此队列可以理想地(在没有内存或资源限制)的情况下接受而不阻止的附加元素数。 
Spliterator<E> spliterator()  返回此队列中的元素Spliterator 。 
String toString()

返回此集合的字符串表示形式。   

package com.juc.pool.queue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/30 23:50
 * @Description 商品
 */
public class Goods {
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/30 23:50
     * @Description:品牌
     */
    private String brand;
    /**
     * @Author zcm
     * @Email [email protected]
     * @date 2020/5/30 23:50
     * @Description:名称
     */
    private String name;

    public Goods(String brand, String name) {
        this.brand = brand;
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

 生产者

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/31 9:44
*@Description:生产者不断生产商品往队列里添加,队列放满了就不再生产,必须等消费者消费了,在生产商品
*/
public class ProducerQueue implements Runnable {
    private BlockingQueue<Goods> queue;

    public ProducerQueue(BlockingQueue<Goods> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods goods = null;
            if (i % 2 == 0) {
                goods = new Goods("旺仔" + i, "小馒头" + i);

            } else {
                goods = new Goods("娃娃哈" + i, "矿泉水" + i);
            }
            System.out.println("生产者开始生产商品" + goods.getBrand() + "--" + goods.getName());
            try {
                queue.put(goods);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

消费者 

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:44
 * @Description:消费者不断消费队列里的商品,队列没有商品就不能被消费
 */
public class ConsumerQueue implements Runnable {
    private BlockingQueue<Goods> queue;


    public ConsumerQueue(BlockingQueue<Goods> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            Goods take = null;
            try {
                take = this.queue.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费者消费商品" + take.getBrand() + "--" + take.getName());
        }
    }
}
Main
package com.juc.pool.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:45
 * @Description:一个有限的blocking queue由数组支持。 这个队列排列元素FIFO(先进先出)。
 * 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。
 */
public class ArrayBlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue queue = new ArrayBlockingQueue(5);
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();

    }
}
  •  LinkedBlockingQueue
  • 基于链接节点的可选限定的blockingqueue 。 这个队列排列元素FIFO(先进先出)。 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。 链接队列通常具有比基于阵列的队列更高的吞吐量,但在大多数并发应用程序中的可预测性能较低。

package com.juc.pool.queue;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 9:45
 * @Description:基于链接节点的可选限定的blocking queue 。 这个队列排列元素FIFO(先进先出)。
 * 队列的头部是队列中最长的元素。 队列的尾部是队列中最短时间的元素。 新元素插入队列的尾部,队列检索操作获取队列头部的元素。 链接队列通常具有比基于阵列的队列更高的吞吐量,但在大多数并发应用程序中的可预测性能较低。
 */
public class LinkedBlockingQueueDemo {
    public static void main(String[] args) {
        BlockingQueue queue = new LinkedBlockingQueue();
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();

    }
}
  •  DelayQueue

一个无限制的BlockingQueueDelayed元素,其中元素只能在其延迟到期时才被使用。 队列的Delayed元素,其延迟期满后保存时间。 如果没有延迟到期,那么没有头, poll会返回null 。 当元素的getDelay(TimeUnit.NANOSECONDS)方法返回小于或等于零的值时,就会发生getDelay(TimeUnit.NANOSECONDS) 。 即使未使用的元素不能使用takepoll ,它们另外被视为普通元素。 例如, size方法返回到期和未到期元素的计数。 此队列不允许空元素。

package com.juc.pool.queue;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

public class DelayedTask implements Delayed {

    private String name;

    private Long delayedTime;

    private TimeUnit delayedTimeUnit;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public DelayedTask(String name, Long delayedTime, TimeUnit delayedTimeUnit) {
        this.name = name;
        this.delayedTime = delayedTime;
        this.delayedTimeUnit = delayedTimeUnit;
    }

    @Override
    public long getDelay(TimeUnit unit) {
        return unit.convert(delayedTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        if (this.getDelay(TimeUnit.MILLISECONDS) > o.getDelay(TimeUnit.MILLISECONDS)) {
            return 1;
        } else if (this.getDelay(TimeUnit.MILLISECONDS) < o.getDelay(TimeUnit.MILLISECONDS)) {
            return -1;
        }
        return 0;
    }
}
package com.juc.pool.queue;

import java.util.concurrent.DelayQueue;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:04
 * @Description:一个无限制的blocking queue的Delayed元素,其中元素只能在其延迟到期时才被使用。 队列的头是Delayed元素,其延迟期满后保存时间。 如果没有延迟到期,那么没有头, poll会返回null 。 当元素的getDelay(TimeUnit.NANOSECONDS)方法返回小于或等于零的值时,就会发生getDelay(TimeUnit.NANOSECONDS) 。 
 * 即使未使用的元素不能使用take或poll ,它们另外被视为普通元素。 例如, size方法返回到期和未到期元素的计数。 此队列不允许空元素。 
 */
public class DelayedQueueDemo {
    public static void main(String[] args) {

        DelayQueue<DelayedTask> queue = new DelayQueue<DelayedTask>();
        queue.put(new DelayedTask("1", 1000L, TimeUnit.MILLISECONDS));
        queue.put(new DelayedTask("2", 2000L, TimeUnit.MILLISECONDS));
        queue.put(new DelayedTask("3", 3000L, TimeUnit.MILLISECONDS));
        System.out.println("queue put done");

        while (!queue.isEmpty()) {
            DelayedTask task = null;
            try {
                task = queue.take();
                System.out.println(System.currentTimeMillis() + "---" + task.getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  •  PriorityBlockingQueue

一个无界的BlockingQueue使用与PriorityQueue类相同的排序规则,并提供阻塞检索操作。 虽然这个队列在逻辑上是无界的,但由于资源耗尽,尝试的添加可能会失败(导致OutOfMemoryError )。 这个类不允许null元素。 根据natural ordering的优先级队列也不允许插入不可比较的对象(这样做在ClassCastException )。 
该类及其迭代器实现CollectionIterator接口的所有可选方法。 方法iterator()中提供的迭代器不能保证以任何特定的顺序遍历PriorityBlockingQueue的元素。 如果需要排序遍历,请考虑使用Arrays.sort(pq.toArray()) 。 此外,方法drainTo可以用于以优先级顺序移除一些或所有元素并将它们放置在另一集合中。 这个类的操作不会保证等同优先级的元素的排序。 如果需要强制执行排序,可以定义自定义类或比较器,它们使用辅助键来破坏主优先级值的关系。 例如,这里是一个适用于先进先出的打破破坏类似元素的课程。 要使用它,将插入一个new FIFOEntry(anEntry)而不是一个简单的条目对象。

 

package com.juc.pool.queue;

public class PriorityTask implements Comparable<PriorityTask> {

    private int id;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int compareTo(PriorityTask o) {
        return this.id > o.id ? 1 : (this.id < o.id) ? -1 : 0;
    }

    @Override
    public String toString() {
        return "PriorityTask{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}
package com.juc.pool.queue;

import java.util.concurrent.PriorityBlockingQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:12
 * @Description:一个无界的blocking queue使用与PriorityQueue类相同的排序规则,并提供阻塞检索操作。
 * 虽然这个队列在逻辑上是无界的,但由于资源耗尽,尝试的添加可能会失败(导致OutOfMemoryError )。
 * 这个类不允许null元素。 根据natural ordering的优先级队列也不允许插入不可比较的对象(这样做在ClassCastException )。
 */
public class PriorityBlockingQueueDemo {
    public static void main(String[] args) {

        PriorityBlockingQueue<PriorityTask> queue = new PriorityBlockingQueue<>();
        for (int i = 1; i <=4; i++) {
            PriorityTask task = new PriorityTask();
            task.setId(i);
            task.setName("Id为" + task.getId());
            if (i % 2 == 0) {
                task.setId(i + 2);
                task.setName("Id为" + task.getId());
            }
            queue.add(task);
        }
        System.out.println("容器:" + queue);
        try {
            System.out.println(queue.take().getId());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("容器:"+queue);
    }
}
  •  SynchronousQueue

A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。 同步队列没有任何内部容量,甚至没有一个容量。 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在; 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它; 你不能迭代,因为没有什么可以迭代。 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。 为了其他Collection方法(例如contains )的目的, SynchronousQueue充当空集合。 此队列不允许null元素。 
同步队列类似于CSP和Ada中使用的会合通道。 它们非常适用于切换设计,其中运行在一个线程中的对象必须与在另一个线程中运行的对象同步,以便交付一些信息,事件或任务。 

此类支持可选的公平策略,用于订购等待的生产者和消费者线程。 默认情况下,此订单不能保证。 然而,以公平设置为true的队列以FIFO顺序授予线程访问权限。

package com.juc.pool.queue;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 18:31
 * @Description:A blocking queue其中每个插入操作必须等待另一个线程相应的删除操作,反之亦然。
 * 同步队列没有任何内部容量,甚至没有一个容量。 你不能peek在同步队列,因为一个元素,当您尝试删除它才存在;
 * 您无法插入元素(使用任何方法),除非另有线程正在尝试删除它;你不能迭代,因为没有什么可以迭代。
 * 队列的头部是第一个排队的插入线程尝试添加到队列中的元素; 如果没有这样排队的线程,那么没有元素可用于删除,并且poll()将返回null 。
 * 为了其他Collection方法(例如contains )的目的, SynchronousQueue充当空集合。 此队列不允许null元素。
 */
public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<Goods> queue = new SynchronousQueue<Goods>();
        ProducerQueue producerQueue = new ProducerQueue(queue);
        ConsumerQueue consumerQueue = new ConsumerQueue(queue);
        new Thread(producerQueue).start();
        new Thread(consumerQueue).start();
    }
}

拒绝策略

  •  ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。【默认第一个】
  •  ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
  •  ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
  •  ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务

Execute方法执行逻辑

  1. 如果当前运行的线程少于corePoolSize,则会创建新的线程执行新的任务。
  2. 如果运行的线程个数等于或者大于corePoolSize,则会将提交的任务放到阻塞队列WorkQueue中。
  3. 如果当前WorkQueue队列已满,则会创建新的线程来执行任务。
  4. 如果线程个数已经超过maximumPoolSize,则会使用饱和策略RejectedExecutionHander来处理。

submit方法 

submit是基方法Executor.execute(Runnable)的延伸,通过创建并返回一个Future类对象可用于取消执行和/或等待完成。

package com.juc.pool.queue;

import java.util.concurrent.Callable;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 19:37
 * @Description: Callable
 */
public class Task implements Callable<String> {

    @Override
    public String call() throws Exception {
        return Thread.currentThread().getName() + "is running";
    }
}
package com.juc.pool.queue;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;

import static java.util.concurrent.Executors.*;
/**
*@Author zcm
*@Email [email protected]
*@date 2020/5/31 19:44
*@Description:submit提交值返回任务以执行,并返回代表任务待处理结果的Future。 未来的get方法将在成功完成后返回任务的结果。
 *  如果您想立即阻止等待任务,您可以使用result = exec.submit(aCallable).get();格式的result = exec.submit(aCallable).get(); 
 * 注意: Executors类包括一组方法,可以将一些其他常见的类似对象的对象,例如PrivilegedAction转换为Callable表单,以便它们可以提交。 
*/
public class TestTask {
    public static void main(String[] args) {
        ExecutorService executorService = newFixedThreadPool(5);
        for (int i = 1; i <= 10; i++) {
            Future<String> future = executorService.submit(new Task());
            try {
                Object x = future.get();
                System.out.println(x);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }

        }

    }


}

线程池的关闭

关闭线程池,可以通过shutdown和shutdownNow两个方法。

原理:遍历线程池中的所有线程,然后一次中断。

  1. shutdownNow首先将线程池的状态设置为Stop然后尝试停止所有正在执行和未执行任务的线程,并返回等待执行任务的列表。
  2. shutdown只是将线程池的状态设置为SHUTDOWN状态,然后中断所有没有执行任务的线程。

自定义适合自己的线程池 

package com.juc.pool;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @Author zcm
 * @Email [email protected]
 * @date 2020/5/31 19:54
 * @Description:自定义个线程池
 */
public class ThreadPoolDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(5));
        threadPoolExecutor.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("打印个随机数:"+Math.random());;
            }
        });
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章