Java多线程常见案例分析线程池与单例模式及阻塞队列

目录
  • 一、单例模式
    • 1、饿汉模式
    • 2、懒汉模式(单线程)
    • 3、懒汉模式(多线程)
  • 二、阻塞队列
    • 阻塞队列的实现
    • 生产者消费者模型
  • 三、线程池
    • 1、创建线程池的的方法
      • (1)ThreadPoolExecutor
      • (2)Executors(快捷创建线程池的API)
    • 2、线程池的工作流程

一、单例模式

设计模式:软件设计模式

是一套被反复使用、多数人知晓、经过分类编目、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。

单例模式:是设计模式的一种。保证某个类在程序中只存在唯一一份实例,不会创建出多个实例。单例模式的具体实现分为“懒汉”和“饿汉”两种。

构造方法必须是私有的,保证该类不能在类外被随便创建。

1、饿汉模式

类加载的同时,创建实例。(缺点是无论是否使用都会创建对象,比较占空间)

//类加载的时候创建对象,确保只能有一个实例对象
class Singleton {
    private static Singleton instance = new Singleton();
    //私有的构造方法
    private Singleton() {}
    //只能通过getInstance()方法获取到同一个实例对象
    public static Singleton getInstance() {
        return instance;
    }
}

2、懒汉模式(单线程)

类加载的时候不创建实例,第一次使用的时候才创建实例。

(缺点:线程不安全,如果存在多个线程并发并行执行,可能创建多个实例,所以只适用于单线程)

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    public static Singleton getInstance() {
        //第一次使用时,创建实例
        if (instance == null) {
            instance = new Singleton();
        }
        //后面使用时,直接返回第一次创建的实例
        return instance;
    }
}

3、懒汉模式(多线程)

上面的懒汉模式存在线程安全问题,如果多个线程同时调用getInstance()方法,可能创建多个实例。所以在多线程时,我们需要使用synchronized改善线程安全问题。

class Singleton {
    private static Singleton instance = null;
    private Singleton() {}
    //加锁保证不会有多个线程同时访问改代码块
    public synchronized static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

对于以上代码,虽然保证了线程安全,但是对于懒汉模式,只有在第一次调用时才会创建实例,大多数境况下只进行读操作,如果对代码块整体加锁,程序执行的效率会大大降低。我们可以对上面的程序进一步优化,对于读操作,我们使用volatile修饰变量;只给写操作的代码块加上锁即可。

【单例模式懒汉模式多线程的进一步优化】双重if判定

class Singleton {
    //使用volatile修饰变量
    private static volatile Singleton instance = null;
    private Singleton() {};
    public static Singleton getInstance() {
        if (instance == null) {
            //只给写操作的相关代码加锁
            synchronized (Singleton.class) {
                //需要双重if判断,防止在多线程中加锁前instance发生变化
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        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

提供了更多的可选参数,可以进一步细化线程池行为的设定。

以第三个构造方法为例:

  1. corePoolSize:表示核心线程的数量
  2. maximumPoolSize:最大线程数(核心线程+临时线程)
  3. keepAliveTime:允许临时线程空闲的时间(如果超过该时间临时线程还是没有任务执行,就被销毁)
  4. unit: keepaliveTime的时间单位
  5. workQueue:传递任务的阻塞队列
  6. threadFactory:规定创建线程的标准
  7. 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线程池内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Java实现线程安全单例模式的五种方式的示例代码

    目录 饿汉式 枚举单例 懒汉式 DCL 懒汉式 静态内部类懒汉单例 饿汉式 饿汉式:类加载就会导致该单实例对象被创建 // 问题1:为什么加 final // 问题2:如果实现了序列化接口, 还要做什么来防止反序列化破坏单例 public final class Singleton_hungry implements Serializable { // 问题3:为什么设置为私有? 是否能防止反射创建新的实例? private Singleton_hungry(){} // 问题4:这样初始化是否

  • Java使用线程池执行定时任务

    目录 1.schedule 2.scheduleAtFixedRate 3.scheduleWithFixedDelay 总结 前言: 在 Java 语言中,有两个线程池可以执行定时任务:ScheduledThreadPool 和 SingleThreadScheduledExecutor,其中 SingleThreadScheduledExecutor 可以看做是 ScheduledThreadPool 的单线程版本,它的用法和 ScheduledThreadPool 是一样的,所以本文重点来

  • Java阻塞队列的实现及应用

    目录 1.手写生产者消费者模型 2.手写定时器 总结 1.手写生产者消费者模型 所谓生产者消费者模型,可以用我们生活中的例子来类比:我去一个小摊儿买吃的,老板把已经做好的小吃都放在摆盘上,供我挑选.那么,老板就是生产者:我就是消费者:摆盘就是阻塞队列,用来当做生产与消费的缓冲区.因此,阻塞队列在生产者与消费者模型中起着至关重要的缓冲作用. 此次先演示如何手写阻塞队列(也可以使用Java库中自带的阻塞队列). 手写的阻塞队列只实现最基础的两个功能:入队和出队.之所以叫阻塞队列,是因为当队空或者队满

  • Java线程池7个参数的含义

    目录 参数1:corePoolSize 参数2:maximumPoolSize 参数3:keepAliveTime 参数4:TimeUnit 参数5:BlockingQueue 参数6:ThreadFactory 参数7:RejectedExecutionHandler 总结 所谓的线程池的 7 大参数是指,在使用 ThreadPoolExecutor 创建线程池时所设置的 7 个参数, 如以下源码所示: public ThreadPoolExecutor(int corePoolSize, i

  • Java中常用阻塞队列的问题小结

    Java常用阻塞队列 ArrayBlockingQueue 内部由一个固定长度的数组来实现阻塞队列 /** The queued items */ final Object[] items; /** items index for next take, poll, peek or remove */ int takeIndex; /** items index for next put, offer, or add */ int putIndex; public ArrayBlockingQue

  • java 线程池状态及状态转换

    目录 线程池状态转移 terminated方法 总结 前言: 在 Java 中,线程池的状态和线程的状态是完全不同的, 线程有 6 种状态: NEW:初始化状态. RUNNABLE:可运行/运行状态. BLOCKED:阻塞状态. WAITING:无时限等待状态 TIMED_WAITING:有时限等待状态和 TERMINATED:终止状态. 而线程池的状态有以下 5 种: RUNNING:运行状态,线程池创建好之后就会进入此状态,如果不手动调用关闭方法,那么线程池在整个程序运行期间都是此状态. S

  • 详解Java单例模式的实现与原理剖析

    目录 一.什么是单例模式 二.哪些地方用到了单例模式 三.单例模式的优缺点 优点 缺点 四.手写单例模式 饿汉式 枚举饿汉式 DCL懒汉式 双检锁懒汉式 内部类懒汉式 小结 一.什么是单例模式 单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一.这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建.这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对

  • java 线程池如何执行策略又拒绝哪些策略

    目录 线程池执行流程 线程池拒绝策略 DiscardPolicy拒绝策略 AbortPolicy拒绝策略 自定义拒绝策略 总结 前言: 聊到线程池就一定会聊到线程池的执行流程,也就是当有一个任务进入线程池之后,线程池是如何执行的?我们今天就来聊聊这个话题.线程池是如何执行的?线程池的拒绝策略有哪些? 线程池执行流程 想要真正的了解线程池的执行流程,就得先从线程池的执行方法 execute() 说起,execute() 实现源码如下: public void execute(Runnable co

  • Java多线程常见案例分析线程池与单例模式及阻塞队列

    目录 一.单例模式 1.饿汉模式 2.懒汉模式(单线程) 3.懒汉模式(多线程) 二.阻塞队列 阻塞队列的实现 生产者消费者模型 三.线程池 1.创建线程池的的方法 (1)ThreadPoolExecutor (2)Executors(快捷创建线程池的API) 2.线程池的工作流程 一.单例模式 设计模式:软件设计模式 是一套被反复使用.多数人知晓.经过分类编目.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容易被他人理解.保证代码可靠性.程序的重用性. 单例模式:是设计模式的一种.

  • 从java源码分析线程池(池化技术)的实现原理

    目录 线程池的起源 线程池的定义和使用 方案一:Executors(仅做了解,推荐使用方案二) 方案二:ThreadPoolExecutor 线程池的实现原理 前言: 线程池是一个非常重要的知识点,也是池化技术的一个典型应用,相信很多人都有使用线程池的经历,但是对于线程池的实现原理大家都了解吗?本篇文章我们将深入线程池源码来一探究竟. 线程池的起源 背景: 随着计算机硬件的升级换代,使我们的软件具备多线程执行任务的能力.当我们在进行多线程编程时,就需要创建线程,如果说程序并发很高的话,我们会创建

  • 浅谈java常用的几种线程池比较

    1. 为什么使用线程池 诸如 Web 服务器.数据库服务器.文件服务器或邮件服务器之类的许多服务器应用程序都面向处理来自某些远程来源的大量短小的任务.请求以某种方式到达服务器,这种方式可能是通过网络协议(例如 HTTP.FTP 或 POP).通过 JMS 队列或者可能通过轮询数据库.不管请求如何到达,服务器应用程序中经常出现的情况是:单个任务处理的时间很短而请求的数目却是巨大的. 构建服务器应用程序的一个简单模型是:每当一个请求到达就创建一个新线程,然后在新线程中为请求服务.实际上对于原型开发这

  • 高价值Java多线程面试题分析

    问题一 A线程正在执行一个对象中的同步方法,B线程是否可以同时执行同一个对象中的非同步方法? 可以,两个线程运行所需资源不同,不需要抢占. 案例一. package duoxiancheng2; /** * @author yeqv * @program A2 * @Classname Ms1 * @Date 2022/2/7 19:08 * @Email w16638771062@163.com */ public class Ms1 { //A线程正在执行一个对象中的同步方法,B线程是否可以

  • nginx源码分析线程池详解

    nginx源码分析线程池详解 一.前言 nginx是采用多进程模型,master和worker之间主要通过pipe管道的方式进行通信,多进程的优势就在于各个进程互不影响.但是经常会有人问道,nginx为什么不采用多线程模型(这个除了之前一篇文章讲到的情况,别的只有去问作者了,HAHA).其实,nginx代码中提供了一个thread_pool(线程池)的核心模块来处理多任务的.下面就本人对该thread_pool这个模块的理解来跟大家做些分享(文中错误.不足还请大家指出,谢谢) 二.thread_

  • Java并发之串行线程池实例解析

    前言 做Android的这两年时间,通过研究Android源码,也会Java并发处理多线程有了自己的一些理解. 那么问题来了,如何实现一个串行的线程池呢? 思路 何为串行线程池呢? 也就是说,我们的Runnable对象应该有个排队的机制,它们顺序从队列尾部进入,并且从队列头部选择Runnable进行执行. 既然我们有了思路,那我们就考虑一下所需要的数据结构? 既然是从队列尾部插入Runnable对象,从队列头部执行Runnable对象,我们自然需要一个队列.Java的SDK已经给我们提供了很好的

  • 聊聊java多线程创建方式及线程安全问题

    什么是线程 线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位.每个程序程序都至少有一个线程,也即是程序本身. 线程的状态 新建(New):创建后尚未启动的线程处于这种状态 运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间. 等待(Wating):处于这种状态的线程不会被分配CPU执行时间.等待状态又分为无限期等待和有限期等待,处于无

  • Java多线程之如何确定线程数的方法

    关于多线程的线程数的确定,最近研读过几篇paper,在此做一下笔记,方便使用时翻看. 1.<Java 虚拟机并发编程>中介绍 就是说:线程数 = CPU的核心数 * (1 - 阻塞系数) 另一篇:<Java Concurrency in Practice>即<java并发编程实践>,给出的线程池大小的估算公式: Nthreads=Ncpu*Ucpu*(1+w/c),其中 Ncpu=CPU核心数,Ucpu=cpu使用率,0~1:W/C=等待时间与计算时间的比率 仔细推敲两

  • Java多线程编程安全退出线程方法介绍

    线程停止 Thread提供了一个stop()方法,但是stop()方法是一个被废弃的方法.为什么stop()方法被废弃而不被使用呢?原因是stop()方法太过于暴力,会强行把执行一半的线程终止.这样会就不会保证线程的资源正确释放,通常是没有给与线程完成资源释放工作的机会,因此会导致程序工作在不确定的状态下 那我们该使用什么来停止线程呢 Thread.interrupt(),我们可以用他来停止线程,他是安全的,可是使用他的时候并不会真的停止了线程,只是会给线程打上了一个记号,至于这个记号有什么用呢

  • Java并发编程面试之线程池

    目录 什么是线程池 线程池好处 线程池的执行流程 怎么用线程池 corePoolSize maximumPoolSize keepAliveTime unit workQueue threadFactory ejectedExecutionHandler 线程池参数如何设置? 监控线程池 总结 什么是线程池 是一种基于池化思想管理线程的工具.池化技术:池化技术简单点来说,就是提前保存大量的资源,以备不时之需.比如我们的对象池,数据库连接池等. 线程池好处 我们为什么要使用线程池,直接new th

随机推荐