一.原子类

JUC中提供了针对一个变量的修改的简单原子操作,提供了原子类,相对于我们自己采用锁的方式实现来说,原子类的性能更好。

1.1原子类的底层实现原理理论:volatile+(循环的CAS)

CAS大致流程:每次修改的时候都会拿着之前读到的内容当做期望值,和当前值进行比较,如果说两者相等,那么可以将修改后的值进行赋值。

分析:
首先通过volatile保证可见性,也就是每次修改之后对于其他线程是可见的。

但是我又想到之前学的内容,volatile虽然可以保证可见性,但是存在竞态条件,那么肯定这个CompareAndSet是原子操作的。那么这个CAS是怎么保证原子性的,随便找一个原子类,看内部的源码,都是调用Unsafe类的方法,其内部提供的方法又是native的,网上看了一些资料,需要翻到jdk的源码。


大致能看懂,LOCK_IF_MP(mp) cmpxchg… 也就是多处理器的时候需要使用CPU的lock指令,实现原子性的,所以说是无锁应该是针对java层来说的,但是底层由CPU直接lock指令的性能比java层来触发要好的多,单处理器(单线程的)由于没有同一时刻的多线程,通过可见性以及cmpxchg就可以保证线程的安全性。

1.2 原子化的基本数据类型

JUC提供的原子类,分为五个类别:原子化的基本数据类型原子化的对象引用类型原子化数组原子化对象属性更新器原子化的累加器。图片来源

原子化的基本数据类型:AtomicBoolean、AtomicInteger 和 AtomicLong
getAndIncrement() //原子化i++
getAndDecrement() //原子化的i--
incrementAndGet() //原子化的++i
decrementAndGet() //原子化的--i
//当前值+=delta,返回+=前的值
getAndAdd(delta)
//当前值+=delta,返回+=后的值
addAndGet(delta)
//CAS操作,返回是否成功
compareAndSet(expect, update)
//以下四个方法
//新值可以通过传入func函数来计算
getAndUpdate(func)
updateAndGet(func)
getAndAccumulate(x,func)
accumulateAndGet(x,func)
原子化的对象引用类型:AtomicReference、AtomicStampedReference 和 AtomicMarkableReference

相对于上面的原子化的基本数据类型引用数据类型存在ABA的问题,解决ABA的问题方式通过添加自增的版本号,那么A->B->A的过程通过版本号进行记录。

AtomicStampedReference方法:


boolean compareAndSet(V expectedReference,//期望值V newReference,//更新值int expectedStamp,//期望的版本号int newStamp) // 新的版本号

AtomicMarkableReference 的实现机制则更简单,将版本号简化成了一个 Boolean 值,方法签名如下:


boolean compareAndSet(V expectedReference,V newReference,boolean expectedMark,boolean newMark)
原子化数组:AtomicIntegerArray、AtomicLongArray 和 AtomicReferenceArray

原子化地更新数组里面的每一个元素。这些类提供的方法和原子化的基本数据类型的区别仅仅是:每个方法多了一个数组的索引参数

原子化对象属性更新器:AtomicIntegerFieldUpdater、AtomicLongFieldUpdater 和 AtomicReferenceFieldUpdater

创建更新器的方法:


public static <U> AtomicXXXFieldUpdater<U> newUpdater(Class<U> tclass,String fieldName)

需要注意的是,对象属性必须是 volatile 类型的,只有这样才能保证可见性,如果对象属性不是 volatile 类型的,newUpdater() 方法会抛出 IllegalArgumentException 这个运行时异常。

原子化的累加器:DoubleAccumulator、DoubleAdder、LongAccumulator 和 LongAdder

累加器仅支持累加的操作,相对于原子化的基本数据类型,累加的操作速度更快。

二.线程池

2.1线程池的基础工作原理

线程对象在JAVA中是一个重量级对象,因为java中的线程不仅仅是在堆上申请一个空间,需要调用操作系统的API,然后操作系统还需要为线程分配一系列的资源,所以在开发的过程中我们要避免频繁的创建线程和销毁线程,合理的使用资源,所以采用池化技术。

但是线程池的池化技术,和我们不同的池化技术不一样,普通的池化模型对外提供acquire,release方法,但是如果线程池按照这个模型去封装的话,对于使用方很不友好,如果达到最大线程数怎么办,等待的任务如何存放等等。最好是在进一步的封装,把线程的的获取和释放也封装进去,对外暴露统一的入口(放任务)。类似于生产者-消费者模型,调用方为生产者,线程池为消费者。

简化版的线程池实现代码:便于理解

代码来源


//简化的线程池,仅用来说明工作原理
class MyThreadPool{//利用阻塞队列实现生产者-消费者模式BlockingQueue<Runnable> workQueue;//保存内部工作线程List<WorkerThread> threads = new ArrayList<>();// 构造方法MyThreadPool(int poolSize, BlockingQueue<Runnable> workQueue){this.workQueue = workQueue;// 创建工作线程for(int idx=0; idx<poolSize; idx++){WorkerThread work = new WorkerThread();work.start();threads.add(work);}}// 提交任务void execute(Runnable command){workQueue.put(command);}// 工作线程负责消费任务,并执行任务class WorkerThread extends Thread{public void run() {//循环取任务并执行while(true){ ①Runnable task = workQueue.take();task.run();} }}
}/** 下面是使用示例 **/
// 创建有界阻塞队列
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2);
// 创建线程池
MyThreadPool pool = new MyThreadPool(10, workQueue);
// 提交任务
pool.execute(()->{System.out.println("hello");
});

2.2 ThreadPoolExecutor

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)
  • corePoolSize:表示线程池保有的最小线程数。有些项目很闲,但是也不能把人都撤了,至少要留 corePoolSize 个人坚守阵地。
  • maximumPoolSize:表示线程池创建的最大线程数。当项目很忙时,就需要加人,但是也不能无限制地加,最多就加到 maximumPoolSize 个人。当项目闲下来时,就要撤人了,最多能撤到 corePoolSize 个人。
  • keepAliveTime & unit:上面提到项目根据忙闲来增减人员,那在编程世界里,如何定义忙和闲呢?很简单,一个线程如果在一段时间内,都没有执行任务,说明很闲,keepAliveTime 和 unit 就是用来定义这个“一段时间”的参数。也就是说,如果一个线程空闲了keepAliveTime & unit这么久,而且线程池的线程数大于 corePoolSize ,那么这个空闲的线程就要被回收了。
  • workQueue:工作队列,和上面示例代码的工作队列同义。
  • threadFactory:通过这个参数你可以自定义如何创建线程,例如你可以给线程指定一个有意义的名字。
  • handler:通过这个参数你可以自定义任务的拒绝策略。如果线程池中所有的线程都在忙碌,并且工作队列也满了(前提是工作队列是有界队列),那么此时提交任务,线程池就会拒绝接收。至于拒绝的策略,你可以通过 handler 这个参数来指定。

ThreadPoolExecutor 已经提供了以下 4 种策略。

  • CallerRunsPolicy:提交任务的线程自己去执行该任务。
  • AbortPolicy:默认的拒绝策略,会 throws RejectedExecutionException。
  • DiscardPolicy:直接丢弃任务,没有任何异常抛出。
  • DiscardOldestPolicy:丢弃最老的任务,其实就是把最早进入工作队列的任务丢弃,然后把新任务加入到工作队列。

一般都需要我们手动的设置threadFactory,定义业务线程名,方便定位问题,和实现handler拒绝策略(需要保存任务,可以存储到redis或者是消息中间件中)

之前的笔记中记录了合理线程数设置的公式:

     核数  X (1+io耗时/cpu耗时)

2.3JDK中提供的直接使用的四种线程池

1.SingleThreadPool:只有一个线程的线程池
2.FixedThreadPool:固定数量的线程池
3.CachePool:有弹性的线程池,来一个任务启动一个,只要没有闲的就会启动新的
4.ScheduledPool:定时任务来执行的线程池

四个线程池底层就是:ThreadPoolExcutor,只不过是参数不一样

我们在使用线程池还是根据业务自定义。

并发笔记(八)JUC原子类以及线程池(Executors)相关推荐

  1. 【Java_多线程并发编程】JUC原子类——4种原子类

    根据修改的数据类型,可以将JUC包中的原子操作类可以分为4种,分别是: 1. 基本类型: AtomicInteger, AtomicLong, AtomicBoolean ; 2. 数组类型: Ato ...

  2. Java多线程系列--“JUC原子类”01之 框架

    2019独角兽企业重金招聘Python工程师标准>>> Java多线程系列--"JUC原子类"01之 框架 根据修改的数据类型,可以将JUC包中的原子操作类可以分 ...

  3. Java多线程系列--“JUC原子类”03之 AtomicLongArray原子类

    概要 AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似.本章以AtomicLongArray对数 ...

  4. 【JUC】第五章 JUC 阻塞队列、线程池

    第五章 JUC 阻塞队列.线程池 文章目录 第五章 JUC 阻塞队列.线程池 一.阻塞队列 1.简介 2.BlockingQueue 的方法 3.常见的 BlockingQueue 二.线程池 1.简 ...

  5. JUC源码分析-线程池篇(五):ForkJoinPool - 2

    通过上一篇(JUC源码分析-线程池篇(四):ForkJoinPool - 1)的讲解,相信同学们对 ForkJoinPool 已经有了一个大概的认识,本篇我们将通过分析源码的方式来深入了解 ForkJ ...

  6. java 原子数据类型_java并发编程(十一)----(JUC原子类)基本类型介绍

    上一节我们说到了基本原子类的简单介绍,这一节我们先来看一下基本类型: AtomicInteger, AtomicLong, AtomicBoolean.AtomicInteger和AtomicLong ...

  7. Java多线程学习二十八:原子类和 volatile 有什么异同?

    原子类和 volatile 有什么异同 案例说明 volatile 和原子类的异同 我们首先看一个案例.如图所示,我们有两个线程. 在图中左上角可以看出,有一个公共的 boolean flag 标记位 ...

  8. java多线程系类:JUC原子类:04之AtomicReference原子类

    概要 本章对AtomicReference引用类型的原子类进行介绍.内容包括: AtomicReference介绍和函数列表 AtomicReference源码分析(基于JDK1.7.0_40) At ...

  9. JUC原子类-数组类型(三)

    AtomicLongArray介绍: AtomicIntegerArray, AtomicLongArray, AtomicReferenceArray这3个数组类型的原子类的原理和用法相似.本章以A ...

最新文章

  1. LogBack配置详解(一)
  2. PLSQL 查询结果只显示年月日不显示时分秒的解决方法
  3. CSS Grid 网格布局教程
  4. 设计师所需图标素材网站,不用到处找了,都在这!
  5. java getconstructors,java 反射 constructors的问题,真心需要解决
  6. 知名视频编辑工具:达芬奇剪辑调色软件 DaVinci Resolve Studio Mac v17.3.1
  7. 如何使用 JavaScript 读取文件
  8. 良心安利东方 rpg游戏制作大师素材网站
  9. Linux命令:查看服务器IP地址
  10. 关于王小云破解MD5
  11. ImportError: cannot import name ‘PILLOW_VERSION‘ from ‘PIL‘ (/home/user8/anaconda3/envs/FCOS/lib/pyt
  12. sqlserver数据库快照和mysql_解析SQLServer视图、数据库快照_MySQL
  13. 如何将拉勾网(智联招聘)的预览简历导出来
  14. 2021年我国热泵市场规模、产值及进出口分析[图]
  15. depot_tools在windows上用遇到的问题和RTC编译出错
  16. 如何停止胡思乱想,保持专注?
  17. 游戏引擎与游戏引擎开发入门
  18. 多项式计算大模拟:csp202112-3登机牌条码
  19. 成立了一个软件工作室,但是如何才能接到项目呢?
  20. Mac 解决向日葵被别人远程控制无法操作的问题

热门文章

  1. Ogre 3D程序设计 Ogre材质1
  2. c语言数组找100以内的素数,使用c语言判断100以内素数的示例(c语言求素数)
  3. ie和chrom等主流主要区别
  4. 数据就是生产力,四大引擎打造卓越流程管理系统
  5. 诗词在线网络月刊2010年第二期(总第十四期)
  6. SpringSecurity图片验证码java
  7. Linux网管学习笔记(26)Linux操作系统学习路线图
  8. 使用socket的阻塞简单通信聊天工具
  9. react-beautiful-dnd实现拖拽排序+合并+拆分的功能(完整代码)
  10. FORK客户端使用教程