本篇博客详细介绍Java多线程的基本原理与并发知识。参考博客https://github.com/CyC2018
线程状态
线程状态如下图所示,共有线程新建、可运行、阻塞、无限期等待、限期等待和终止五种状态。
新建(New)
线程的新建状态是指创建之后未使用。
可运行(Runnable)
线程的可运行状态指的是可能正在运行,也可能正在等待CPU时间片。
阻塞(Blocked)
等待获取排它锁,如果其他线程释放锁便会结束此状态。
无限期等待
等待其他线程显式的唤醒,否则不会分配时间片。
进入方法 | 退出方法 |
---|---|
没有设置TImeout参数的Object.wait() | Object().notify()和Object().notifyAll() |
没有设置Timeout参数的Object.join() | 被调用的线程执行完毕 |
限期等待
无需等待其他线程显示的唤醒,在一定时间之后会被系统自动的唤醒。
调用Thread.sleep()方法使线程进入限期等待状态,用“使一个线程睡眠”来进行描述。
调用Object.wait()方法是线程进入限期等待状态,用“使一个线程挂起”来进行描述。
睡眠和挂起用来描述行为,而阻塞和等待用来描述状态。
阻塞和等待的区别为,阻塞是被动的,它是等待获取一个排它锁,而等待是主动的。通常调用Thread.sleep()或者Object.wait()方法进入
进入方法 | 退出方法 |
---|---|
使用Thread.sleep()方法 | 时间结束 |
设置Timeout参数的Thread.join()方法 | 时间结束/被调用的线程执行完毕 |
设置Timeout参数的Object.wait()方法 | 时间结束/Object.notify()/Object.notifyAll() |
终止
线程执行完毕或者产生异常而结束。
线程的实现方法
有三种实现线程的方法
- 实现Runnable接口
- 实现Callable接口
- 继承Thread类
实现Runnable接口或者实现Callable接口的类只能当做可以在线程中运行的任务,而不是真正的线程。最后还需要Thread类来调用。
实现Runnable接口
1 | public class AIMain { |
实现Callable接口
与Runnable接口相比,Callable接口有返回值,返回值通过FutureTask进行封装。
1 | public class AIMain { |
继承Thread类
1 | public class AIMain { |
线程之间的协作
当多个线程可以一起工作去解决某个问题时,如果某些部分必须在其他部分之前完成。那么就需要对线程进行协调。
join()
在线程中调用另一个线程join()方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
对于下面的程序来说,在线程B中,调用了a.join()方法,所以在执行时,即使B线程先执行,但是也要等线程a执行完毕之后再执行线程b。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
27
28
29
30
31
32
33
34
35
36public class AIMain {
public static void main(String[] args) {
JoinExample joinExample = new JoinExample();
joinExample.test();
}
}
class JoinExample {
private class A extends Thread {
public void run() {
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B (A a) {
this.a = a;
}
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}
}
wait()、notify()和notifyAll()
调用wait()使得线程等待某个条件满足,线程在等待时会挂起,当其他线程运行使得这个条件满足时,其他线程会调用notify()或者notifyAll()来唤醒被挂起的线程。
它们都属于Object的一部分,而不属于Thread()。
使用wait()挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么对象就无法进入同步方法或者同步控制块中,那么就无法执行notify()或者notifyAll()来唤醒挂起的线程,造成死锁。
1 | public class AIMain { |
实例说明
死锁代码
1 | public class DeadLock { |