一、线程
- 线程(Thread)是并发编程的基础,也是程序执行的最小单元,它依托进程而存在。
- 一个进程中可以包含多个线程,多线程可以共享一块内存空间和一组系统资源。
- 因此线程之间的切换,更加节省资源、更加轻量化,也因此被称为轻量级的进程。
1. 线程状态
- 在 JDK 1.5 之后,以枚举的方式被定义在
Thread
的源码中,总共包含以下 6 个状态:
- NEW:新建状态
线程被创建出来,但尚未启动时的线程状态。
- RUNNABLE:就绪状态
表示可以运行的线程状态,它可能正在运行,或者是在排队等待操作系统给它分配
CPU
资源。
- BLOCKED:阻塞等待锁的线程状态
- 表示处于阻塞状态的线程正在等待监视器锁。
- 比如等待执行
synchronized
代码块或者使用synchronized
标记的方法。
- WAITING:等待状态
- 一个处于等待状态的线程正在等待另一个线程执行某个特定的动作。
- 比如一个线程调用了
Object.wait()
方法,那它就在等待另一个线程调用Object.notify()
或Object.notifyAll()
方法。
- TIMED_WAITING:计时等待状态
- 和等待状态(
WAITING
)类似,它只是多了超时时间。- 比如调用了有超时时间设置的方法
Object.wait(long timeout)
和Thread.join(long timeout)
等这些方法时,它才会进入此状态。
- TERMINATED:终止状态
表示线程已经执行完成。
2. 线程状态源码
public enum State {
/**
* 新建状态
* 尚未启动的线程的线程状态。
*/
NEW,
/**
* 就绪状态
* 可运行线程的线程状态。可运行线程状态正在Java虚拟机中执行,但它可能等待来自操作系统的其他资源如处理器。
*/
RUNNABLE,
/**
* 阻塞等待锁的线程状态
* 线程的线程状态被阻塞,等待监视器锁定。处于阻塞状态的线程正在等待监视器锁定输入同步块/方法或调用后重新输入同步块/方法。
* {@link Object#wait() Object.wait}
*/
BLOCKED,
/**
* 等待状态
* 等待线程的线程状态。线程由于调用其中之一而处于等待状态
* 由于调用以下方法之一,线程处于等待状态:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*/
WAITING,
/**
* 计时等待状态
* 具有指定等待时间的等待线程的线程状态。线程由于调用了以下之一而处于定时等待状态
* 以下方法具有指定的正等待时间:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* 终止状态
* 终止线程的线程状态。线程已完成执行。
*/
TERMINATED;
}
3. 线程工作模式
- 线程的工作模式:
- 首先先要创建线程,并指定线程需要执行的业务方法。
- 然后再调用线程的
start()
方法,此时线程就从NEW
(新建状态)变成了RUNNABLE
(就绪状态)- 此时线程会判断要执行的方法中,有没有
synchronized
同步代码块。
- 如果有同步代码块,并且其他线程也在使用此锁,那么线程就会变为
BLOCKED
(阻塞等待状态)
- 当其他线程使用完此锁之后,线程会继续执行剩余的方法。
4. 线程工作流程
- 线程工作流程:
- 当遇到
Object.wait()
或Thread.join()
方法时,线程会变为WAITING
(等待状态状态)- 如果是带了超时时间的等待方法,那么线程会进入
TIMED_WAITING
(计时等待状态)- 当有其他线程执行了
notify()
或notifyAll()
方法之后,线程被唤醒继续执行剩余的业务方法。- 直到方法执行完成为止,此时整个线程的流程就执行完了。
- 执行流程如下图所示:
二、知识扩展
1. BLOCKED
和 WAITING
区别
- 虽然
BLOCKED
和WAITING
都有等待的含义,但二者有着本质的区别。
- 它们形成状态的调用方法不同。
BLOCKED
可以理解为当前线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源。WAITING
是因为自身调用了Object.wait()
或Thread.join()
又或LockSupport.park()
而进入等待状态,只能等待其他线程执行某个特定的动作才能被唤醒继续。
- 比如当线程因为调用了
Object.wait()
而进入WAITING
状态之后。- 则需要等待另一个线程执行
Object.notify()
或Object.notifyAll()
才能被唤醒。
2. start()
和 run()
区别
- 从源码:
start()
方法属于Thread
自身的方法,并且使用了synchronized
来保证线程安全。run()
方法为Runnable
的抽象方法,必须由调用类重写此方法,重写的run()
方法就是此线程要执行的业务方法。
- 从执行效果:
start()
方法可以开启多线程,让线程从NEW
状态转换成RUNNABLE
状态,run()
方法只是一个普通的方法。
2.1 start()
源码
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
// 状态验证,不等于 NEW 的状态会抛出异常
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
// 通知线程组,此线程即将启动
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
// 不处理任何异常,如果 start0 抛出异常,则它将被传递到调用堆栈上
}
}
}
2.2 run()
源码
// 将要运行什么。
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
3. 线程优先级
Thread
源码中,和线程优先级相关的有 3 个属性:
- 线程的优先级可以理解为:线程抢占
CPU
时间片的概率。- 优先级越高的线程,优先执行的概率就越大,但并不能保证优先级高的线程一定先执行。
// 线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;
// 线程默认优先级
public final static int NORM_PRIORITY = 5;
// 线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10
3.1 Thread.setPriority()
设置优先级
Thread.setPriority()
设置优先级。
/**
* @see #getPriority
* @see #checkAccess()
* @see #getThreadGroup()
* @see #MAX_PRIORITY
* @see #MIN_PRIORITY
* @see ThreadGroup#getMaxPriority()
*/
public final void setPriority(int newPriority) {
ThreadGroup g;
checkAccess();
// 先验证优先级的合理性
if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
throw new IllegalArgumentException();
}
if((g = getThreadGroup()) != null) {
// 优先级如果超过线程组的最高优先级,则把优先级设置为线程组的最高优先级
if (newPriority > g.getMaxPriority()) {
newPriority = g.getMaxPriority();
}
setPriority0(priority = newPriority);
}
}
4. join()
插入等待
- 在一个线程中调用
other.join()
,这时候当前线程会让出执行权给other
线程。- 直到
other
线程执行完或者过了超时时间之后,再继续执行当前线程。
join()
源码:
- 可以看出
join()
方法底层还是通过wait()
方法来实现的。
public final synchronized void join(long millis) throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
// 超时时间不能小于 0
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// 等于 0 表示无限等待,直到线程执行完为止
if (millis == 0) {
// 判断子线程 (其他线程) 为活跃线程,则一直等待
while (isAlive()) {
wait(0);
}
} else {
// 循环判断
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
4.1 未使用 join()
@Test
public void test33() {
Thread thread = new Thread(() -> {
for (int i = 1; i < 6; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程睡眠: " + i + "秒");
}
});
// 开启线程
thread.start();
// 主线程执行
for (int i = 1; i < 4; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程睡眠: " + i + "秒");
}
}
- 程序执行结果为:
- 从结果可以看出,在未使用
join()
时主子线程会交替执行。
主线程睡眠: 1秒
子线程睡眠: 1秒
主线程睡眠: 2秒
子线程睡眠: 2秒
主线程睡眠: 3秒
子线程睡眠: 3秒
4.2 使用 join()
- 把
join()
方法加入。
@Test
public void test333() throws InterruptedException {
Thread thread = new Thread(() -> {
for (int i = 1; i < 6; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子线程睡眠: " + i + "秒");
}
});
// 开启线程
thread.start();
// 1. 等待子线程先执行 2 秒钟
thread.join(2000);
// 主线程执行
for (int i = 1; i < 4; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程睡眠: " + i + "秒");
}
}
- 程序执行结果为:
- 从执行结果可以看出,添加
join()
方法之后,主线程会先等子线程执行 2 秒之后,才继续执行。
子线程睡眠: 1秒
子线程睡眠: 2秒
主线程睡眠: 1秒// thread.join(2000); 等待 2 秒之后,主线程和子线程再交替执行
子线程睡眠: 3秒
主线程睡眠: 2秒
子线程睡眠: 4秒
主线程睡眠: 3秒
子线程睡眠: 5秒
5. yield()
愿意出让CPU
- 看
Thread
的源码yield()
为本地方法,也就是说yield()
是由C
或C++
实现的。
yield()
源码:
yield()
表示给线程调度器一个当前线程愿意出让CPU
使用权的暗示,但是线程调度器可能会忽略这个暗示。
public static native void yield();
5.1 使用 yield()
@Test
public void test4() {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("线程: " + Thread.currentThread().getName() + i);
if (i == 5) {
Thread.yield();
}
}
}
};
new Thread(runnable, "A").start();
new Thread(runnable, "B").start();
}
- 程序执行结果为:
- 多次执行这段代码之后会发现,每次执行的结果都不相同。
- 这是因为
yield()
执行非常不稳定,线程调度器不一定会采纳yield()
出让CPU
使用权的建议,从而导致了这样的结果。
线程: B0
线程: A0
线程: A1
线程: A2
线程: A3
线程: A4
线程: A5
线程: B1
线程: B2
线程: B3
线程: B4
线程: B5
线程: A6
线程: A7
线程: A8
线程: A9
线程: B6
线程: B7
线程: B8
线程: B9