线程阻塞导致线程池队列满了,Java多线程与线程池技术详解
00-1010I、单例模式1、饥饿模式2、懒惰模式(单线程)3、懒惰模式(多线程)二、阻塞队列生产者-消费者模型的实现三、线程池1、创建线程池的方法(1)线程池执行器(2)快速创建线程池的执行器(API)2)二、线程池的工作流程
00-1010设计模式:软件设计模式
是一套被反复使用,被大多数人知晓,分类编目,代码设计的经验。设计模式用于重用代码,使其他人更容易理解,确保代码的可靠性和程序的可重用性。
单体模式:这是一种设计模式。确保程序中一个类只有一个实例,不会创建多个实例。单例模式的具体实现可以分为“懒人”和“饿人”两种类型。
方法必须是私有的,这样就不能在类之外创建类。
00-1010类,并创建一个实例。(缺点是不管用不用都会创建对象,占用空间比较大)
//加载类时创建一个对象,并确保只能有一个实例对象class Singleton { Private Static Singleton Instance=NewSingleton();//私有构造函数private Singleton() {} //同一个实例对象public static Singleton getInstance(){ return instance;}}
00-1010类在加载时不创建实例,只有在第一次使用时才创建实例。
(缺点:线程不安全。如果有多个线程并行执行,可能会创建多个实例,因此它只适用于单个线程)
类Singleton { private static Singleton instance=null;private singleton(){ } PublicSingletonGetInstance(){//第一次使用时,创建一个实例if(instance==null){ instance=newsingleton();}//以后使用时,直接返回第一次创建的实例返回实例;}}
00-1010上面的懒惰模式存在线程安全问题。如果多个线程同时调用getInstance()方法,可能会创建多个实例。所以在多线程时,我们需要使用synchronized来提高线程的安全性。
类Singleton { private static Singleton instance=null;Private Singleton() {} //锁定保证不会有多个线程访问已更改的代码块Public Synchronized Static Singleton GetInstance(){ if(instance==null){ instance=newsingleton();}返回实例;}}}对于上面的代码,虽然保证了线程安全,但是对于lazy模式,只有在第一次调用时才会创建实例,大多数情况下只会执行读操作。如果整个代码块都被锁定,程序执行的效率会大大降低。我们可以进一步优化上面的程序。对于读操作,我们使用volatile修改变量;只需在写操作的代码块中添加一个锁。
【单例模式懒惰模式下多线程的进一步优化】双if判断
Class Singleton {//使用volatile修改变量private static volatile Singleton instance=null;私有Singleton(){ };public static singleton getinstance(){ if (instance==null){//仅锁定与同步的写操作相关的代码(singleton.class) {//Double if需要判断以防止实例在多线程if(instance==null)中锁定之前发生更改{instance=new sing
leton(); } } } return instance; }}写操作加锁,保证线程安全;
如果已经实例化,进行读操作,保证多个线程并发并行执行,保证效率。
二、阻塞队列
阻塞队列是什么?
阻塞队列是一种特殊的队列。也遵守先进先出的原则。
阻塞队列是一种线程安全的数据结构:
当队列满的时候,继续入队队列就会阻塞,知道有其他线程从队列中取走元素;当队列空的时候,继续出队也会阻塞,直到其他线程往队列中插入元素。阻塞队列的一个经典应用场景就是生产者消费者模型。
标准库中的阻塞队列:
BlockingQueue是一个接口,真是实现的是类是:LinkedBlockingQueue。put方法用于阻塞式的入队列,take用于阻塞式的出队列。BlockingQueue也有offer、poll、peek方法,但是不具有阻塞特性。
阻塞队列的实现
通过循环队列实现;使用synchronized进行加锁控制put插入元素,如果队列满了,就进行wait(要在循环中进行wait,多线程情况下可能唤醒多个线程,所以唤醒后队列可能还是满的)take取出元素,如果队列为空,就wait(循环中wait)
public class BlockingQueue{ //使用循环数组来实现阻塞队列 private int[] array; //队列中已经存放元素的个数 private int size; //放入元素的下标 private int putIndex; //取元素的下标 private int takeIndex; //在构造方法中指定队列的大小 public BlockingQueue(int capacity){ array=new int[capacity]; } /*放元素:需要保证线程安全,如果队列满了,线程进入等待*/ public synchronized void put(int m) throws InterruptedException { //队列满,线程等待 if(size==array.length){ //需要注意的是,进行等待的是当显得实例对象,不是类对象 this.wait(); } //放元素,同时更新下标 array[putIndex]=m; putIndex=(putIndex+1)%array.length; size++; //通知等待的线程 notifyAll(); } /*取元素:保证线程安全。如果队列为空,线程等待*/ public synchronized int take() throws InterruptedException { //队列为空,线程等待 if(size==0){ this.wait(); } //取元素,同时更新下标 int ret=array[takeIndex]; takeIndex=(takeIndex+1)%array.length; size--; //通知等待的线程 notifyAll(); return ret; }}
生产者消费者模型
生产者消费者模型就是通过一个容器来解决生产者和消费者之间的强耦合问题。
生产者和消费者之间不直接通信,而通过阻塞队列来实现通讯,所以生产者生产完数据不需要等待消费者来处理,直接扔给阻塞队列。消费者也不需要去找生产者,而是直接从阻塞队列中取。
阻塞队列相当于一个缓冲区,平衡了消费者和生产者的处理能力;阻塞队列也能使生产者和消费者之间解耦。耦合和解耦:
耦合指的是两个类之间联系的紧密程度。强耦合(表示类之间存在着直接的关系)。弱耦合(在两个类的中间加入一层,将原来的之间关系变成间接关系,使得两个类对中间层是强耦合,两个类之间变成了弱耦合。解耦:降低耦合度,也就是将强耦合变成弱耦合的过程。
三、线程池
池:字符串常量池(类似缓存)、数据库连接池等
线程池:初始化的时候就创建一定数量的线程【不同的从线程池的阻塞队列中取任务(消费者)】【在其他线程中提交任务到线程池(生产者)】
优点:
线程的创建和销毁都有一定的代价,使用线程池就可以重复使用线程来执行多组任务。(如果线程不再使用,并不是真正的将线程释放,而是放到一个池子中,下次如果需要用到线程直接从池子中取,不必通过系统来创建)
1、创建线程池的的方法
(1)ThreadPoolExecutor
提供了更多的可选参数,可以进一步细化线程池行为的设定。
以第三个构造方法为例:
corePoolSize:表示核心线程的数量maximumPoolSize:最大线程数(核心线程+临时线程)keepAliveTime:允许临时线程空闲的时间(如果超过该时间临时线程还是没有任务执行,就被销毁)unit: keepaliveTime的时间单位workQueue:传递任务的阻塞队列threadFactory:规定创建线程的标准RejectedExecutionHandler:拒绝策略,如果阻塞队列已满,再传进来任务该怎么办【1】AbortPolicy():超过负荷,直接抛出异常(默认的拒绝策略,使用其他不带拒绝策略的构造方法时的默认参数)
【2】CallerRunsPolicy():调用者负责处理
【3】DiscardOldestPolicy():丢弃队列中最老的任务
【4】DiscardPolicy():丢弃新来的任务
创建线程池如下:
//使用ThreadPoolExecutor创建线程池 ThreadPoolExecutor threadPool1=new ThreadPoolExecutor( 5, 10, 3, //自由线程无任务时最大存活时间单位:分 TimeUnit.MINUTES, //一般不使用无边界的阻塞队列,内存有限 new ArrayBlockingQueue<>(100), //规定创建线程的标准 Executors.defaultThreadFactory(), //拒绝策略:一般最多使用CallerRunsPolicy(),或自己实现 new ThreadPoolExecutor.CallerRunsPolicy() );
(2)Executors(快捷创建线程池的API)
Executors创建线程的几种方式:
newFixedThreadPool:创建固定线程数的线程池(没有临时线程)newCachedThreadPool:创建线程数目动态增长的线程池(缓存的线程池,没有核心线程,全是临时线程)newSingleThreadExecutor:创建只包含单个线程的线程池newScheduledThreadPool:设定延迟时间后执行任务,或者定期执行命令(计划线程池)创建线程池如下:
//Executors的四种创建线程的方法 //没有临时线程的线程池 ExecutorService threadPool2= Executors.newFixedThreadPool(10); //线程数目动态增长的线程池 ExecutorService threadPool3=Executors.newCachedThreadPool(); //创建单个线程的线程池 ExecutorService threadPool4=Executors.newSingleThreadExecutor(); //计划线程池 ExecutorService threadPool5=Executors.newScheduledThreadPool(7);
2、线程池的工作流程
线程池工作流程
使用线程池:
创建线程池
提交任务:
【1】submit(Runnable task)
【2】execute(Runnable task)
到此这篇关于Java多线程常见案例分析线程池与单例模式及阻塞队列的文章就介绍到这了,更多相关Java线程池内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。