阻塞队列、线程池、原子性及并发工具类

简介: 阻塞队列、线程池、原子性及并发工具类的简单示例

一、阻塞队列

ArrayBlockingQueue类:底层是数组,有界,没有无参构造方法

LinkedBlockingQueue类:底层是链表,无界但最多能存放int的最大值,无参构造方法默认容量就是最大值

常用方法:

put(Object o):将参数放入队列,如果放不进去会阻塞

take():取出第一个数据,取不到会阻塞

使用阻塞队列实现生产者消费者模式:

public class Test {
    public static void main(String[] args) throws
            InterruptedException {
        ArrayBlockingQueue<String> arrayBlockingQueue = new
                ArrayBlockingQueue<>(1);
        Foodie foodie = new Foodie(arrayBlockingQueue);
        Cooker cooker = new Cooker(arrayBlockingQueue);
        foodie.start();
        cooker.start();
    }
}
class Foodie extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Foodie(ArrayBlockingQueue<String> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                String take = arrayBlockingQueue.take();
                System.out.println("生产者消费了一个"+take);} catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class Cooker extends Thread{
    private ArrayBlockingQueue<String> arrayBlockingQueue;
    public Cooker(ArrayBlockingQueue<String> arrayBlockingQueue){
        this.arrayBlockingQueue = arrayBlockingQueue;
    }
    @Override
    public void run() {
        while (true) {
            try {
                arrayBlockingQueue.put("汉堡包");
                System.out.println("生产者放了一个汉堡包");} catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

image.gif

二、线程池

每一个线程的启动和结束都是比较消耗时间和资源的,如果在系统中用到很多线程,大量的线程启动和结束操作会导致性能变卡,响应变慢,为了解决这个问题,引入了线程池的设计思想,线程池就是一种生产者消费者模式

主要思想是创建若干个线程放入池子,有任务需要处理时将任务提交到线程池中的任务队列,任务处理完后线程池并不会销毁,而是继续在线程池中等待下一个任务

静态方法创建线程池:

1、使用Executors类中的静态方法static ExecutorService newCachedThreadPool()创建线程池,默认线程池是空的,根据需要创建线程,超过60秒未被使用的线程则销毁,最多创建int最大值个线程

2、使用Executors类中的静态方法static ExecutorService newFixedThreadPool(int nThreads)创建线程池,默认线程池是空的,根据需要创建线程,参数表示线程池最多能够创建的线程,创建的线程将一直存到直到显式调用shutdown()方法

3、这两个方法返回值类型是ExecutorService接口,这个接口里边定义了操作线程的方法,常用的两个方法是:

submit(task):task是需要执行的任务,可以是实现Runnable接口或Callable接口的类对象,也可以是Lambda表达式

shutdown():用于任务执行后关闭线程池

使用ThreadPoolexecutor类创建线程池:

1、上述使用静态方法创建的线程池实际上是使用类该类来创建并返回线程池

2、常用构造方法:

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

image.gif

corePoolSize :核心线程数量

maximumPoolSize :最大线程数量

keepAliveTime :空闲线程存活时间的值

unit :存活时间的单位

workQueue :任务队列

threadFactory :线程工厂,指定创建线程的方式

handler :任务拒绝策略,当任务队列已满,新任务不能提交到线程池时触发对新任务

的处理策略

3、任务拒绝策略

ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectExecutionException异

常,默认的任务拒绝策略

ThreadPoolExecutor.DiscardPolicy:丢弃任务但不抛出异常,不推荐使用

ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务然后将当

前任务加入任务队列

ThreadPoolExecutor.CallerRunsPolicy:调用任务的run()方法绕过线程池直接执行

三、原子性

原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败。即使是多个线程一起执行,一个操作一旦开始,就不会被其他线程所干扰。

volatile不能保证原子性,synchronized可以保证原子性。

原子性操作类,既能保证原子性又比synchronized高效,如Atomiclnteger

1、构造方法

public AtomicInteger():创建初始值为0的对象

public AtomicInteger(int value):创建指定值的对对象

2、常用方法

方法名 说明
int get() 获取值
int getAndIncrement()

以原子方式将当前值加1,返回加1前的旧值

int incrementAndGet() 以原子方式将当前值加1,返回加1后的新值
int addAndGet(int value) 以原子方式将当前值与参数相加,并返回结果
int getAndSet(int value) 以原子方式将当前值设置为参数的值,返回旧值

3、原理(底层使用自旋+CAS算法)

自旋:就是重新获取共享变量的操作

CAS算法:

● 线程在修改共享数据时查看共享数据的值与变量副本的值是否相同

● 如果相同说明共享变量的值没有被其他线程修改,可以直接将新值赋给共享数据

● 如果不相同,说明在对变量副本进行操作时有其他线程修改了共享数据,此时不能

修改共享数据,而是重新获取共享数据的值

4、sychronized与CAS的区别

相同点:在多线程的情况下,都可以保证共享数据的安全性

不同的:

1、sychronized总是从最坏的角度出发,认为每次获取数据时,别的线程都有可能修改,所以每次操作共享数据前,都会上锁(悲观锁

2、CAS是从乐观的角度出发,假设每次获取数据时别的线程都不会修改,所以不上锁,只是在修改共享数据时再查看其他线程有没有修改共享数据,如果有就重新获取新的共享数据,如果没有就直接修改共享数据(乐观锁

四、并发工具类

HashTable类

● HashMap是线程不安全的,为了保证数据安全性可以使用线程安全的HashTable代替

● HashTable效率比较低下

● HashTable采用sychronized悲观锁,当有线程访问时会将整个集合加锁

ConcurrentHashMap类

● ConcurrentHashMap是线程安全的,效率较高

● 在JDK7和JDK8中实现的原理有区别

● JDK7原理

       ■ 使用无参构造创建对象时,创建一个默认长度16,加载因子为0.75的数组,数组名为

segment,并且这个数组无法扩容

       ■ 再创建长度为2的小数组,将该小数组地址存入segment数组的0索引,segment其他索引均为null,这个小数组作为模板数组

       ■ 在添加元素时会根据元素的哈希值计算出在segment的应存入位置的索引。如果为null

则按照模板数组创建小数组,创建完毕后会进行二次哈希,计算出在小数组中应存入位置的索引,然后直接存入;如果不是null则直接找到小数组进行二次哈希,计算出在小数组中应存入位置的索引,如果小数组需要扩容则扩容到两倍,然后存入,如果小数组不需要扩容就查看该位置有无元素,如果没有元素直接存,如果有元素就调用equals()方法比较,相同的话不存,不相同就形成哈希桶结构

       ■ 根据添加的原理,该集合实际上是创建了16个哈希表结构

       ■ 保证线程安全的方式是,当线程对segment某个索引处的哈希表进行操作时对该索引处加锁,而其他索引则不加锁

● JDK8原理

       ■ 使用无参构造创建对象时并不会创建底层数组,而是在第一次添加数据时初始化长度为16,加载因子为0.75的数组

       ■ 添加元素时计算应存入的索引,如果索引为null,则利用CAS算法将元素添加到此处;如果不为null,则利用volatile关键字获取当前位置最新的节点地址,将当前元素挂在它下面变成链表,当链表长度大于8时转为红黑树

       ■ 保证数据安全的方式是对链表或者红黑树头节点加锁,配合悲观锁保证多线程操作集合时的安全性

CountDownLatch类

Semaphore类

目录
相关文章
|
1月前
|
并行计算 Java 数据处理
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
SpringBoot高级并发实践:自定义线程池与@Async异步调用深度解析
169 0
|
27天前
|
安全
List并发线程安全问题
【10月更文挑战第21天】`List` 并发线程安全问题是多线程编程中一个非常重要的问题,需要我们认真对待和处理。只有通过不断地学习和实践,我们才能更好地掌握多线程编程的技巧和方法,提高程序的性能和稳定性。
131 59
|
6天前
|
安全 Java 开发者
Java 多线程并发控制:深入理解与实战应用
《Java多线程并发控制:深入理解与实战应用》一书详细解析了Java多线程编程的核心概念、并发控制技术及其实战技巧,适合Java开发者深入学习和实践参考。
|
18天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####
|
2月前
|
数据采集 负载均衡 安全
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
本文提供了多个多线程编程问题的解决方案,包括设计有限阻塞队列、多线程网页爬虫、红绿灯路口等,每个问题都给出了至少一种实现方法,涵盖了互斥锁、条件变量、信号量等线程同步机制的使用。
LeetCode刷题 多线程编程九则 | 1188. 设计有限阻塞队列 1242. 多线程网页爬虫 1279. 红绿灯路口
|
1月前
|
Java
【编程进阶知识】揭秘Java多线程:并发与顺序编程的奥秘
本文介绍了Java多线程编程的基础,通过对比顺序执行和并发执行的方式,展示了如何使用`run`方法和`start`方法来控制线程的执行模式。文章通过具体示例详细解析了两者的异同及应用场景,帮助读者更好地理解和运用多线程技术。
29 1
|
1月前
|
消息中间件 NoSQL 关系型数据库
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
【多线程-从零开始-捌】阻塞队列,消费者生产者模型
25 0
|
2月前
|
数据采集 消息中间件 并行计算
进程、线程与协程:并发执行的三种重要概念与应用
进程、线程与协程:并发执行的三种重要概念与应用
60 0
|
1月前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
48 1
C++ 多线程之初识多线程
|
29天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
19 3