并发编程
# 线程和进程的区别
进程是正在运行程序的实例,包含了线程。
不同进程拥有不同的进程空间,一个进程中的线程共用进程内存空间
线程上下文切换成本低
# 并行和并发
时间片轮转。
并发,在一段时间内,处理多件事情。
并行,同一时间处理多件事情。
# 创建线程的方式
继承Thread类
实现Runable接口
实现Callable接口,可以拿到返回值
使用线程池
# 线程状态
# notify和notifAll
随机唤醒一个线程,和唤醒所有线程。object.notif();
# wait和sleep
共同点
wait() ,wait(long) 和 sleep(long) 的效果都是让当前线程暂时放弃 CPU 的使用权,进入阻塞状态
不同点
- 方法归属不同
sleep(long) 是 Thread 的静态方法而 wait(),wait(long) 都是 Object 的成员方法,每个对象都有
- 醒来时机不同
执行 sleep(long) 和 wait(long) 的线程都会在等待相应毫秒后醒来wait(long) 和 wait() 还以被 notify 唤醒,wait() 如果不唤醒就一直等下去它们都可以被打断唤醒
- 锁特性不同(重点)
wait 方法的调用必须先获取 wait 对象的锁,而 sleep 则无此限制wait 方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃 cpu,但你们还可以用)而 sleep 如果在synchronized 代码块中执行,并不会释放对象锁(我放弃 cpu,你们也用不了)
# 如何停止线程
采用退出标记
使用interrupt
打断阻塞的线程( sleep,wait,join )的线程,线程会抛出InterruptedException异常 打断正常的线程,可以根据打断状态来标记是否退出线程
# synchronized
最多一个线程获得锁,其它线程会阻塞。
Java对象头
|---------------------------------------------------------------|
| Object Header (64 bits) |
|-------------------------------|-------------------------------|
| Mark Word (32 bits) | Klass Word (32bits) |
|-------------------------------|-------------------------------|
创建锁记录对象,每个线程的栈帧都会包含一个所记录,内部存储锁定的对象的Mark Work
当线程尝试锁定某对象,判断对象头中是否为空闲状态,如果空闲,则将栈帧的锁记录与对象头交换
如果此时有其它线程想获取该轻量锁,那么会失败,则进行如下步骤
为该对象申请Monitor锁,将对象头指向Monitor锁地址,自己进入EntryList中等待。
锁结构
等原来获取到轻量级锁的线程,解锁,按照Monitor解锁的流程,情况Owner,唤醒EntryList。
自旋优化
# JMM
Java内存模型,划分为工作内存,共享内存。线程之间是相互隔离的,交互需要主内存。
# CAS
Compare And Swap
乐观锁,如果旧的值,和原来值不一致,则会自旋(重新进行更新)
# volatile
不从缓存,从内存中读取数据。禁止指令重排序
# AQS
抽象队列同步器。内部维护一个state变量,如果线程将state成功修改为1,则表示该线程获得锁,否则线程会被标记到AQS中的队列中。
# ReentrantLock
可中断,设置超时时间,公平锁,可重入。
# synchronized和lock
前者,C++实现,自动释放锁。后者java实现,需要lock,unlock。
可打断,在获取锁阻塞时,其它线程可以打断他的打断过程。
# ConcurrentHashMap
底层数据结构与HashMap一致,数组+链表/红黑二叉树
加锁:采用CAS添加新节点,锁定链表或者红黑树的首节点
# 线程池使用场景
# 分批执行
# 汇总信息
# 异步调用
@Async("线程池")
# 控制方法的并发量
信号量,限流。
Semaphore semaphore = new Semaphore(3);
// 获取信号量
semaphore.acquire();
// 释放信号量
semaphore.release();
# ThreadLocal
实现资源对象的线程隔离
每个线程内有一个ThreadLocalMap类型的成员变量,用来存储资源对象。
调用set方法,就是以ThreadLocal作为key,资源对象作为value,放入当前线程的ThreadLocalMap集合中
调用get方法,同理。
调用remove方法,同理。
ThreadLocalMap内存泄露,需要手动调用remove释放一下。
# 底层原理
每个Thread类都有一个Map,当需要存储一个ThreadLocal<T> tl = new ThreadLocal
,值为v时,Thread中的map存储方式为map.set(tl, v)
,这样线程取指定ThreadLocal时,只需要在自己线程内map.get(tl)。
使用方式是
ThreadLocal<T> tl = new ThreadLocal<>();
tl.set(val);
tl.get();
tl.remove();
实际上是,Thread中的ThreadLocalMap存了多个ThreadLocal。