主要关注concurrent atomic locks function包
并发和并行是不一样的 并发主要目的是为了充分地利用单核CPU的资源 而并行是在多核CPU下的概念
java线程状态 :new runnable blocked waiting(等待) time_waiting(延迟等待) terminated
waiting:不占有锁 不占有cpu资源
sleep:占有锁 不占有cpu资源
blocking:占有锁 不占有cpu资源(sleep也就是进入blocking状态 io阻塞)
lock锁
关于lock和syncronsized的归纳这里我之前也有 下面主要写一些使用方法:
https://blog.csdn.net/weixin_40695328/article/details/104069459
(1)retranlock:
需要手动lock unlock
trylock尝试获取锁 获取不到就结束等待 可以用来解决synchronized阻塞时候死锁的问题
(2)虚假唤醒问题: 用where不要用if
(3) synchronized用wait notify
lock 用condition对象的 await signal
condition的好处就是可以创建同一个lock对象锁下的多个condition 来完成精确的唤醒!
LIst:
大家都知道arraylist线程不安全,容易出ConcurrentModificationException:迭代的时候同时对其进行修改
(1)vector是线程安全 但是效率低
(2)可以用Collections.syncronsizedList(new ArrayList()) 效率一样低:
根据是否实现RandomAccess这个接口来返回的SynchronizedRandomAccessList还是SynchronizedList. 而因为arraylist实现了 SynchronizedList 所以用的是SynchronizedList :基本上也是暴力加锁
(3)最好用 copyonwriteArrayLIst 效率高
用指针完成写入时复制 来提高效率
同理可以用 copyonwriteSet concurrentHashmap(注意根据实际的业务设置初始值)
callable
跟其他两个runnable thread的区别:有返回值 会抛异常
需要使用FutureTask(Callable) 实现了RunnableRutue继承了Runnable (桥接模式) 而FutureTask就是适配类
会有结果缓存! (开多个线程执行同一段代码 后面的线程不会再执行)
获取值的方法会阻塞 等待结果返回后后才执行
讲一些辅助类
(1)减法计数器countDownLAtch(初始值)
countDownLAtch.countDown 每一次减少一个值
在达到初始值设置的后 就可以继续执行 await处的代码 否则await处是阻塞的
(2)加法计数器CyclicBarrier(初始值,达到初始值后开始运行的线程runnable)
调用await方法 线程等待 等到等待的数量达到了初始值 设置的runnable才会继续进行 相对应的其他await线程结束
(3)semaphone信号灯(初始值):初始值代表了一次性能进入acquire()方法的线程有多少:控制同一时刻能有多少线程进入acquire方法执行
读写锁
ReentranreadWirteLock进行读写分离
写锁:调用witreLock。lock 记得unlock释放
读锁:readlLOck.lock 同样记得释放
读写分离 写入只能单线程 读取可以并发
同步队列
synchronousQueue 同步队列
队列是空的 不会存储元素 每次put操作 要等待下一个take 否则无法继续添加元素
比ArrayBlockingQueue轻量 可以用在线程间传递单一资源使用
阻塞队列
(1)加入满的队列会阻塞 超过初始值抛出IllegalStateException
(2)取出空的队列会阻塞 没内容再取抛出nosuchelementException
BlockingQueue---ArrayBlockingQueue(初始值) linkedBlockingQueue
add remove会抛出异常 element检测队首的元素
offer(元素,时间,TimeUNit枚举类属性) poll(元素,时间) 不会抛出异常会返回特殊值 一直等待直到超时推出 插入不进去返回false peek检测队首元素没有值返回null
put take 一直阻塞
线程池
Executors 可以管理线程的总数
----new\FIxedThreadPool 固定数量 execute方法放入runnable线程进行执行 注意关闭线程 同时只有初始值的线程一起执行
----newCachedThreadPool 可以动态弹性增减
----newsingleThreadExecutor 只有一个
----newScheduledThreadPool 定长线程池 支持定时和周期性执行任务
底层都是调用ThreadPoolExecutor 只是参数的不同 项目中要使用这个类去自定义创建 不能用executors 因为允许的队列长度是Integer.MaxValue 一定会oom
corePoolsieze 核心池的大小
maximunpoolsize 核心池最大有多少:只有在队列已满的情况下才会使用到maximunpoolsize - corePoolsieze的线程 因为线程池是优先使用队列的 如果创建队列的时候不指定初始值而是无限大 就会导致maximunpoolsize无法使用 全部都是corePoolsieze来处理 直到队列满了后maximunpoolsize才会生效使用
keepalivetime 空闲线程的存活时间:指的是maximunpoolsize - corePoolsieze的线程
timeunit时间单位
workqueue队列
threadFactory 创建线程使用 一般不必传参
rejectexecutionhandler 拒绝策略:超过maximunpoolsize+workqueue队列长度的线程怎么处理的问题
----abortpolicy :默认 队列满了就丢弃任务抛出异常
---callerRUnspolicy: 哪个线程调用的返回给哪个线程 例如是主线程开启的 就返回给主线程来执行
----discardOldestPolicy:尝试进入队列 方法是尝试删除最早进入队列的任务 不一定成功
----discardPolicy:队列满了也会丢弃 不抛出异常
怎么设置maximunpoolsize:可以根据不同的任务类型来设置线程池 最好准备两个版本
(1)根据Runtime.getRuntime().availableprocessors判断cpu核数 来创建并发数量 ---- cpu密集型
(2)磁盘读写 io线程数比较多的时候 应该设置最够多的线程数量 不让cpu空闲下来 目的还是为了提高cpu使用效率 ----io密集型
四大函数是接口:只包含一个抽象方法的接口
(1)Function apply 传入T返回R
(2)Predicate test 传入T返回boolean
(3)Consumer accept 传入T没有返回值
(4)Supplier get 没有参数传入返回T
Stream流式计算
主要关注 stream filter map sort limit
stream并行流效率很块! 底层也是Forkjoin
分支合并:
Forkjoin:将一个比较大型的任务切分成小块 再将小块的结果合并
工作窃取:利用双端队列 A任务完成后 比B快的话 会从B的尾巴拿取任务继续执行 而Forkjoin的意义就在于这里 可以而不像Future调用get后就会阻塞 加大效率和线程的利用率
类:通过ForkJoinPool workqueue是其内部类 也就是队列,并且存在于每一个线程(ForkJoinThread)当中
ForkJoinTask是正在ForkJoinPool中运行的任务 一般使用(recursionTask有返回值 recursionAction没返回值 使用方式继承即可)
---fork 创建子任务 在递归方法调用后调用相应的fork方法 在返回的时候返回join
---join 任务完成后获取计算结果 在递归方法返回的时候调用
----invoke 开始执行 计算没有完毕就等待 ForkJoinPool的方法 传入ForkJoinTask接口
使用于cpu密集型而不是io密集型
异步回调
completableFuture
runAsync方法执行没有结果 supplyAsync有返回结果 get回取结果 whenComplete正常编译完成 exceptionally异常情况
主要好处就是发送任务后直到获取返回值前不阻塞
JMM
jmm主要跟volitaile 和 主内存工作线程有关
volitaile 轻量级同步机制
(1)保证可见性质
(2)不保证原子性 可以用atomic类来完成(轻量级 通过自旋+cas完成 不进行内核态操作 但是因为自旋所以适合在低并发场景使用)
(3)**禁止指令重排序**
--利用内存屏障 保证特定变量的线程安全和内存可见性
而synchronized只能保证块与块之间的顺序 无法保证锁的代码块内部非原子操作的指令重排序
单例模式
主要关注三个问题
代码层面怎么防止多例
序列化反序列化
反射
(1)饿汉式 线程安全
(2)懒汉式 线程不安全 一般使用双重if判断 即使如此 同步代码块内的创建对象的方法也有指令重排序的问题
指令重拍:分配内存 构造方法创建对象 栈区引用指向实际的内存对象 可能先第三步 导致返回对象为null 所以懒汉式建议加volitaile
需要在private构造器中再加一个判断防止反射后再调用构造器问题 但是还是不能避免反射的问题
(3)静态内部类
(4)以上并不能禁止反射导致的问题!! 最好的就是用枚举
枚举的好处:
没有无餐构造器!!!通过反编译可以知道
枚举序列化只是序列化了name属性 可以防止序列化反序列化导致的单例破坏问题 并且编译器是不允许任何对这种序列化机制的定制的
枚举无法通过反射进行创建 因为在newInstance方法里面写明了针对枚举类型的逻辑 直接抛出异常(调用的是两个参数的构造器)
CAS
atomac中:
原子性 内存级别的 所以效率高
CAS的核心类: Unsafe java可以用它来直接操作内存
cas是cpu的并发源语:
首先获得传入的对象offset位置的值 然后在获取新值跟这个值是否一样 一样就做你的数据操作
比较工作内存中的值和驻内存的值 相同执行操作 否则一直循环比较到值一致为止
缺点: 自旋循环时间长 开销大
只能保证一个共享变量的原子操作
ABA问题!!:取出交换数据的期间可能被变动了 只是最终的结果一致
解决ABA:
原子引用atomicStampedReference 用版本号 来控制(乐观锁)
atomicReference引申:
自旋锁 底层原理是CAS 利用atomicReference的compareAndSet方法判断是否是null并且可以获得锁 可以的话把相应的线程赋值 意味着我这个线程已经拿到了这个锁 解锁相反判断是否是我这个线程 是就置为空 其他线程的调用过程同理
死锁解决
jps -l拿到进程号
jstack 进程哈 分析指定线程的堆栈信息