Java 多线程

概念

Java Thread 类相关

Thread.sleep()

Runnable

Callable

synchronized 方法

synchronized 关键字只能用于修饰方法和代码块,而且不能用于修饰构造方法。

参考 Java Language Specification Chapter 17. Threads and Locks

A synchronized method automatically performs a lock action when it is invoked; its body is not executed until the lock action has successfully completed. If the method is an instance method, it locks the monitor associated with the instance for which it was invoked (that is, the object that will be known as this during execution of the body of the method). If the method is static, it locks the monitor associated with the Class object that represents the class in which the method is defined. If execution of the method’s body is ever completed, either normally or abruptly, an unlock action is automatically performed on that same monitor.

也就是说

1
2
3
4
5
6
7
8
// 写法 1
public class DemoOne {
private int a = 0;

synchronized public void methodOne() {
a++;
}
}
1
2
3
4
5
6
7
8
9
10
// 写法2
public class DemoOne {
private int a = 0;

public void methodOne() {
synchronized (this) {
a++;
}
}
}

写法 1 等同于写法 2。

1
2
3
4
5
6
7
8
// 写法 3
public class DemoOne {
private static int a = 0;

synchronized public static void methodOne() {
a++;
}
}
1
2
3
4
5
6
7
8
9
10
// 写法 4
public class DemoOne {
private static int a = 0;

public static void methodOne() {
synchronized (DemoOne.class) {
a++;
}
}
}

写法 3 等同于 写法 4。

住:以上“等同”仅仅指代码执行效果相同,但是两种写法编译之后生成的字节码不一定相同。

任何线程进入同步方法或者同步代码块之前,需要先获得 Monitor 对象。那同步方法和同步代码块什么时候会释放 Monitor 对象呢?

有以下几种情况:

  • 当前线程的同步方法执行结束、无论是正常结束还是异常结束。
  • 在当前线程执行同步方法或者同步代码块的时候调用了同步监视器对象的 wait() 方法。

以下几种情况不会释放同步监视器对象:

  • 线程执行同步方法或者同步代码块的时候,程序调用 Thread.sleep() 方法或者 Thread.yield() 方法暂停当前线程的执行,当前线程不会释放同步监视器。
  • 线程执行同步方法或者同步代码块的时候,其他线程调用了该线程的 suspend() 方法将该线程挂起,该线程不会释放同步监视器对象。

线程通信

wait()、notify()、notifyAll() 方法

这三个方法都是 java.lang.Object 类的方法。必须由同步监视器对象来调用。

wait()

导致当前线程等待,直到其他线程调用该同步监视器的 notify() 或者 notifyAll() 方法来唤醒该线程。

使用 Condition 控制线程通信

使用阻塞队列 BlockQueue 控制线程通信

线程组

线程池

从 JDK 1.5 开始,Java 内建支持线程池。

为什么要使用线程池?

  • 系统创建一个新线程的成本比较高,因为涉及到和操作系统的交互,可以通过线程池提高性能
  • 使用线程池可以控制系统中并发线程的数量,防止 JVM 中线程数过多导致 JVM 性能下降

JDK 中使用 java.util.concurrent.ExecutorService 接口表示线程池。

ForkJoinPool

线程池参考资料

  1. Java中线程池,你真的会用吗?-HollisChuang’s Blog
  2. 一次Java线程池误用引发的血案和总结 - 知乎

参考资料

  1. 协程的好处有哪些? - 知乎
  2. 协程 - 维基百科,自由的百科全书