以前写的java多线程知识
JMM(java内存模型)
可见性
原子性
有序性
volatile关键字
保证可见性
禁止指令重排
处理器在进行重排序时必须要考虑指令间的数据依赖性
多线程由于交替执行,由于编译器优化重排的存在,两个线程使用的变量保持一致性是无法确定的,结果无法预测,通过加内存屏障实现。
内存屏障的作用
- 阻止屏障两侧的指令重排序;
- 强制把写缓冲区/高速缓存中的脏数据等写回主内存,让缓存中相应的数据失效。
对于Load Barrier来说,在指令前插入Load Barrier,可以让高速缓存中的数据失效,强制从新从主内存加载数据;
对于Store Barrier来说,在指令后插入Store Barrier,能让写入缓存中的最新数据更新写入主内存,让其他线程可见。
不保证原子性(需要使用AtomicInteger 原子类)
创建对象的过程
instance = new Singleton(); 分成下面3步 memory = allocate(); // 1 分配对象内存空间 instance(memory); // 2初始化对象 instance = memory; // 3 设置instance指向刚分配的内存地址,此时instance != null
Calllable**接口
使用 FutureTask<> 进行连接 Runnable接口与Callable接口
多个线程争抢FutureTask,只算一次FutureTask
CAS(比较交换
CAS是一条CPU并发原语
判断内存某个位置值是否为预期值,如果是,则更改为新的值
AtomicInteger 类采用CAS实现
getAndInt(Object var1, long var2, int var4)
var2:valueoffset(内存地址偏移量),先用var1与var2得到一个缓存值var5,然后将var1对象里的值与缓存值比较,如果一样,则更新为var4。
CAS底层原理----自旋锁
Unsafe----CAS的核心类,(native) 可以直接操作特定内存的数据,内部方法像C的指针一样操控内存
变量valueOffset,表示该变量在内存中的偏移地址,Unsafe类根据内存偏移地址获取数据
变量value用volatile修饰,保证多线程之间的内存可见性。
ABA问题
1 当值不变时,有可能过程中改变了多次,
原子引用类,解决ABA问题
AtomicReference()
集合类
ArrayList
ConcurrentModificationException 并发修改异常
解决方案
1 可以用Vector代替
2 使用Collections.synchronizedList()
3 CopyOnWriteArrayList()
写时复制,在每次添加的时候,使用ReentrantLock进行加 锁,然后复制整个列表
ArraySet
在JUC里,有CopyOnWriteSet()实现,继承了AbstractSetj类,底层是CopyOnWriteArrayList()
HashMap
在JUC里,有一个包:ConcurrentHashMap()类实现,通过加锁
锁
公平锁 非公平锁
公平锁:多个线程按照申请锁的顺序来获取锁,类似排队
非公平锁:多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程先获取锁,在高并发情况下,会造成优先级反转以及饥饿现象,非公平锁吞吐量较大。
ReentrantLock可以指定构造函数来得到公平锁或非公平锁,默认是非公平锁
Synchronized也是一种非公平锁
可重入锁(递归锁)
为了解决线程死锁问题
同一线程外层函数获得锁之后,内层递归函数仍能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方***自动获取锁
ReentrantLock/Synchronized就是可重入锁
自旋锁
尝试获取锁的线程不会立即阻塞,而实采用循环的方式获取锁,能够减小线程上下文的切换,缺点是循环会消耗CPU
读写锁
ReadWriteLock()
CountDownLatch/CyclicBarrier/Semaphore
CountDownLatch
一个线程等待其他线程执行完毕后才执行,例如 秦灭六国,等到六国全被灭之后,才能统一,主线程使用await()方法等待。
CyclicBarrier
其他线程来了之后,要同时等待所有线程到了才继续执行,在线程中使用await()方法等待
Semaphore(信号量)
两个目的,1 用于多个共享资源的互斥使用,2 用于并发线程数的控制
阻塞队列
ArrayBlockingQueue
基于数组结构的有界阻塞队列,此队列按FIFO(先进先出原则)对元素进行排序
LinkedBlockingQueue
基于链表结构的阻塞队列,按FIFO排序元素,吞吐量比ArrayBlockingQueue要高
SynchronousQueue
一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态
Synchroonized 与Lock区别
1 原始构成:
Synchronized是关键字,属于jvm层面,底层通过monitor对象来完成,wait/notify也依赖monitor对象,
Lock类是具体类,是api层面的锁
2 使用方法
Synchronized方法不需要用户手动释放,当synchronized代码执行完后,系统自动让线程释放对锁的占用
3 等待是否可中断
synchronized不可中断,除非抛出异常或正常执行完成
ReentrantLock可中断,1 设置超时方法tryLock() 2 LockInterruptibly()放代码块中,调用interrupt()方法可中断
4 加锁是否公平
synchronized 是非公平锁
ReentrantLock可设置公平与非公平
5 condition
synchronized没有
ReentrantLock实行分组放行
生产者消费者模式
通过synchronized、wait、notify来实现
通过Lock、condition(await、signal)实现
通过BlockingQueue实现
线程池
优势
1 降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗
2 提高响应速度,当任务到达,不需要等待线程创建就能立即执行
3 提高线程的可管理性,无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
分类
1 Executors.newFixedThreadPool():执行长期的任务,性能好很多
2 Executors.newSingleThreadExecutor():一个任务一个任务执行的场景
3 Executors.newCachedThreadPool():执行很多短期异步的小程序或者负载较轻的任务。
底层实现
几种线程池的顶层实现为ThreadPoolExecutor()
七大参数
corePoolSize
核心线程数
maximumPoolSize
线程池能够容纳的同时执行的最大线程数
keepAliveTime
多余的空闲线程的存活时间,当前线程数量超过corePoolSize时,空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程
unit
keepAliveTime 单位
workQueue
阻塞队列
threadFactory
线程工厂
handler
拒绝策略
1 AbortPolicy(默认) 直接抛出RejectedExecutionException异常组织系统正常运行
2 CallerRunsPolicy : ”调用者运行“一种调节机制,该策略不会抛弃任务,也不会抛出异常
3 DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
4 DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常
如何配置线程池参数
根据:CPU密集型、IO密集型
CPU密集型任务配置尽可能少的线程数量:CPU核数 + 1
IO密集型:1 cpu 核数 * 2 ; 2 CPU核数 / 1 - 阻塞系数, 阻塞系数在0.8-0.9之间
死锁
解决
jps 命令定位进程号
jstack找到死锁查看