多线程系列 篇一 周期和状态
文章规划
知识需要连续性
本系列主要讲解多线程相关知识
算是记录 不算是精讲 但是会让你有提升 每个知识点会找一些比较热门的相关面试题进行讲解如果你总是想要学习多线程相关知识 但是又无从下手 可以
点击关注
按钮 及时收到更新消息
专栏地址:
多线程专栏文章末尾有系列文章规划图
前言
随着计算机硬件性能的不断提升,单核CPU已经不再能够满足人们的需求,多核CPU已经成为主流。在多核CPU上,线程的使用越来越重要,可以充分发挥计算机的性能。Java语言是一种面向对象的编程语言,线程是Java语言中非常重要的概念之一。本文将围绕线程生命周期和线程状态,从不同角度讲解线程的相关知识。
小故事
假设你是一个电商平台的后台开发工程师,你们平台上有很多商家和买家。商家们可以在平台上发布商品,买家们可以在平台上浏览和购买商品。现在,你们平台上的访问量越来越大,很多用户反映访问速度变慢,你们需要优化系统。
你仔细观察了一下系统,发现其中一个瓶颈在于商品列表的生成速度。当一个用户在浏览商品列表时,系统需要从数据库中查询商品信息,然后将这些信息渲染成页面。这个过程比较耗时,如果在一个请求中处理,会导致其他用户的请求被阻塞,系统响应变慢。
为了解决这个问题,你们决定采用多线程技术。具体地,当一个用户请求商品列表时,系统会开启一个新的线程,这个线程负责查询数据库,然后将结果返回给主线程。主线程在等待子线程返回结果的同时,可以处理其他用户的请求,这样就避免了请求阻塞的问题。
假设有这么一天,你收到了一个名为小明的用户反馈,说他浏览商品时遇到了问题。他点开商品列表页面,发现页面上的商品信息不断闪烁,看不清楚,很不友好。你们经过了一番调查发现,这是由于多线程技术带来的问题。具体地,当小明请求商品列表时,子线程查询数据库的速度比较慢,返回结果时,主线程正在处理其他请求,页面已经渲染出来了。这时子线程返回的结果更新了页面的数据,导致页面上的商品信息不停地闪烁。
为了解决这个问题,你们需要对多线程进行优化。具体地,你们可以引入线程池技术。线程池维护一个线程队列,每次有请求时,从线程池中取出一个空闲线程去处理请求,处理完毕后,线程不销毁,而是放回线程池中。这样就可以避免线程频繁的创建和销毁,提高系统的性能。同时,你们还可以控制线程池的大小,避免线程数量过多,导致系统的负荷过大。
通过引入线程池技术,你们成功地优化了系统,提高了系统的性能,用户体验得到了很大的改善。
线程生命周期
线程的生命周期可以分为五个阶段:
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TERMINATED
NEW
线程处于新建状态时,尚未调用 start()
方法。此时线程还没有被分配系统资源,如CPU和内存等。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
});
System.out.println("线程状态:" + thread.getState());
}
}
RUNNABLE
线程处于就绪状态时,等待系统分配CPU时间片。此时线程已经被分配了系统资源,可以运行。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
});
System.out.println("线程状态:" + thread.getState());
thread.start();
System.out.println("线程状态:" + thread.getState());
}
}
BLOCKED
线程处于阻塞状态时,等待获取一个监视器锁(synchronized关键字)。当线程获取到锁时,状态将重新进入就绪状态。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取到锁");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取到锁");
}
});
thread1.start();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1状态:" + thread1.getState());
System.out.println("线程2状态:" + thread2.getState());
}
}
WAITING
线程处于等待状态时,等待某个条件的满足,例如等待线程的结束或者等待输入输出等。当条件满足时,状态将重新进入就绪状态。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
try { Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
}
TERMINATED
线程处于终止状态时,表示线程已经执行完成,不再具有运行的状态。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
}
}
线程状态转换
线程的状态转换可以通过如下图示表示:
图中,线程状态之间的转换是有条件的,可以通过一些API方法来实现。
sleep()
sleep()
方法可以让线程进入 TIMED_WAITING
状态,在一定时间后自动转换为就绪状态。 sleep()
方法会释放当前线程的CPU资源,但不会释放当前线程持有的锁资源。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread.start();
System.out.println("线程状态:" + thread.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
}
}
wait()
wait()
方法可以让线程进入 WAITING
状态,等待其他线程的通知,通常与 notify()
或 notifyAll()
方法配合使用。调用 wait()
方法时,线程必须持有对象的锁,否则会抛出 IllegalMonitorStateException
异常。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Object lock = new Object();
Thread thread1 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程1获取到锁");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1继续执行");
}
});
Thread thread2 = new Thread(() -> {
synchronized (lock) {
System.out.println("线程2获取到锁");
lock.notify();
}
});
thread1.start();
thread2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System .println("线程1状态:" + thread1.getState());
}
join()
join()
方法可以让一个线程等待另一个线程的结束,主线程等待子线程结束的例子在前面已经介绍过了。调用 join()
方法时,线程必须处于 RUNNABLE
状态。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
System.out.println("线程1开始执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1执行完成");
});
Thread thread2 = new Thread(() -> {
System.out.println("线程2开始执行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程2执行完成");
});
thread1.start();
thread2.start();
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程1状态:" + thread1.getState());
System.out.println("线程2状态:" + thread2.getState());
}
}
synchronized
synchronized
关键字用于同步代码块或同步方法,可以保证多个线程访问同一对象时,不会出现数据不一致的情况。
当一个线程获取对象锁后,其他线程访问同步代码块或同步方法时会被阻塞,直到锁被释放。
示例代码:
public class ThreadStateDemo {
private int num = 0;
public synchronized void add() {
num++;
}
public static void main(String[] args) {
ThreadStateDemo demo = new ThreadStateDemo();
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.add();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
demo.add();
}
});
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("num = " + demo.num);
}
}
线程生命周期
线程的生命周期从创建开始,直到线程终止。线程生命周期可以分为五个阶段:
- 新建状态(New)
- 就绪状态(Runnable)
- 运行状态(Running)
- 阻塞状态(Blocked)
- 终止状态(Terminated)
如下图所示:
图中,不同状态之间的转换如下:
- 新建状态(New):线程已经被创建,但还没有调用
start()
方法,处于这个状态的线程并没有被执行。
-
就绪状态(Runnable):线程已经被调度,但还没有开始执行。
-
运行状态(Running):线程正在执行中。
-
阻塞状态(Blocked):线程因为某些原因被阻塞了,无法继续执行。阻塞状态包括以下几种:
- 等待阻塞(WAITING):线程调用了
wait()
方法,进入等待状态。 - 同步阻塞(BLOCKED):线程在等待获取锁。
- 其他阻塞(TIMED_WAITING):线程调用了
sleep()
、join()
、park()
等方法,进入等待状态。
- 等待阻塞(WAITING):线程调用了
-
终止状态(Terminated):线程执行完毕或者因为异常退出,进入终止状态。
线程的状态可以通过 Thread
类中的 getState()
方法获取。示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
System.out.println("线程开始执行");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程执行完成");
});
System.out.println("线程状态:" + thread.getState());
thread.start();
System.out.println("线程状态:" + thread.getState());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程状态:" + thread.getState());
}
}
输出结果如下:
线程状态:NEW
线程状态:RUNNABLE
线程状态:RUNNING
线程状态:TERMINATED
面试问题
- 什么是线程生命周期?
线程生命周期是指线程从创建到销毁的整个过程,包括线程的状态转换以及对应的操作。
- 线程有哪几种状态?
- NEW:线程已经被创建,但是还没有开始执行。
- RUNNABLE:线程已经准备好运行,但是可能还没有获得 CPU 资源。
- RUNNING:线程正在运行中。
- BLOCKED:线程被阻塞了,无法继续执行。
- WAITING:线程在等待某个条件的出现。
- TIMED_WAITING:线程在等待一段时间后自动唤醒。
- TERMINATED:线程执行完成或者异常终止。
- 如何创建线程? 答:可以通过继承 Thread 类或者实现 Runnable 接口来创建线程。例如:
// 继承 Thread 类
public class MyThread extends Thread {
@Override
public void run() {
// 线程执行的代码
}
}
// 实现 Runnable 接口
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程执行的代码
}
}
// 创建线程并启动
MyThread thread1 = new MyThread();
thread1.start();
MyRunnable runnable = new MyRunnable();
Thread thread2 = new Thread(runnable);
thread2.start();
- 如何启动线程?
可以通过调用线程对象的 start() 方法来启动线程,例如:
MyThread thread = new MyThread();
thread.start();
- 如何停止线程?
可以通过调用线程的 interrupt() 方法来请求线程停止执行,并在线程中判断该方法是否被调用。例如:
public class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {
// 线程执行的代码
}
}
}
// 停止线程
MyThread thread = new MyThread();
thread.start();
thread.interrupt();
- 如何等待线程执行完成?
可以调用线程对象的 join() 方法来等待线程执行完成,例如:
MyThread thread = new MyThread();
thread.start();
thread.join();
- 如何使线程进入阻塞状态
可以调用线程的 sleep() 方法或者在 synchronized 块中调用 wait() 方法来使线程进入阻塞状态,例如:
// sleep() 方法
public class MyThread extends Thread {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
// wait() 方法
public class MyObject {
public synchronized void method() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 如何使线程重新进入就绪状态
答:可以通过调用线程对象的 interrupt() 方法或者在 synchronized 块中调用 notify() 或 notifyAll() 方法来使线程重新进入就绪状态,例如:
// interrupt() 方法
public class MyThread extends Thread {
@Override
public void run() {
while (!Thread.interrupted()) {
// 线程执行的代码
}
}
}
// notify() 方法
public class MyObject {
public synchronized void method() {
notify();
}
}
- 如何判断线程是否在运行中? 答:可以通过判断线程对象的状态来判断线程是否在运行中,例如:
Thread.State state = thread.getState();
if (state == Thread.State.RUNNING) {
// 线程正在运行中
} else {
// 线程不在运行中
}
- 如何处理线程运行过程中的异常? 答:可以通过 try-catch 块来捕获线程执行过程中的异常,并进行相应的处理,例如:
public class MyThread extends Thread {
@Override
public void run() {
try {
// 线程执行的代码
} catch (Exception e) {
e.printStackTrace();
}
}
}
总结
本文介绍了线程的生命周期和状态,以及线程同步的相关内容。线程是多任务处理的基本单位,掌握线程的生命周期和状态对于编写高效的多线程程序至关重要。在编写多线程程序时,需要注意线程同步的问题,避免数据不一致的情况。
规划
第一篇、线程生命周期 和 线程状态
第二篇、线程创建方式 深度总结 预计周五更新
规划:
转载自:https://juejin.cn/post/7211046353924014141