多线程八股
多线程
基础
进程和线程
进程:当一个程序被运行,从磁盘加载代码到内存,此时就开启了一个进程
线程:一个进程可以分为多个线程
比较:
- 进程是正在运行的实例,进程包含线程,每个线程执行不同的任务
- 不同的进程使用不同的内存空间,一个进程的线程共享内存空间
- 线程上下文切换代价更低
并发和并行
并发:一个cpu同时处理多个线程
并行:多个线程同时执行
:star:创建线程的方式
- 继承Thread类,重写run(),start()开启
- 实现Runnable接口,重写run(),用Thread()包装,start()开启
- 实现Callable接口,重写call(),用FutureTask()包装,再用Thread()包装,start()开启,get()获取返回值
- 线程池创建线程,将任务submit提交给线程池
Runnable和Callable的区别:
- Runnable接口的run方法没有返回值,Callable接口call方法有返回值
- Callable接口的call方法允许抛出异常,Runnable的run只能catch
start()和run()的区别
start():启动线程,start方法只能调用一次
run():要被线程执行的代码
线程状态
1 | |
方法
Thread.join():等待线程执行完
Object.wait():放弃锁,在这个锁上等待
Object.notify():随机唤醒锁上等待的线程
Object.notifyAll():唤醒当前锁上的所有线程
Thread.sleep():抱着锁睡眠
Thread.interrupt():打断线程,被打断的线程如果在阻塞会抛异常;被打断线程没有阻塞,则自己通过interrupt变量判断是否要终止线程
并发安全
锁
synchronized
互斥的对象锁,重量级锁,性能较低,一旦锁发生竞争都会升级为重量级锁
原理
synchronized(lock)中的lock会与Monitor关联(对象的MarkWord指向Monitor)
Monitor监视器:
- WaitSet:在锁上wait的线程
- EntryList:等待锁的线程队列
- Owner:当前锁的持有者
轻量级锁:通过CAS来获取锁;锁对象的MarkWord指向锁记录地址,原来MarkWord的内容放在持有锁线程中
偏向锁:只是在MarkWord中记录一下持有锁线程的id,锁重入时不需要cas
内存模型
JMM(Java Memory Model),定义了共享内存和多线程程序读写操作的行为规范

CAS
Compare And Swap,体现乐观锁的思想,在无锁状态下保证线程操作数据的原子性
1 | |
特点:
- 没有加锁,线程不会阻塞,效率较高
- 若竞争激烈,重试频繁,效率会受影响
底层:
依赖于Unsafe类来直接调用操作系统的CAS指令
使用场景:
- ReentrantLock、AtomicXXX类等
volatile
关键字,被修饰的变量可以保证可见性和防止指令重排序
共享变量不可见的情况:由于一个变量需要读很多次,而每次都读到的同一个值,JIT编译器就会做优化
1 | |
指令重排序的情况:编译器优化代码顺序
volatile通过向上的写屏障和向下的读屏障来防止指令重排
AQS
Abstract Queue Synchronized,抽象队列同步器
原理:通过state变量表示锁是否被占用(0 无锁,1 有锁),线程通过CAS操作来进行修改,没抢到锁的去FIFO队列等待
AQS常见的实现类
- ReentrantLock 可重入锁
- Semaphore 信号量
- CountDownLatch 倒计时锁