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

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

一、阻塞队列

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月前
|
安全 数据库连接 数据库
连接池的并发和线程安全
连接池的并发和线程安全
|
2月前
|
Java 调度 开发者
JDK 21中的虚拟线程:轻量级并发的新篇章
本文深入探讨了JDK 21中引入的虚拟线程(Virtual Threads)概念,分析了其背后的设计哲学,以及与传统线程模型的区别。文章还将讨论虚拟线程如何简化并发编程,提高资源利用率,并展示了一些使用虚拟线程进行开发的示例。
|
1月前
|
存储 安全 Java
并发编程知识点(volatile、JMM、锁、CAS、阻塞队列、线程池、死锁)
并发编程知识点(volatile、JMM、锁、CAS、阻塞队列、线程池、死锁)
71 3
|
6天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。
|
8天前
|
Java 开发者
Java中多线程并发控制的实现与优化
【4月更文挑战第17天】 在现代软件开发中,多线程编程已成为提升应用性能和响应能力的关键手段。特别是在Java语言中,由于其平台无关性和强大的运行时环境,多线程技术的应用尤为广泛。本文将深入探讨Java多线程的并发控制机制,包括基本的同步方法、死锁问题以及高级并发工具如java.util.concurrent包的使用。通过分析多线程环境下的竞态条件、资源争夺和线程协调问题,我们提出了一系列实现和优化策略,旨在帮助开发者构建更加健壮、高效的多线程应用。
7 0
|
11天前
|
Java API 调度
安卓多线程和并发处理:提高应用效率
【4月更文挑战第13天】本文探讨了安卓应用中多线程和并发处理的优化方法,包括使用Thread、AsyncTask、Loader、IntentService、JobScheduler、WorkManager以及线程池。此外,还介绍了RxJava和Kotlin协程作为异步编程工具。理解并恰当运用这些技术能提升应用效率,避免UI卡顿,确保良好用户体验。随着安卓技术发展,更高级的异步处理工具将助力开发者构建高性能应用。
|
24天前
|
安全 Java
Java中的多线程并发控制
在Java中,多线程是实现并发执行任务的一种重要方式。然而,随着多个线程同时访问共享资源,可能会导致数据不一致和其他并发问题。因此,了解并掌握Java中的多线程并发控制机制显得尤为重要。本文将深入探讨Java的多线程并发控制,包括synchronized关键字、Lock接口、Semaphore类以及CountDownLatch类等,并通过实例代码演示其使用方法和注意事项。
12 2
|
25天前
|
存储 安全 Java
Java线程池ThreadPoolExcutor源码解读详解08-阻塞队列之LinkedBlockingDeque
**摘要:** 本文分析了Java中的LinkedBlockingDeque,它是一个基于链表实现的双端阻塞队列,具有并发安全性。LinkedBlockingDeque可以作为有界队列使用,容量由构造函数指定,默认为Integer.MAX_VALUE。队列操作包括在头部和尾部的插入与删除,这些操作由锁和Condition来保证线程安全。例如,`linkFirst()`和`linkLast()`用于在队首和队尾插入元素,而`unlinkFirst()`和`unlinkLast()`则用于删除首尾元素。队列的插入和删除方法根据队列是否满或空,可能会阻塞或唤醒等待的线程,这些操作通过`notFul
44 5
|
25天前
|
存储 安全 Java
Java线程池ThreadPoolExcutor源码解读详解07-阻塞队列之LinkedTransferQueue
`LinkedTransferQueue`是一个基于链表结构的无界并发队列,实现了`TransferQueue`接口,它使用预占模式来协调生产者和消费者的交互。队列中的元素分为数据节点(isData为true)和请求节点(isData为false)。在不同情况下,队列提供四种操作模式:NOW(立即返回,不阻塞),ASYNC(异步,不阻塞,但后续线程可能阻塞),SYNC(同步,阻塞直到匹配),TIMED(超时等待,可能返回)。 `xfer`方法是队列的核心,它处理元素的转移过程。方法内部通过循环和CAS(Compare And Swap)操作来确保线程安全,同时避免锁的使用以提高性能。当找到匹
35 5
|
25天前
|
存储 安全 Java
Java线程池ThreadPoolExcutor源码解读详解04-阻塞队列之PriorityBlockingQueue原理及扩容机制详解
1. **继承实现图关系**: - `PriorityBlockingQueue`实现了`BlockingQueue`接口,提供了线程安全的队列操作。 - 内部基于优先级堆(小顶堆或大顶堆)的数据结构实现,可以保证元素按照优先级顺序出队。 2. **底层数据存储结构**: - 默认容量是11,存储数据的数组会在需要时动态扩容。 - 数组长度总是2的幂,以满足堆的性质。 3. **构造器**: - 无参构造器创建一个默认容量的队列,元素需要实现`Comparable`接口。 - 指定容量构造器允许设置初始容量,但不指定排序规则。 - 可指定容量和比较
42 2