# 线程与多线程
# 了解高并发
高并发指的是:同一时间系统可以承受的并发数。
TPS 和 QPS 是衡量系统并发性能的两个常见指标,尤其在讨论数据库、Web 服务器或其他类型的服务时经常被提及。它们各自代表不同的含义:
- TPS(Transactions Per Second):每秒事务数。这是一个度量单位,用于表示一个系统或服务每秒钟能够处理的事务数量。这里的“事务”可以指代任何预定义的操作集合,比如在数据库中完成的一次完整查询和更新操作。
- QPS(Queries Per Second):每秒查询率。这是另一个度量单位,用来衡量系统或服务每秒能够处理的查询请求数量。通常用于描述Web服务器或数据库管理系统等能够处理的查询请求的数量。
简单来说,TPS 更侧重于整个事务的处理能力,而 QPS 则专注于查询请求的处理速度。两者都是评估系统性能的重要指标,特别是在高并发环境下,这些指标可以帮助了解系统的最大承载能力和响应效率。
# 如何支撑高并发
硬件资源:决定了应用系统性能的上限,主要包括以下硬件:
1.CPU的核心数决定着同一时间可以处理的并发任务数量上限
2.内存的大小,因为内存打了就可以存储更多的热点数据,又因为内存的IO速度很快,所以加快了数据处理的速度。
3.网卡 决定了网络数据的传输效率和响应时间。例如网卡的带宽是指在单位时间内可以传输的数据量。我们生活中100M,50M就是指定带宽。
软件层面主要解决一下三个问题:
1.充分的利用CPU的多核的能力
2.解决IO速度的影响。(IO -> 数据库交互 ->刷到磁盘 (IO速度影响着响应速度,那么怎么去提升IO速度呢) 可以使用内存/缓存/异步刷盘..;数据库做分库分表,分布式缓存,分布式消息中间件)
3.单节点系统瓶颈,可以多个计算机组成一个分布式系统。
# 一、线程与多线程
进程和线程是操作系统中的两个核心概念,它们都涉及到程序的执行,但有着不同的特点和用途。下面分别介绍进程和线程,并比较它们之间的区别。
# 进程(Process)
进程是指操作系统进行资源分配和调度的基本单位。它是程序执行过程中的一个实例,包含了正在运行的程序的代码、数据以及执行状态。每个进程都有独立的内存空间,包括代码区、数据区、堆栈区等,这使得进程间的数据通常是相互隔离的。进程的状态可以包括新建、就绪、运行、阻塞、终止等。
- 资源拥有:进程是系统资源分配的基本单位,它拥有独立的地址空间、文件描述符等资源。
- 隔离性:不同进程之间默认是相互隔离的,一个进程崩溃通常不会直接影响其他进程。
- 开销:创建和销毁进程需要较大的系统开销,因为需要分配和回收资源。
# 线程(Thread)
线程是进程内的一个执行单元,是操作系统能够进行运算调度的最小单位。一个进程中可以包含多个线程,这些线程共享进程的资源,如内存地址空间、文件描述符等。线程之间的切换开销比进程小得多,因此多线程编程在处理并发任务时非常有效。
- 资源共享:同一进程下的所有线程共享该进程的资源,包括内存地址空间和文件资源。
- 轻量级:与进程相比,线程是一种更为轻量级的执行单位,创建、撤销和切换所需的开销更小。
- 通信便捷:由于线程共享相同的地址空间,线程间的通信更加方便高效。
# 进程与线程的区别
- 资源占用:进程是资源分配的基本单位,拥有独立的资源;而线程是处理器任务调度和执行的基本单位,线程之间共享所属进程的资源。
- 系统开销:进程的创建、销毁和切换开销大于线程,因为进程间的数据和状态完全独立,而线程之间共享了大部分资源。
- 通信方式:进程间通信(IPC)较为复杂,通常需要使用专门的机制,如管道、消息队列等;而线程间可以直接访问相同内存区域,通信更为直接。
- 稳定性:由于进程间相互独立,一个进程的崩溃不会直接影响其他进程;而一个进程中某个线程的问题可能会影响整个进程的所有线程。
理解进程和线程的区别对于开发高效的并发程序至关重要。选择合适的设计取决于具体的应用场景和需求。
# 多线程
多线程是指在一个程序中同时运行多个线程,每个线程执行不同的任务。它是实现并发处理的一种方式,通过允许多个操作并行执行来提高程序的效率和响应速度。以下是关于多线程的一些重要概念、优势以及挑战。
# 多线程的优势
- 提高效率:在多核处理器上,多个线程可以同时执行,从而更高效地利用CPU资源。
- 增强响应性:对于GUI应用程序而言,使用多线程可以让耗时的操作在后台线程中进行,保持用户界面的流畅性和响应性。
- 资源共享:同一进程下的所有线程共享该进程的资源(如内存地址空间、文件描述符等),这使得线程间的通信和数据共享更加方便。
- 简化编程模型:在某些情况下,将复杂的应用程序分解为多个相对独立的任务(线程)可以使程序结构更清晰,便于管理和维护。
# 二、实现线程的方式
在Java中,实现线程的方式大体上分为三种,通过继承Thread类、实现Runnable接口,实现Callable接口。简单的示例代码分别如下所示。
- 继承Thread类代码
package io.binghe.concurrent.executor.test;
/**
* @author binghe
* @version 1.0.0
* @description 继承Thread实现线程
*/
public class ThreadTest extends Thread {
@Override
public void run() {
//TODO 在此写在线程中执行的业务逻辑
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 实现Runnable接口代码
package io.binghe.concurrent.executor.test;
/**
* @author binghe
* @version 1.0.0
* @description 实现Runnable实现线程
*/
public class RunnableTest implements Runnable {
@Override
public void run() {
//TODO 在此写在线程中执行的业务逻辑
}
}
2
3
4
5
6
7
8
9
10
11
12
13
- 实现Callable接口代码
package io.binghe.concurrent.executor.test;
import java.util.concurrent.Callable;
/**
* @author binghe
* @version 1.0.0
* @description 实现Callable实现线程
*/
public class CallableTest implements Callable<String> {
@Override
public String call() throws Exception {
//TODO 在此写在线程中执行的业务逻辑
return null;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 注意事项
直接调用run方法和通过Thread.start()方法的区别
# 调用 run() 方法
当你直接调用 run() 方法时,它就像普通方法调用一样执行,并不会启动一个新的线程。代码仍然运行在当前线程中。
特点:
- 不会创建新线程:直接调用
run()方法并不会启动新的线程,而是以普通方法的方式执行。 - 顺序执行:代码会按照调用的顺序依次执行,没有并发行为。
- 简单方法调用:
run()方法只是一个普通的 Java 方法,调用它不会涉及线程的调度。
# Thread.start()方法
当通过 Thread.start() 方法启动线程时,JVM 会为该线程分配资源并调度其运行,最终会调用线程的 run() 方法。此时,代码会在新线程中执行。
特点:
- 启动新线程:
start()方法会通知 JVM 创建一个新线程,并让新线程执行run()方法。 - 并发执行:新线程与主线程(或其他线程)可以并发运行。
- 线程调度:由 JVM 的线程调度器决定线程的执行顺序。
- 只能调用一次:每个线程对象的
start()方法只能被调用一次,多次调用会抛出IllegalThreadStateException。
# 开启一个线程的发生了什么
首先我们通过执行一个main方法,开启虚拟机加载类执行到Thread.start()方法的时候就会通过JVM来调用操作系统的底层方法告诉CPU我要创建一个新的线程,创建成功之后我们的线程等待CPU的调度进行执行,当CPU把创建的线程交给CPU核心区执行

# 1. Java 层面:主线程执行 Thread.start() 方法
- 当程序运行时,JVM 启动并加载
main方法所在的类。 - 在main方法中,当我们调用new Thread(...).start()时:
- JVM 会检查当前线程对象是否已经启动过(即
start()方法是否被调用过)。如果已启动,再次调用会抛出IllegalThreadStateException。 - 如果是首次调用
start(),JVM 会为该线程分配必要的资源(如栈空间等),并将线程注册到线程调度器中。 - JVM 调用底层的本地方法(Native Method),将线程创建请求传递给操作系统的线程管理模块。
- JVM 会检查当前线程对象是否已经启动过(即
# 2. 操作系统层面:创建线程
- 操作系统接收到 JVM 的线程创建请求后,会执行以下步骤:
- 线程结构初始化:
- 操作系统会为新线程分配一个线程控制块(TCB, Thread Control Block),用于存储线程的状态信息(如寄存器值、栈指针、优先级等)。
- 为新线程分配独立的栈空间(用户态栈或内核态栈,取决于线程模型)。
- 线程状态设置:
- 新线程被标记为“就绪”状态,进入操作系统的线程调度队列。
- 线程注册到调度器:
- 操作系统将新线程注册到线程调度器中,等待 CPU 时间片分配。
- 线程结构初始化:
# 3. CPU 层面:线程调度与执行
线程调度:
- CPU 的调度器根据一定的调度算法(如时间片轮转、优先级调度等)选择合适的线程来执行。
- 当新线程被调度器选中时,它会从“就绪”状态变为“运行”状态。
线程执行:
- CPU 核心加载线程的上下文信息(从 TCB 中恢复寄存器、栈指针等)。
- CPU 开始执行线程的入口函数(对于 Java 线程来说,就是
run()方法中的代码逻辑)。 - 执行过程中,线程可能会因 I/O 操作、锁竞争等原因进入“阻塞”状态,或者因时间片耗尽回到“就绪”状态。
Java 层面 | v main() -> new Thread(...) -> start() | v JVM 层面 | v 调用 Native 方法 -> 操作系统线程管理 | v 操作系统层面 | v 线程初始化 -> 注册到调度器 -> 进入就绪队列 | v CPU 层面 | v 调度器分配时间片 -> CPU 执行 run() 方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 三、线程的生命周期
# 1.生命周期
一个线程从创建,到最终的消亡,需要经历多种不同的状态,而这些不同的线程状态,由始至终也构成了线程生命周期的不同阶段。线程的生命周期可以总结为下图。

其中,几个重要的状态如下所示。
- NEW:初始状态,线程被构建,但是还没有调用start()方法。
- RUNNABLE:可运行状态,可运行状态可以包括:运行中状态和就绪状态。
- BLOCKED:阻塞状态,处于这个状态的线程需要等待其他线程释放锁或者等待进入synchronized。
- WAITING:表示等待状态,处于该状态的线程需要等待其他线程对其进行通知或中断等操作,进而进入下一个状态。
- TIME_WAITING:超时等待状态。可以在一定的时间自行返回。
- TERMINATED:终止状态,当前线程执行完毕。
# 2.代码示例
为了更好的理解线程的生命周期,以及生命周期中的各个状态,接下来使用代码示例来输出线程的每个状态信息。
- WaitingTime
创建WaitingTime类,在while(true)循环中调用TimeUnit.SECONDS.sleep(long)方法来验证线程的TIMED_WARTING状态,代码如下所示。
package io.binghe.concurrent.executor.state;
import java.util.concurrent.TimeUnit;
/**
* @author binghe
* @version 1.0.0
* @description 线程不断休眠
*/
public class WaitingTime implements Runnable{
@Override
public void run() {
while (true){
waitSecond(200);
}
}
//线程等待多少秒
public static final void waitSecond(long seconds){
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
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
- WaitingState
创建WaitingState类,此线程会在一个while(true)循环中,获取当前类Class对象的synchronized锁,也就是说,这个类无论创建多少个实例,synchronized锁都是同一个,并且线程会处于等待状态。接下来,在synchronized中使用当前类的Class对象的wait()方法,来验证线程的WAITING状态,代码如下所示。
package io.binghe.concurrent.executor.state;
/**
* @author binghe
* @version 1.0.0
* @description 线程在Warting上等待
*/
public class WaitingState implements Runnable {
@Override
public void run() {
while (true){
synchronized (WaitingState.class){
try {
WaitingState.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- BlockedThread
BlockedThread主要是在synchronized代码块中的while(true)循环中调用TimeUnit.SECONDS.sleep(long)方法来验证线程的BLOCKED状态。当启动两个BlockedThread线程时,首先启动的线程会处于TIMED_WAITING状态,后启动的线程会处于BLOCKED状态。代码如下所示。
package io.binghe.concurrent.executor.state;
/**
* @author binghe
* @version 1.0.0
* @description 加锁后不再释放锁
*/
public class BlockedThread implements Runnable {
@Override
public void run() {
synchronized (BlockedThread.class){
while (true){
WaitingTime.waitSecond(100);
}
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- ThreadState
启动各个线程,验证各个线程输出的状态,代码如下所示。
package io.binghe.concurrent.executor.state;
/**
* @author binghe
* @version 1.0.0
* @description 线程的各种状态,测试线程的生命周期
*/
public class ThreadState {
public static void main(String[] args){
new Thread(new WaitingTime(), "WaitingTimeThread").start();
new Thread(new WaitingState(), "WaitingStateThread").start();
//BlockedThread-01线程会抢到锁,BlockedThread-02线程会阻塞
new Thread(new BlockedThread(), "BlockedThread-01").start();
new Thread(new BlockedThread(), "BlockedThread-02").start();
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
运行ThreadState类,如下所示。

可以看到,未输出任何结果信息。可以在命令行输入“jps”命令来查看运行的Java进程。
c:\>jps
21584 Jps
17828 KotlinCompileDaemon
12284 Launcher
24572
28492 ThreadState
2
3
4
5
6
可以看到ThreadSate进程的进程号为28492,接下来,输入“jstack 28492”来查看ThreadSate进程栈的信息,如下所示。
c:\>jstack 28492
2020-02-15 00:27:08
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.202-b08 mixed mode):
"DestroyJavaVM" #16 prio=5 os_prio=0 tid=0x000000001ca05000 nid=0x1a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"BlockedThread-02" #15 prio=5 os_prio=0 tid=0x000000001ca04800 nid=0x6eb0 waiting for monitor entry [0x000000001da4f000]
java.lang.Thread.State: BLOCKED (on object monitor)
at io.binghe.concurrent.executor.state.BlockedThread.run(BlockedThread.java:28)
- waiting to lock <0x0000000780a7e4e8> (a java.lang.Class for io.binghe.concurrent.executor.state.BlockedThread)
at java.lang.Thread.run(Thread.java:748)
"BlockedThread-01" #14 prio=5 os_prio=0 tid=0x000000001ca01800 nid=0x6e28 waiting on condition [0x000000001d94f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at io.binghe.concurrent.executor.state.WaitingTime.waitSecond(WaitingTime.java:36)
at io.binghe.concurrent.executor.state.BlockedThread.run(BlockedThread.java:28)
- locked <0x0000000780a7e4e8> (a java.lang.Class for io.binghe.concurrent.executor.state.BlockedThread)
at java.lang.Thread.run(Thread.java:748)
"WaitingStateThread" #13 prio=5 os_prio=0 tid=0x000000001ca06000 nid=0x6fe4 in Object.wait() [0x000000001d84f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000780a7b488> (a java.lang.Class for io.binghe.concurrent.executor.state.WaitingState)
at java.lang.Object.wait(Object.java:502)
at io.binghe.concurrent.executor.state.WaitingState.run(WaitingState.java:29)
- locked <0x0000000780a7b488> (a java.lang.Class for io.binghe.concurrent.executor.state.WaitingState)
at java.lang.Thread.run(Thread.java:748)
"WaitingTimeThread" #12 prio=5 os_prio=0 tid=0x000000001c9f8800 nid=0x3858 waiting on condition [0x000000001d74f000]
java.lang.Thread.State: TIMED_WAITING (sleeping)
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at io.binghe.concurrent.executor.state.WaitingTime.waitSecond(WaitingTime.java:36)
at io.binghe.concurrent.executor.state.WaitingTime.run(WaitingTime.java:29)
at java.lang.Thread.run(Thread.java:748)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000000001c935000 nid=0x6864 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x000000001c88c800 nid=0x6a28 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x000000001c880000 nid=0x6498 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x000000001c87c000 nid=0x693c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x000000001c87b800 nid=0x5d00 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x000000001c862000 nid=0x6034 runnable [0x000000001d04e000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x0000000780b2fd88> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x0000000780b2fd88> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:64)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001c788800 nid=0x6794 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001c7e3800 nid=0x3354 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001c771000 nid=0x6968 in Object.wait() [0x000000001cd4f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000780908ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x0000000780908ed0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c770800 nid=0x6590 in Object.wait() [0x000000001cc4f000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x0000000780906bf8> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x0000000780906bf8> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000000001a979800 nid=0x5c2c runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000000033b9000 nid=0x4dc0 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000000033ba800 nid=0x6690 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000000033bc000 nid=0x30b0 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000000033be800 nid=0x6f68 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000000033c1000 nid=0x6478 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000000033c2000 nid=0x4fe4 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00000000033c5000 nid=0x584 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00000000033c6800 nid=0x6988 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x000000001c959800 nid=0x645c waiting on condition
JNI global references: 12
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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
由以上输出的信息可以看出:名称为WaitingTimeThread的线程处于TIMED_WAITING状态;名称为WaitingStateThread的线程处于WAITING状态;名称为BlockedThread-01的线程处于TIMED_WAITING状态;名称为BlockedThread-02的线程处于BLOCKED状态。
注意:使用jps结合jstack命令可以分析线上生产环境的Java进程的异常信息。
也可以直接点击IDEA下图所示的图表直接打印出线程的堆栈信息。

输出的结果信息与使用“jstack 进程号”命令输出的信息基本一致。
