创建线程的三种方式
- 继承Thread对象,重写run方法
- 实现runnable接口,作为Thread构造参数 - Thread默认的run()方法中会调用runnable对象的run()方法
- 实现callable接口,配合FutureTask
对象使用 - 底层依然是runnable接口,通过共享变量实现线程之间的控制和通信
/*
* 实质:
* - Thread对象在start()方法中调用native方法通知操作系统创建线程,
* - 线程创建后,会回调Thread对象的run()方法实现业务调用
*/
public class ThreadTest {
public static void main(String[] args) throws Exception{
// 1. Thread类通过start()方法通知操作系统构建线程,操作系统创建线程后会回调Thread对象的run()方法
Thread myThread = new MyThread();
myThread.start();
// 2. Thread类中存在一个runnable对象,默认的run()方法中会调用runnable对象的run()方法
Runnable myRunnable = new MyRunnable();
new Thread(myRunnable).start();
// 3. 本质是对runnable接口的封装,添加future接口的实现,通过共享变量实现主线程与子线程之间的信息传递,包括:控制和返回值
Callable callable = new MyCallable();
FutureTask<String> task = new FutureTask<String>(callable);
new Thread(task).start();
String answer = task.get();
}
}
操作系统中线程的生命周期
新建(New):新创建出来的线程,尚未执行start()方法
就绪(Ready):等待操作系统分配CPU时间片
运行(Running):正在运行中的线程
阻塞(Blocked):等待锁或者等待被唤醒
终结(Terminated):执行完成的线程
JMM中线程的生命周期
新建(New):新创建出来的线程,尚未执行start()方法
运行态(Running):操作系统中就绪态和运行态的集合,代表正在执行中的线程
阻塞(Blocked):由Synchronized触发的阻塞状态
等待(Waiting): 等待被唤醒
限时等待(Timed Waiting):带超时时间的Waiting状态
终结(Terminated):执行完成的线程
tips: LockSupport是JMM为实现锁机制提供的类,其底层调用unsafe的park/unpark()方法
interrupt 线程中断标志
JMM底层为Thread提供了interrupt标志位,用于控制线程的执行。
- interrupt():将标志位设置为true(此时,会唤醒处于part()的线程)
- interrupted():将标志位恢复为false(使标志位复位,以等待下一次中断信号)
- isInterrupted():获取线程的中断状态
tips:interrupt标志位用于优雅地中止线程,当interrupt标志被置为true时,会唤醒对应的线程,由线程内部实现决定后续逻辑(InterruptedException)
tips:interrupt只发送中断信号,而是否中断以及具体的中断逻辑由线程自行决定
synchronized(类锁/对象锁)
-
特性:
- 不可中断
- 可重入(底层有重入计数器)
- 非公平锁
-
JDK 6对synchronized的优化:无锁化(偏向锁,轻量级锁)
- 偏向锁:适用场景 - 在同步周期内大概率不存在并发问题,通过CAS设置偏向标志,代表已有线程获取锁
- 轻量级锁(自适应自旋锁):适用场景 - 大部分锁都会在很短时间内被释放,通过CAS自旋不断获取锁,避免线程挂起的损耗
- 重量级锁:通过Monitor对象实现锁,内部实现为EntryList和WaitSet两个阻塞队列 - 用于同步状态(锁)和等待状态(wait - notify)
死锁的条件
- 互斥,共享资源 X 和 Y 只能被一个线程占用;
- 占有且等待,线程 T1 已经取得共享资源 X,在等待共享资源 Y 的时候,不释放共享资源 X;
- 不可抢占,其他线程不能强行抢占线程 T1 占有的资源;
- 循环等待,线程 T1 等待线程 T2 占有的资源,线程 T2 等待线程 T1 占有的资源,就是循环等待。
volatile
并发编程问题:
- CPU存在多级缓存(会导致缓存一致性问题)
- 对指令实现重排序优化(编译器优化重排序/指令并行重排序/内存系统重排序),导致多线程任务下指令执行顺序的不可预测
volatile:
- 即时可见性:修饰变量发生修改,会立刻被其他线程感知(可见) - 缓存一致性协议
- 避免指令重排序:在修饰变量处插入内存屏障,避免指令重排序(实现指令的happen-before关系) - Lock字节码指令
缓存一致性协议(MESI):
- M(Modify):缓存已被修改
- E(Exclusive):独占缓存,数据只缓存在了当前CPU缓存中,未被修改
- S(Shared):共享缓存,多个CPU均存储了该缓存,且未被修改
- I(Invalid):缓存已失效
内存屏障:
- 读内存屏障(处理失效的缓存): volatile在读操作之前会插入一个load屏障(刷新无效缓存,得到最新的值)
- 写内存屏障(将当前缓存的值写回主存): volatile变量在写操作之后会插入一个store屏障(将之前store的缓存写入主存)
- 读写内存屏障
Happens-Before模型
- 程序次序规则(as-if-serial语义):单线程环境下,执行结果不变
- volatile变量规则: volatile 修饰的变量的写操作,一定happens-before后续对于volatile变量的读操作.
- 传递性规则:若a happens-before b,b happens-before c,则有 a happens-before c
- start()规则:如果线程A执行操作ThreadB.start(),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
- Join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回。
- 等
Thread.join()
使当前线程阻塞,等待Thread结束后被唤醒,让线程的执行结果对后续线程可见(实现线程间的顺序性)
ThreadLocal - 线程隔离存储机制
// ThreadLocal.class#get()
public T get() {
Thread t = Thread.currentThread();
// 获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null) {
// 从ThreadLocalMap中获取当前ThreadLocal对应的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
tips
- sleep(), join() 和 yiled() 的区别
- sleep: 让线程睡眠指定时间,会释放cpu时间片(单不会释放锁资源)
- join - wait()/notify(): 让主线程等待join线程的执行结果(happens-before模型join规则)
- yiled: 让出时间片,触发重新调度,效果等同于sleep(0)
- i++操作是线程安全的吗?
不是,i++底层字节码分为load,store两条指令,非原子性,可能存在并发问题