进程是一个服务 也就是一个程序的动态表现 线程是进程中的最小执行单元
从Thread类继承
实现 Runnable接口
sleep : Thread#sleep() 不会释放锁 但是 Object#wait()会释放锁 睡眠结束完后 也会回到就绪队列
yeild : 本线程进入等待队列 , 让出一下CPU
join : 让本线程 跑到被调用者线程中去运行 且要在调用线程执行完后 运行
线程中断获取响应
同步关键字
hospot 实现 : 锁的应该是对象头 一个对象头有32/64位具体看JVM的位数 其中两位 用于标志锁
锁升级 : 无锁 - > 偏向锁 - > 轻量级锁(自旋锁 默认旋10次 如果还得不到锁 就升级) -> 重量级锁(操作系统锁 进入等待队列)
什么时候用自旋锁 ? 什么时候用重量级锁 ?
线程数量少 并且锁定的业务代码运行时间短 就用自旋锁
线程数多 并且等待时间长的 就用重量级锁
可重入
程序执行出现异常时 默认情况下 锁是会被释放的
同步方法和非同步方法 是可以同时执行的
当做锁的对象一定要加final 并且不能用String作为锁对象
一致性 , 可见性 ( 保证线程可见性 )
底层使用MESI(CPU缓存一致性协议)
保证Jvm指令不会被重排序( 禁止指令重排序 )
底层使用cpu读写屏障来完成的 loadFence storeFence (CPU原语)
volatile修饰引用对象其实是没有什么意义的 引用对象内的数据改变了 其他线程是没法看见的
L1 速度非常快 存的数据也很少 本人电脑中 只有256k
L2 速度慢于L1 本人电脑中 只有1M L1 和L2 是CPU独享的 在CPU内部
L3 在主板上 CPU共享 在本人电脑中 只有6M
为了提高效率 读取数据的时候 会一次性读取缓存行大小的数据 一般为 64字节
同一缓存行的两个不同数据, 被两个不同的CPU锁定 产生互相影响的伪共享问题
各种Atomic类就是使用了CAS保证线程安全的
ABA问题 如果是基础数据类型无所谓 但如果是引用类型 就可能有问题了
解决 : 加一个版本号 一起判断 就OK
所有的CAS 都是使用Unsafe类
底层使用的是CAS
加锁时 Lock.lock() 最后需要手动解锁
锁 | synchronized | ReentrantLock |
---|---|---|
重入 | 可重入 | 可重入 |
解锁 | 可自动解锁 | 需要手动解锁 |
维护队列 | 只有一个队列 | 可有多个队列 (Condition) |
可尝试获取锁 tryLock() 不管有没有获取都立即返回 | ||
公平锁 | 只有非公平锁 | 有公平与非公平的切换 |
ReentrantLock.unlock() -> 调用 AQS.release(int args)
?
AQS.release() -> AQS.tryRelease(int arg)调用具体的实现类 -> ReentrantLock.Syn.tryRelease(int arg) 该方法中主要做了以下几件事
1 : int c = getState() - releases 获取state 并且减 arg
2 : 判断c == 0 ? setExclusiveOwnerThread为空 即无线程获取锁
3 : setState(c); 将state值更新
await() 线程阻塞
countDown() 减数 当门闩减到0的时候线程会继续运行
初始化时 可传入两个参数 ( 数量, 当满了时调用的Runnable)
其实也就是共享锁和排他锁
可用于限流
用于两线程之间数据交换 调用exchange() 阻塞 等待第二方调用exchange()时 都会放开阻塞
底层使用的是UnSafe实现的
wait()会释放锁
看Object源码得知
在使用迭代器对集合对象进行遍历的时候,如果A线程对集合进行遍历,正好B线程对集合进行修改(增加、删除、修改)则A线程会抛出ConcurrentModificationException异常。
java.util 下的集合都是快速失败的 不支持并发修改
采用安全失败机制的集合容器,在遍历时不是直接在集合内容上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时是对原集合的拷贝进行遍历,所以在遍历过程中对原集合所作的修改并不能被迭代器检测到,所以不会触发Concurrent Modification Exception,例如CopyOnWriteArrayList。
java.util.concurrent 下的集合都是安全失败的 支持并发修改
底层实现使用了跳表
写时复制 当需要添加元素的时候 复制一份原数组 然后操作复制出来的 最后将修改后的数据 set回去
其主要用在读比较多 写比较少的情况下
写操作是线程安全的 读的时候没有加锁
Queue | ||
---|---|---|
insert | offer 如果插入没有成功 就会返回false | add 如果插入没有成功 就会抛异常 |
remove | poll 如果队列没有元素了 就会返回null | remove 如果队列没有元素了就会抛异常 |
Examine | element 只是检索队列 不改变队列 为空返回异常 | peek 只检索队列 不改变队列 为空返回null |
BlokingQueue | |
---|---|
insert | put 如果队列满了 会阻塞 最大值为Integer.MAX_VALUE |
remove | take 如果队列空了 会阻塞 |
内部实现了一个排序 内部是一颗树 二叉小顶堆实现
是一颗完全二叉树 其中 父节点一定小于叶子节点
leftNodeNo = parentNo * 2 +1
rightNodeNo = parentNo * 2 + 2
parentNodeNo = (nodeNo - 1) /2
插入节点的时候 算出节点下标 然后逐层比较 直到>= 父节点时
链接 : https://www.cnblogs.com/CarpenterLee/p/5488070.html
按时间去排序的Queue 按理说 这已经打破了queue的规范了 队列存储的顺序 并不是插入顺序 而是比较后的顺序
一般用来 按时间进行任务调度
这个queue 容量是0的
用来两个线程间传递数据的
transfer(E e) 该方法 只有被其他线程消费了 才会继续往下走 否则会一直阻塞在那
Queue添加了很多对线程友好的api 比如 offer poll peek
BlokingQueue 在Queue的基础上 又添加了一些api 比如 put take
put take 实现了阻塞 天然的实现了生产者消费者模型
扩容机制 : 如果oldCapacity < 64 ? 扩容两倍 + 2: 扩容50%
初始容量 : 11
每个Thread中都维护了一个ThreadLocal->ThreadLocalMap
ThreadLocal 有用在Spring的声明式事务 比如 将数据库连接放入ThreadLocal中
只要是一个事务中的 都是同一个数据库连接
只要引用指向对象 对象就不会被回收
当一个对象只有一个软引用指向的时候 当系统内存不够用时 才会被GC回收
当一个对象只有一个虚引用指向时 只要发生了GC 就会被GC回收
一般用在容器里
使用了弱引用
WeakHashMap中的Entry<K,V> K 是使用了弱引用
如果发生gc WeakHashMap中的key会被干掉
每次put的时候 都会清理一遍WeakHashMap
WeakHashMap 的默认初始容量 16 负载因子 0.75f 每次扩容为原本的两倍
Thread中维护着一个ThreadLocalMap
ThreadLocalMap中有一个Entry<K,V>
Entry继承WeakReference
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
内部 : ThreadLocal 被一个弱引用指向着
外面 : 如果是直接new出来 被一个强引用指向 如果外面的强引用断开了
那么每当发生gc时 ThreadLocal就会被干掉
?
如果内部不用弱引用指向 那么会造成内存泄漏 ThreadLocal 永远不会被回收
但是 那也只是回收了Key value指向的对象 依旧存在
所以 使用完ThreadLocal后 必须要调用remove()方法
用于管理堆外内存的
new PhantomReference(引用 , 引用队列);
给写JVM的人用的
当与引用指向的对象被回收后 会在Queue中存放进一个对象
相当于有返回值的Runnable
存储执行的将来才会产生的结果
get方法是阻塞的
Futrue + Runnable
主要做线程的执行接口
把线程的定义与线程的执行分开
线程池的父接口
里面定义了一些生命周期方法和提交任务方法
submit方法是异步调用的
管理多个Future的结果
corePoolSize
核心线程数
maximumPoolSize
最大线程数
keepAliveTime
当线程的数量大于内核时,这是多余的空闲线程在终止之前等待新任务的最大时间。
unit
时间单位
workQueue
任务队列
threadFactory
线程工厂
handler
拒绝策略 jdk一共提供了四种拒绝策略
1 : AbortPolicy 抛异常
2 : CallerRunsPolicy 在调用者的线程中执行任务
3 : DiscardOldestPolicy 丢弃队列头部的任务(其实也是等待最久的任务) 并且将当前任务尝试处理
4 : DiscardPolicy 不处理 抛弃掉
当执行被阻塞时使用的处理策略,因为达到了线程边界和队列容量
加入一个任务 先看核心线程 如果和心线程没满 起核心线程
如果核心线程满了 查看任务队列 如果任务队列没满 加入任务队列
如果任务队列满了 起一个新线程去处理
如果达到了最大线程数 并且 任务队列也满了 那么启动拒绝策略
最大线程数 + 任务队列长度
分叉出子任务执行 然后最后做汇总
分解汇合任务
用很少的线程可以执行很多的任务(子任务) ThreadPoolExecutor做不到先执行子任务
CPU密集型
原文:https://www.cnblogs.com/self-crossing/p/12658087.html