Java 并发 - 理论基础
为什么需要多线程④
并发出现问题的根源
JAVA是怎么解决并发问题
- 可以通过什么保证原子性,可见性,有序性
- JMM是通过什么来保证有序性的
Happens-Before 规则
单一线程原则
管程锁定规则
volatile 变量规则
线程启动规则
线程加入规则
对象终结规则
线程中断规则
传递性
线程安全
可以将共享数据按照安全程度的强弱顺序分成以下五类:
不可变
不可变的类型
对于集合类型,可以使用什么方法来获取一个不可变的集合
绝对线程安全
相对线程安全
线程兼容
线程对立
线程安全的实现方法
互斥同步
最大问题
属于什么的并发策略,哪些操作
synchronized 和 ReentrantLock
非阻塞同步
CAS
属于什么类
是什么
检测到冲突
实现
AtomicInteger
- compareAndSet()
- getAndIncrement()
ABA
变量初次读取是 A ,被改成 B,后来又改回 A,那 CAS 操作就认为它没有被改过
J.U.C 包提供了一个带有标记的类 什么来解决这个问题
解决 ABA 问题,改用什么可能会比原子类更高效
无同步方案
栈封闭
线程本地存储
可重入代码
Java 并发 - 线程基础
线程状态转换
新建(New)
可运行(Runnable)②
阻塞(Blocking)
无限期等待(Waiting)
进入方法和退出方法
是什么
限期等待(Timed Waiting)
是什么
进入方法和退出方法
睡眠和挂起是用来描述,而阻塞和等待用来描述(主动和被动)。
死亡(Terminated)
- 二种情况
线程使用方式
实现 Runnable 接口
实现 Callable 接口
- 与 Runnable 相比,Callable 可以有什么,返回值通过。
继承 Thread 类
需要实现,因为
当调用 start() 方法启动一个线程时
实现接口会更好一些,因为
基础线程机制
Executor
是什么
三种Executor
Daemon
是什么
当所有非守护线程结束时
main() 属于
使用什么 方法将一个线程设置为守护线程
sleep()
- 使用方法,需要
- yield()
线程中断
什么时候会中断
InterruptedExcept
interrupted()
Executor 的中断操作
shutdown() 方法
shutdownNow()
只想中断 Executor 中的一个线程
线程互斥同步
两种锁机制
synchronized
同步一个代码块
同步一个方法
同步一个类
同步一个静态方法
ReentrantLock
- 是什么
比较
锁的实现
性能
等待可中断
公平锁
锁绑定多个条件
使用选择
线程之间的协作
join()
wait() notify() notifyAll()
- 属于
- 只能用在
wait() 和 sleep() 的区别
await() signal() signalAll()
- 在哪调用
synchronized详解
Synchronized的使用
synchronized是通过实现的
注意
- 一把锁只能
- 实例对象的锁对象
- *.class或synchronized修饰的static方法锁对象
- synchronized修饰的方法结束时②
- 锁对象不能
- 作用域
- 同步时选择
对象锁②
类锁
Synchronized原理分析
- 加锁和释放锁的原理
可重入原理
保证可见性的原理
JVM中锁的优化
JDK锁的优化
JVM中monitorenter和monitorexit字节码
锁优化⑤
锁的类型
- 可以
- 锁膨胀方向
自旋锁与自适应自旋锁
- 默认的自旋次数
锁消除
锁粗化
轻量级锁
- 轻量级锁加锁
偏向锁
- 偏向锁的撤销
- 偏向锁的撤销
锁的优缺点对比
- 锁 优点 缺点 使用场景
Synchronized与Lock
synchronized的缺陷
Lock解决相应问题
4个方法
Synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活解决办法
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断
关键字: volatile详解
volatile的作用详解
防重排序
实现可见性
保证原子性:单次读/写
- 共享的long和double变量的为什么要用volatile?
volatile 的实现原理
volatile 可见性实现
volatile 变量的内存可见性是基于
在 volatile 修饰的共享变量进行写操作的时候
lock 前缀的指令在多核处理器下会引发两件事情
lock 指令
volatile 有序性实现
- volatile 的 happens-before 关系
- volatile 禁止重排序
内存屏障指令
volatile 的应用场景
使用 volatile 必须具备的条件
模式1:状态标志
模式2:一次性安全发布
模式3:独立观察
模式4:volatile bean 模式
模式5:开销较低的读-写锁策略
模式6:双重检查
关键字: final详解
final基础使用
修饰类
继承
final类中的所有方法
final类型的类如何拓展
修饰方法
- private 方法
- final方法可以
修饰参数
- 无法在方法中
- 主要用来
修饰变量
所有的final修饰的字段都是编译期常量吗
static final
- 只占据
- 赋值时机
- 属于这个类
blank final
- 赋值时机
final域重排序规则
final域为基本类型
写final域重排序规则②
读final域重排序规则
final域为引用类型
对final修饰的对象的成员域写操作
对final修饰的对象的成员域读操作
final再深入理解
final的实现原理②
为什么final引用不能从构造函数中“溢出”
使用 final 的限制条件和局限性
当声明一个 final 成员时,必须
final指向引用对象
JUC - 类汇总和学习指南
JUC主要包含哪几部分
Lock框架和Tools类
接口: Condition
接口: Lock
接口: ReadWriteLock
核心抽象类(int): AbstractQueuedSynchonizer
锁常用类: LockSupport
锁常用类: ReentrantLock
锁常用类: ReentrantReadWriteLock
锁常用类: StampedLock
工具常用类: CountDownLatch
工具常用类: CyclicBarrier
工具常用类: Phaser
工具常用类: Semaphore
工具常用类: Exchanger
Collections: 并发集合
Queue: ArrayBlockingQueue
Queue: LinkedBlockingQueue
Queue: LinkedBlockingDeque
Queue: ConcurrentLinkedQueue
Queue: ConcurrentLinkedDeque
Queue: DelayQueue
Queue: PriorityBlockingQueue
Queue: SynchronousQueue
Queue: LinkedTransferQueue
List: CopyOnWriteArrayList
Set: CopyOnWriteArraySet
Set: ConcurrentSkipListSet
Map: ConcurrentHashMap
Map: ConcurrentSkipListMap
Atomic: 原子类
是什么
基础类型:AtomicBoolean,AtomicInteger,AtomicLong
数组:AtomicIntegerArray,AtomicLongArray,BooleanArray
引用:AtomicReference,AtomicMarkedReference,AtomicStampedReference
FieldUpdater:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
Executors: 线程池
接口: Executor
ExecutorService
- 继承自
- 提供了什么,以及
- 关闭 ExecutorService会导致
ScheduledExecutorService
AbstractExecutorService
FutureTask
- 线程安全由什么来保证。
核心: ThreadPoolExecutor
核心: ScheduledThreadExecutor
核心: Fork/Join框架
工具类: Executors
JUC原子类: CAS, Unsafe和原子类详解
- java原子类本质上使用
CAS
是什么
- 是一条
- 实现方式
- AtomicInteger类
- 简单解释
CAS使用示例
CAS 问题
ABA问题
- 解决思路②
循环时间长开销大
只能保证一个共享变量的原子操作
- 从Java 1.5开始
UnSafe类详解
- 是什么
Unsafe与CAS
- Unsafe只提供了3种CAS方法
Unsafe底层(了解)
Unsafe其它功能
AtomicInteger
AtomicInteger 底层用的是
源码解析
原子更新基本类型
原子更新数组
原子更新引用类型
- 首先
- 然后
- 然后
原子更新字段类
基于
AtomicIntegerFieldUpdater 的使用稍微有一些限制和约束⑤
AtomicStampedReference解决CAS的ABA问题
主要维护
java中还有哪些类可以解决ABA的问题
- 不是维护,而是
JUC锁: LockSupport详解
LockSupport简介
LockSupport源码分析
类的属性
类的构造函数
核心函数分析
park函数
- 是什么
- 下列情况发生之前都会被阻塞③
- 重载版本
- 有参版本其中一个setBlocker函数调用两次
- 无参重载版本
unpark函数
- 是什么
- 安全性
parkNanos函数
parkUntil
unpark函数
- 判空
LockSupport示例说明
使用wait/notify实现线程同步
先调用notify()再调用wait ```java class MyThread extends Thread {
public void run() { synchronized (this) {
System.out.println("before notify");
notify();
System.out.println("after notify");
} } }
public class WaitAndNotifyDemo {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
synchronized (myThread) {
try {
myThread.start();
// 主线程睡眠3s
Thread.sleep(3000);
System.out.println(“before wait”);
// 阻塞主线程
myThread.wait();
System.out.println(“after wait”);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程不会被阻塞,直接跳过park()
park()/unpark()底层的原理是“二元信号量”,你可以把它相像成只有一个许可证的Semaphore,只不过这个信号量在重复执行unpark()的时候也不会再增加许可证,最多只有一个许可证
不会,park()只负责阻塞线程,释放锁资源再Condition的await()方法.
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态<br />
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中
虚拟的双向队列,不存在队列实例 AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配
procted类型的getState,setState,compareAndSetState进行操作
-
独占
- 公平锁
- 非公平锁
-
共享
<br />自定义同步器在实现时只需要实现共享资源 state 的获取与释放方式即可
<br />isHeldExclusively()//该线程是否正在独占资源。只有用到condition才需要去实现它。<br />
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。<br />
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。<br />
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。<br />
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
Sync queue,即同步队列,是双向链表<br />
Condition queue不是必须的,其是一个单向链表,只有当使用Condition时,才会存在此单向链表
// CANCELLED,值为1,表示当前的线程被取消
// SIGNAL,值为-1,表示当前节点的后继节点包含的线程需要运行,也就是unpark
// CONDITION,值为-2,表示当前节点在等待condition,也就是在condition队列中
// PROPAGATE,值为-3,表示当前场景下后续的acquireShared能够得以执行
// 值为0,表示当前节点在sync队列中,等待着获取锁
public interface Condition {
// 等待,当前线程在接到信号或被中断之前一直处于等待状态
void await() throws InterruptedException;
// 等待,当前线程在接到信号之前一直处于等待状态,不响应中断
void awaitUninterruptibly();
//等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁。
void signal();
// 唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
void signalAll();
}
头结点head,尾结点tail,状态state、自旋时间spinForTimeoutThreshold<br />
AbstractQueuedSynchronizer抽象的属性在内存中的偏移地址
1.判断结点的前驱是否为head并且是否成功获取(资源)。 2.若步骤1均满足,则设置结点为head,之后会判断是否finally模块,然后返回。 3.若步骤2不满足,则判断是否需要park当前线程,是否需要park当前线程的逻辑是判断结点的前驱结点的状态是否为SIGNAL,若是,则park当前结点,否则,不进行park操作。 4.若park了当前线程,之后某个线程对本线程unpark后,并且本线程也获得机会运行。那么,将会继续进行步骤①的判断。
-
每一个结点都是由前一个结点唤醒
-
当结点发现前驱结点是head并且尝试获取成功,则会轮到该线程运行
-
condition queue中的结点向sync queue中转移是通过signal操作完成的
-
当结点的状态为SIGNAL时,表示后面的结点需要运行
<br />ReentrantLock和AbstractQueuedSynchronizer
<br />实现了ReadWriteLock接口
<br />HoldCounter和ThreadLocalHoldCounter<br />
HoldCounter主要有两个属性,count和tid,其中count表示某个读线程重入的次数,tid表示该线程的tid字段的值,该字段可以用来唯一标识一个线程
<br />ThreadLocalHoldCounter重写了ThreadLocal的initialValue方法,ThreadLocal类可以将线程与对象相关联。在没有进行set的情况下,get到的均是initialValue方法里面生成的那个HolderCounter对象
使用了synchronized关键字对put等操作进行加锁<br />
而synchronized关键字加锁是对整个对象进行加锁<br />
也就是说在进行put等修改Hash表的操作时,锁住了整个Hash表
ConcurrentHashMap 是一个 Segment 数组<br />
Segment 通过继承 ReentrantLock 来进行加锁
这个值指的是整个 ConcurrentHashMap 的初始容量,实际操作的时候需要平均分给每个 Segment
Segment 数组不可以扩容,所以这个负载因子是给每个 Segment 内部使用
加锁则采用CAS和synchronized实现
通过提供初始容量,计算了 sizeCtl sizeCtl = 【 (1.5 * initialCapacity + 1),然后向上取最近的 2 的 n 次方】
1.判断key value 不为空<br />
2.计算hash值<br />
3.根据对应位置的节点的类型来赋值,或者helpTransfer,或者增长链表,或者给红黑树增加节点<br />
4.检查满足阈值就"红黑树化"<br />
5.返回oldVal
1.计算hash值<br />
2.找到对应的位置,根据情况进行:<br />
直接取值<br />
红黑树里找值<br />
遍历链表取值<br />
返回找到的结果
CopyOnWriteArrayList实现了List接口,List接口定义了对列表的基本操作;同时实现了RandomAccess接口,表示可以随机访问(数组具有随机访问的特性);同时实现了Cloneable接口,表示可克隆;同时也实现了Serializable接口,表示可被序列化。
FutureTask 常用来封装 Callable 和 Runnable,也可以作为一个任务提交到线程池中执行
FutureTask 的线程安全由CAS来保证
FutureTask类关系
FutureTask源码解析
- Callable接口
- Future接口
JUC线程池: ThreadPoolExecutor详解
为什么要有线程池③
```