多线程-基础

前言

  • 进程、线程、协程/纤程
  • 线程的创建
  • Thread的常用方法
  • 线程状态机
  • synchronized、volatile

进程、线程与纤程/协程

  • 进程:资源分配的最小单位,各进程之间的内存空间是相互隔离的,由处理器的MMU映射到物理内存上。
  • 线程:程序执行的最小单位,同一个进程内的各个线程是处于同一个内存空间的。
  • 纤程(fiber)/协程(coroutine):比线程更轻量级,最著名的是go语言中的纤程。java12之前并没有官方提供纤程支持,有个叫Quasar的纤程库。

线程

线程的创建

继承Thread类,重写run方法

1
2
3
4
5
6
class MyThread extand Thread {
@Override
public void run() {
...
}
}
1
new MyThread().start();

实现Runnable接口

1
2
3
4
5
6
class MyRun implement Runnable {
@Override
public void run() {
...
}
}
1
new Thread(new MyRun()).start();

lamda表达式

1
new Thread(() -> {...}).start();

线程池

1
Executors.newCachedThreadPool();

线程状态机

如下所所示:

多线程状态机

Thread的常用方法

  • sleep,线程进入TimedWating状态
  • yield,线程仍处于Runnable状态,只是被挂起,可能很快被再次调度运行
  • join,等待其他线程执行完成

synchronized

用法

修饰代码块

1
2
3
synchronized (obj) {
...
}
  • 作用范围:{}内的代码

  • 锁:obj对象,注意obj不可为null,否则会抛NullPointerException

  • obj常用this,这样锁就是调用此代码块的对象

修饰方法

1
2
3
public synchronized void fun() {
...
}
  • 作用对象:整个函数

  • 锁:this

  • 等效于:

    1
    2
    3
    4
    5
    public void fun() {
    synchronized (this) {
    ...
    }
    }

修饰静态方法

1
2
3
public synchronized static void fun() {
...
}
  • 作用范围:整个函数
  • 锁:此class对象
  • 即这个类的所有对象,访问这个方法都是互斥的

特性

  1. synchronized既可以保证原子性,又可以保证可见性
  2. 可重入
  3. 如果在同步代码段抛出异常,会自动释放锁,如果不希望释放,需要catch处理。
  4. synchronized(obj),obj不可为String常量,不可为int、long等基础数据类型。

底层实现

  • JDK早期(JDK1.5之前),锁需要向操作系统申请,重量级锁,效率低。
  • JDK1.6开始,synchronized实现了锁升级机制:
    • 偏向锁 -> 自旋锁 -> 重量级锁
    • HotSpot的JVM实现中,锁升级后就不会再降级
    • 自旋锁自旋等待时会消耗CPU资源,重量级锁的申请和线程切换需要较大开销,各有利弊。
  • 底层细节详情请移步:《多线程-synchronized底层实现》

volatile

原理可移步《多线程-JMM》

保证可见性

举例:如果下面代码中去掉volatile,会导致flag的修改不可见。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public class T01_happensBefore {

static volatile Boolean flag = false;

public static void main(String[] args) {

System.out.println("main start");

new Thread(() -> {
try {
Thread.sleep(2000);
flag = true;
System.out.println("T1 Set flag true!");
} catch (Exception e) {
e.printStackTrace();
}
}).start();

while (true) {
if (flag) {
System.out.println("got flag...");
break;
}
}
}
}

禁止指令重排序

著名的DCL(Double Check Lock)单例实现中,如果去掉volatile,可能导致单例的赋值和初始化重排序,导致拿到尚未初始化完成的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Singleton(){  
private volatile static Singleton singleton;
private Sington(){};
public static Singleton getInstance(){
if(singleton == null){
synchronized (Singleton.class){
if(singleton == null){
singleton = new Singleton();
}
}
}
return singleton;
}
}