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

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

一、阻塞队列

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类

目录
相关文章
|
18天前
|
数据采集 存储 Java
高德地图爬虫实践:Java多线程并发处理策略
高德地图爬虫实践:Java多线程并发处理策略
|
4天前
|
设计模式 消息中间件 安全
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
【Java多线程】关于多线程的一些案例 —— 单例模式中的饿汉模式和懒汉模式以及阻塞队列
10 0
|
5天前
|
安全 C++
C++多线程编程:并发与同步
C++多线程编程:并发与同步
8 0
|
7天前
|
安全 Java
Java中的并发编程:理解并发性与线程安全
Java作为一种广泛应用的编程语言,在并发编程方面具有显著的优势和特点。本文将探讨Java中的并发编程概念,重点关注并发性与线程安全,并提供一些实用的技巧和建议,帮助开发人员更好地理解和应用Java中的并发机制。
|
13天前
|
算法 安全
AtomicInteger使用非阻塞算法,实现并发控制多线程实现售票
AtomicInteger使用非阻塞算法,实现并发控制多线程实现售票
|
13天前
|
存储 安全 Java
【亮剑】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能
【4月更文挑战第30天】`ConcurrentHashMap`是Java中线程安全的哈希表,采用锁定分离技术提高并发性能。数据被分割成多个Segment,每个拥有独立锁,允许多线程并发访问不同Segment。当写操作发生时,计算键的哈希值定位Segment并获取其锁;读操作通常无需锁定。内部会根据负载动态调整Segment,减少锁竞争。虽然使用不公平锁,但Java 8及以上版本提供了公平锁选项。理解其工作原理对开发高性能并发应用至关重要。
|
14天前
|
监控 安全 Java
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
【多线程学习】深入探究阻塞队列与生产者消费者模型和线程池常见面试题
|
19天前
|
设计模式 Java 编译器
深入理解Java中的多线程并发控制
Java作为一种流行的编程语言,其多线程并发控制机制一直是开发者关注的焦点。本文旨在通过探讨Java中的多线程并发控制原理、常用同步工具及设计模式,帮助读者深入理解并有效应用多线程并发控制技术,以提高程序性能和稳定性。
|
19天前
|
监控 安全 Java
一文讲明白Java中线程与进程、并发与并行、同步与异步
一文讲明白Java中线程与进程、并发与并行、同步与异步
8 1
|
24天前
|
安全 Java
深入理解 Java 多线程和并发工具类
【4月更文挑战第19天】本文探讨了Java多线程和并发工具类在实现高性能应用程序中的关键作用。通过继承`Thread`或实现`Runnable`创建线程,利用`Executors`管理线程池,以及使用`Semaphore`、`CountDownLatch`和`CyclicBarrier`进行线程同步。保证线程安全、实现线程协作和性能调优(如设置线程池大小、避免不必要同步)是重要环节。理解并恰当运用这些工具能提升程序效率和可靠性。