前言
- 进程、线程、协程/纤程
- 线程的创建
- Thread的常用方法
- 线程状态机
- synchronized、volatile
进程、线程与纤程/协程
- 进程:资源分配的最小单位,各进程之间的内存空间是相互隔离的,由处理器的MMU映射到物理内存上。
- 线程:程序执行的最小单位,同一个进程内的各个线程是处于同一个内存空间的。
- 纤程(fiber)/协程(coroutine):比线程更轻量级,最著名的是go语言中的纤程。java12之前并没有官方提供纤程支持,有个叫Quasar的纤程库。
线程
线程的创建
继承Thread类,重写run方法
1 | class MyThread extand Thread { |
1 | new MyThread().start(); |
实现Runnable接口
1 | class MyRun implement Runnable { |
1 | new Thread(new MyRun()).start(); |
lamda表达式
1 | new Thread(() -> {...}).start(); |
线程池
1 | Executors.newCachedThreadPool(); |
线程状态机
如下所所示:
Thread的常用方法
- sleep,线程进入TimedWating状态
- yield,线程仍处于Runnable状态,只是被挂起,可能很快被再次调度运行
- join,等待其他线程执行完成
synchronized
用法
修饰代码块
1 | synchronized (obj) { |
作用范围:{}内的代码
锁:obj对象,注意obj不可为null,否则会抛NullPointerException
obj常用this,这样锁就是调用此代码块的对象
修饰方法
1 | public synchronized void fun() { |
作用对象:整个函数
锁:this
等效于:
1
2
3
4
5public void fun() {
synchronized (this) {
...
}
}
修饰静态方法
1 | public synchronized static void fun() { |
- 作用范围:整个函数
- 锁:此class对象
- 即这个类的所有对象,访问这个方法都是互斥的
特性
- synchronized既可以保证原子性,又可以保证可见性
- 可重入
- 如果在同步代码段抛出异常,会自动释放锁,如果不希望释放,需要catch处理。
- synchronized(obj),obj不可为String常量,不可为int、long等基础数据类型。
底层实现
- JDK早期(JDK1.5之前),锁需要向操作系统申请,重量级锁,效率低。
- JDK1.6开始,synchronized实现了锁升级机制:
- 偏向锁 -> 自旋锁 -> 重量级锁
- HotSpot的JVM实现中,锁升级后就不会再降级
- 自旋锁自旋等待时会消耗CPU资源,重量级锁的申请和线程切换需要较大开销,各有利弊。
- 底层细节详情请移步:《多线程-synchronized底层实现》
volatile
原理可移步《多线程-JMM》
保证可见性
举例:如果下面代码中去掉volatile,会导致flag的修改不可见。
1 | public class T01_happensBefore { |
禁止指令重排序
著名的DCL(Double Check Lock)单例实现中,如果去掉volatile,可能导致单例的赋值和初始化重排序,导致拿到尚未初始化完成的对象。
1 | public class Singleton(){ |