线程概念
任务: 一个“任务”常常通过实现某种接口( 如 Java 中的 Runnable 或 Callable)来定义 。 线程是执行任务的“工人”,任务是“要完成的具体工作”。
线程 (Thread):
- 一个进程内独立的执行单元。
- 是CPU调度的最小单位。
- 多个线程共享同一个进程的资源(如内存空间)。
进程 (Process):
- 操作系统中运行的一个程序的实例。
- 拥有独立的内存空间和系统资源。
- 一个进程可以包含一个或多个线程。
并发: 指的是多个任务在同一个时间段内看似同时进行。 在单核CPU上实现并发是通过时间片轮转
并行: 指的是多个任务在同一个时刻真正地同时执行。 这需要多核CPU或多处理器系统,每个核心或处理器执行一个任务。
调度器: 操作系统或JVM的一部分,负责决定哪个线程在何时、哪个CPU核心上运行,以及线程状态之间的切换。
同步 : 用于协调多个线程对共享资源的访问,防止出现数据不一致或损坏。
竞态条件: 当多个线程尝试同时访问和修改共享数据时,由于执行顺序不确定,导致最终结果依赖于线程运行的“竞速”情况,
上下文切换 : CPU从一个线程切换到另一个线程执行时,需要保存当前线程的运行状态(如寄存器值、程序计数器等),然后加载下一个线程的运行状态。频繁的上下文切换会带来性能开销。
守护线程:主要用于执行那些为用户线程提供后台服务或支持性工作的任务。守护线程很像一个辅助线程,在后台不停的执行一些操作
普通方法调用 vs 多线程

Thread 类
上面讲了线程的一些术语和概念,线程就是在程序中执行任务的单元,多个线程就有多个执行任务的单元。相当于多个人干活,看看 java 怎么实现多线程。
Thread 类
Thread 单词含义 细线,【计】线程。
定义:
java.lang.Thread类是 Java 中定义和控制线程的核心类。- 它表示程序中的一个执行线程。一个程序可以同时运行多个线程。
作用:
- 允许程序创建和管理自己的执行流程(线程)。
- 是实现多线程的基础。
使用方式
继承 Thread 类并重写 run() 方法:
- 创建一个新类,extends
Thread。 - 在新类中 override
public void run()方法,将线程要执行的代码逻辑放入其中。 - 创建该类的实例:
MyThread thread = new MyThread(); - 调用线程对象的
start()方法来启动线程:thread.start();
-
start()方法是启动一个新线程的关键,新线程会自动调用你重写的run()方法。- 注意:切勿直接调用
run()方法 (thread.run()),那样只是在当前线程中像调用普通方法一样执行run()中的代码,不会创建新线程。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程运行:" + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程
}
}
Runnable 接口
定义:
java.lang.Runnable是一个函数式接口,定义在java.lang包中。- 它表示一个可运行的任务。
作用:
- 用来封装需要在单独线程中执行的业务逻辑代码。
- 它将任务的定义与线程的控制(如启动、调度)分离开来。
核心方法:
public abstract void run();
-
- 这是一个无参数、无返回值的方法。
- 这个方法体中包含了需要在线程中执行的具体任务代码。
- 注意:此方法不能直接声明抛出受查异常(Checked Exception),如果任务代码可能抛出受查异常,需要在
run()方法内部捕获并处理,或者转换为运行时异常抛出。
如何使用 Runnable 创建和运行线程:
这是创建线程的推荐方式之一(相比于继承 Thread 类)。
- 创建一个类,实现
Runnable接口: Java
class MyTask implements Runnable {
@Override
public void run() {
// 线程要执行的任务代码
System.out.println("Task is running in thread: " + Thread.currentThread().getName());
}
}
- 创建实现
Runnable接口的类的对象(这就是你的任务实例): Java
main{
MyTask task = new MyTask();
}
- 创建一个
java.lang.Thread对象,并将Runnable任务实例作为构造方法的参数传入: Java
main{
MyTask task = new MyTask();
Thread thread = new Thread(task);
// 还可以指定线程名称:
// Thread thread = new Thread(task, "MyWorkerThread");
}
- 调用
Thread对象的start()方法来启动线程: Java
main{
MyTask task = new MyTask();
Thread thread = new Thread(task);
// 还可以指定线程名称:
// Thread thread = new Thread(task, "MyWorkerThread");
thread.start(); // 启动新线程,并在这个新线程中执行 task 对象的 run() 方法
}
Runnable 比 Thread 更灵活
Thread 的单继承限制 :
- Java 类只能继承一个父类。如果你通过继承
Thread类来定义线程任务,那么你的类就不能再继承其他任何类了。这在你的类需要同时拥有某个父类的特性(比如一个 Swing 组件类MyPanel extends JPanel)又想作为线程任务执行时,就会遇到冲突。 - 而实现
Runnable接口则没有这个限制。你的类可以继承其他类,同时实现Runnable接口,从而既拥有父类的功能,又具备作为线程任务执行的能力(class MyPanel extends JPanel implements Runnable { ... })。这是Runnable最直接和重要的灵活之处。
任务逻辑与线程对象的解耦 :
- 继承
Thread类时,你的类就是一个线程,任务逻辑 (run方法) 是这个线程对象的一部分。 (子类继承Thread 类,重写Run方法,那你的子类不就是一个线程类) - 实现
Runnable接口时,你的类定义的是一个任务 (Task),类时任务,能给别的任意 Thread 类执行,任务和线程就解耦了!
更好的代码结构和复用性 :
- 由于任务逻辑是独立的
Runnable对象,你可以创建多个Thread对象来运行同一个Runnable实例(如果任务是线程安全的),或者多个Thread对象运行同一个Runnable类创建的不同实例。Runnable对象可复用。
集成线程池:
ExecutorService 的设计就是直接接收 Runnable 或 Callable 类型的任务。你只需要将你的任务逻辑封装到 Runnable 对象中,就可以直接提交给线程池执行。
并发问题
多个线程同时操作同一个资源,导致数据不一致。
多线程+for 要注意的问题
看代码:
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
if(Thread.currentThread().getName().equals(rabbit) && i % 2 == 0){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println(Thread.currentThread().getName() + "走了" + i + "步");
boolean flag = gameOver(i);
if (flag){
break;
}
}
}
public static void main(String[] args) {
new Thread(race,tortoise).start();
new Thread(race,rabbit).start();
}
两个线程,一个叫 tortoise, 一个叫 rabbit, tortoise 先过了重点,然后 break for 循环,此时 break 掉的也只是 tortoise线程的循环,rabbit 畅通无阻,继续运行。所以需要加一个条件,把 rabbit 也 break 了。就是把下面注释的 if 解开
public boolean gameOver(int steps) {
// if(winner != null){
// return true;
// }
if (steps >= 3){
winner = Thread.currentThread().getName();
System.out.println(winner + "到达终点");
return true;
}
return false;
}
断点调试问题
断点的默认行为是“暂停所有线程", 调试器会把整个 JVM 的所有线程都挂起。
如果你想暂停某一个线程,那么右键红色断点,如下图设置
Thread.currentThread().getName().equals(Race.tortoise)

一些对线程的操作
线程状态
new Thread -> Thread.start() -> 等待 cpu 调度 -> 运行状态 -> Sleep 阻塞状态 ->线程结束,kill

基本操作

停止线程
- 官方说不建议用 stop
- 建议使用一个条件,让线程正常结束停止,或者程序自然停止
线程休眠:
sleep 存在异常 InterruptedException , 需要try-catch
sleep 时间到达后就进入就绪状态,等待 cpu 调度
sleep 可以模拟网络延时,倒计时等
sleep 不会释放锁
线程礼让(Yield):
礼让线程 : 让当前正在执行的线程暂停,但不阻塞。将线程转为就绪状态。
让 cpu 重新调度,不过不一定礼让成功。就是回到就绪状态,重新竞速。
实现:Thread.Yield() 简单啊!
线程强制执行:
使用Thread.join 强制执行此线程
示例:
public class MinimalJoinExample {
public static void main(String[] args) {
System.out.println("主线程开始."); // 1. 主线程开始
// 创建并启动一个子线程
Thread childThread = new Thread(() -> {
System.out.println("子线程开始."); // 2. 子线程开始
try {
// 模拟子线程执行任务(例如,耗时操作)
Thread.sleep(2000); // 子线程休眠 2 秒
} catch (InterruptedException e) {
System.out.println("子线程被中断.");
}
System.out.println("子线程结束."); // 3. 子线程结束
});
childThread.start(); // 启动子线程
System.out.println("主线程等待子线程完成..."); // 4. 主线程在等待
try {
// *** 核心代码:主线程在这里调用 join() 等待子线程 ***
childThread.join(); // 主线程会在这里暂停,直到 childThread 执行完毕
} catch (InterruptedException e) {
System.out.println("主线程在等待时被中断.");
}
System.out.println("主线程继续执行."); // 5. 子线程结束后,主线程恢复
System.out.println("主线程结束."); // 6. 主线程最终结束
}
}
观察线程状态(State):
Thead.State 这是一个枚举类型
Thread.State state = thread.getState() 获取到线程状态
线程优先级(Priority):
1-10个等级,优先级越高,权重越大,cpu 越可能调用。
主线程优先级不可更改。
实现:
设置了优先级再启动,反之没用。
Thread.setPriority(1);
Thread.start();
守护线程(Deamon):
主要用于执行那些为用户线程提供后台服务或支持性工作的任务。
守护线程很像一个辅助线程,在后台不停的执行一些操作
实现:
Thread.setDeamon(true); //默认是 false
线程同步
术语:
阻塞 (Blocking): 当一个线程尝试获取一个锁,但锁当前被其他线程持有,该线程就会暂停执行,进入等待状态,直到锁被释放。
可中断 (Interruptible): 指的是线程在执行某个阻塞操作(比如 Thread.sleep(), Object.wait(), 或者这里的 lockInterruptibly())时,如果其他线程调用了这个阻塞线程的 interrupt() 方法,该阻塞操作会立即停止等待,并抛出一个 InterruptedException,同时清除线程的中断标志。
可重入性: 可重入性确实是它们的一个基本且非常重要的特性。如果一个线程已经成功获取了某个锁,那么当这个线程再次尝试获取由同一个锁保护的其他(或同一个)资源时,它不会被阻塞,能够直接再次进入。
Synchronized :一个关键字写在方法或代码块上
Lock
Lock :JUC包下,它提供了一个更灵活、更广泛的锁定操作,作为 synchronized 关键字的替代或补充。
Lock 是一个接口, 在 Java 标准库 java.util.concurrent.locks 包中 ,主要的实现类 ReentrantLock , ReentrantReadWriteLock 。
ReentrantLock :是 Lock 常用的实现类,可以通过构造方法实现公平锁,公平锁会尽量保证线程获取锁的顺序是按照它们请求锁的顺序来,等待时间越长越优先。
Lock 接口的方法
表示了锁的多样性
lock(): 与synchronized类似,阻塞直到获取锁(不可响应中断)。
tryLock(): 非阻塞尝试。如果能立即获取锁,返回true;否则,立即返回false。
tryLock(long time, TimeUnit unit): 定时尝试。在指定的时间内尝试获取锁,如果在指定时间内未能获取到锁(即超时),方法返回false表示获取锁失败。如果在等待过程中线程被中断,使用 interrupt() ,方法会抛出InterruptedException异常,不会返回true或false,也不会获取锁。
lockInterruptibly(): 可中断阻塞。如果锁被占用,当前线程阻塞等待;但如果等待过程中线程被中断,会抛出InterruptedException并停止等待。 关键点: 当线程正在阻塞等待这个锁的过程中,如果有其他线程调用了该阻塞线程的interrupt()方法来中断它,那么lockInterruptibly()方法会立即停止等待,放弃获取锁,并向当前线程抛出一个InterruptedException。
Lock: 通过newCondition()方法可以创建多个Condition对象与同一个Lock关联。每个Condition对象都有自己的等待队列,队列存线程, 线程在调用condition1.await()时,只会进入condition1的等待队列 。线程可以在特定的Condition上等待 (Condition.await()),并只通过对该Condition调用signal()或signalAll()来唤醒等待在这个特定条件上的线程。
Lock 提供了更强的灵活性,但代价是需要手动管理锁的获取和释放,特别是必须将 unlock() 放在 finally 块中,否则容易造成死锁或资源泄露
为什么要用 lockInterruptibly()?
Synchronized 是个简单好用不灵活的家伙,不能中断,傻傻的等
在某些场景下,你可能不希望一个线程无限期地等待一个锁。例如,在一个 GUI 应用中,用户可能点击了一个“取消”按钮,你希望一个正在后台等待资源的线程能够响应这个“取消”信号并终止等待。如果使用 lock(),你很难优雅地让这个等待锁的线程停止;但如果使用 lockInterruptibly(),你就可以通过中断它来实现取消操作。
实现
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockInterruptiblyExample {
// 共享的锁对象
private static final Lock sharedLock = new ReentrantLock();
// --- 任务 1: 持有锁一段时间的线程 ---
static class LockHolderTask implements Runnable {
@Override
public void run() {
sharedLock.lock(); // <-- 使用 lock() 方法获取锁 (不可中断阻塞)
System.out.println(Thread.currentThread().getName() + " 已获取锁并正在持有.");
try {
// 模拟长时间持有锁
System.out.println(Thread.currentThread().getName() + " 持有锁中,休眠 5 秒...");
Thread.sleep(5000); // 休眠期间是可中断的,但这里我们关注的是锁持有本身
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 在持有锁期间被中断.");
// 处理中断,例如重新设置中断标志
Thread.currentThread().interrupt();
} finally {
// 释放锁
sharedLock.unlock();
System.out.println(Thread.currentThread().getName() + " 释放了锁.");
}
}
}
// --- 任务 2: 尝试以可中断方式获取锁的线程 ---
static class InterruptibleLockTask implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " 尝试以可中断方式获取锁...");
try {
// *** 核心代码:尝试以可中断方式获取锁 ***
sharedLock.lockInterruptibly(); // 如果锁被占用,会在这里阻塞,并且如果被中断,会抛异常
// 如果代码执行到这里,说明线程成功获取了锁
// 在我们演示被中断的例子中,通常不会走到这里
System.out.println(Thread.currentThread().getName() + " 成功获取锁 (如果被中断了则不应看到此消息).");
try {
// 正常情况下在这里执行需要锁保护的代码...
System.out.println(Thread.currentThread().getName() + " 执行临界区代码...");
Thread.sleep(2000); // 模拟工作
} finally {
sharedLock.unlock(); // 记得释放锁!
System.out.println(Thread.currentThread().getName() + " 释放了锁.");
}
} catch (InterruptedException e) {
// *** 重点:在这里捕获中断异常 ***
// 这意味着线程在 lockInterruptibly() 处阻塞等待锁时被中断了
System.out.println(Thread.currentThread().getName() + " 在等待锁时被中断! 它**没有获取到锁**.");
// 因为没有获取到锁,所以不需要调用 unlock()
} finally {
// 这里可以做一些无论是否获取到锁都需要进行的清理工作
// System.out.println(Thread.currentThread().getName() + " 完成 try/catch 块的执行.");
}
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("主线程启动.");
// 1. 启动锁持有者线程,让它先获取并持有锁
Thread lockHolderThread = new Thread(new LockHolderTask(), "锁持有者线程");
lockHolderThread.start();
// 给予锁持有者线程足够的时间来获取锁 (例如,休眠 1 秒)
Thread.sleep(1000);
// 2. 启动可中断等待者线程,它会尝试获取锁并被阻塞
Thread interruptibleWaiterThread = new Thread(new InterruptibleLockTask(), "可中断等待者线程");
interruptibleWaiterThread.start();
// 给予可中断等待者线程足够的时间到达 lockInterruptibly() 并进入阻塞状态 (例如,再休眠 1 秒)
Thread.sleep(1000);
// 3. *** 从主线程中断正在等待锁的可中断等待者线程 ***
System.out.println("主线程中断 [" + interruptibleWaiterThread.getName() + "]...");
interruptibleWaiterThread.interrupt(); // 调用 interrupt()
// 等待一段时间,让所有线程有机会执行或结束
Thread.sleep(5000); // 等待,让锁持有者线程完成并释放锁
System.out.println("主线程结束.");
}
}
线程池
核心接口类
Executor: 最基础的接口,只定义了一个方法void execute(Runnable command),用于提交一个Runnable任务。ExecutorService: 继承自Executor,是线程池的核心接口。它添加了更全面的功能:
-
- 生命周期管理:
shutdown(),shutdownNow(),isShutdown(),isTerminated(),awaitTermination()。 - 任务提交: 除了
execute(),还提供submit()方法,可以提交Runnable或Callable任务,并返回代表任务执行结果的Future对象。 - 批量任务提交:
invokeAll(),invokeAny()
- 生命周期管理:
ThreadPoolExecutor是线程池的真正核心 ,实现了ExecutorExecutorService提供了丰富的配置参数:
-
corePoolSize:核心线程数。线程池维护的最少线程数量,即使空闲也不会被终止(除非设置allowCoreThreadTimeOut)。maximumPoolSize:最大线程数。线程池允许的最大线程数量。keepAliveTime:非核心线程的空闲存活时间。当池中线程数大于核心线程数时,空闲时间超过此值的线程会被终止。unit:keepAliveTime的时间单位。workQueue:任务队列。用于存放等待执行的任务。常见的队列有ArrayBlockingQueue,LinkedBlockingQueue,SynchronousQueue,PriorityBlockingQueue等。不同的队列对线程池的行为影响很大。threadFactory:线程工厂。用于创建新线程,可以自定义线程的命名、优先级等。handler:拒绝策略 (RejectedExecutionHandler)。当任务队列已满且线程池中的线程数达到最大线程数时,新提交的任务会触发拒绝策略。默认有几种拒绝策略(如抛异常、丢弃任务、丢弃队列最老的任务、使用提交任务的线程执行)。
创建线程池示例
创建固定大小线程池 (newFixedThreadPool)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class FixedThreadPoolExample {
public static void main(String[] args) {
System.out.println("创建固定大小线程池...");
// 创建一个拥有 3 个线程的固定大小线程池
ExecutorService executor = Executors.newFixedThreadPool(3);
// 提交 10 个任务给线程池
for (int i = 0; i < 10; i++) {
final int taskIndex = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
try {
Thread.sleep(1000); // 模拟任务执行时间
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 任务 " + taskIndex + " 被中断.");
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " 完成任务 " + taskIndex);
});
}
// 关闭线程池 (不再接受新任务,但会执行已提交的任务)
System.out.println("提交完所有任务,关闭线程池...");
executor.shutdown();
// 可选:等待线程池终止 (例如最多等 5 秒)
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.err.println("线程池未在规定时间内终止,可能还有任务未完成。");
// 如果需要在超时后强制停止,可以使用 executor.shutdownNow();
}
} catch (InterruptedException e) {
System.err.println("等待线程池终止时被中断。");
Thread.currentThread().interrupt();
}
System.out.println("主线程结束.");
}
}
创建可缓存线程池 (newCachedThreadPool)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class CachedThreadPoolExample {
public static void main(String[] args) {
System.out.println("创建可缓存线程池...");
// 创建一个可缓存线程池
ExecutorService executor = Executors.newCachedThreadPool();
// 提交一些任务
for (int i = 0; i < 5; i++) { // 提交 5 个任务
final int taskIndex = i;
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
try {
Thread.sleep(500); // 模拟任务执行时间
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 任务 " + taskIndex + " 被中断.");
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " 完成任务 " + taskIndex);
});
}
// 注意:CachedThreadPool 的线程在空闲一定时间后会终止
// 如果在提交任务后立即关闭,可能看不到所有线程被创建和回收的过程
System.out.println("提交完所有任务,关闭线程池...");
executor.shutdown();
// 可选:等待线程池终止
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.err.println("线程池未在规定时间内终止。");
}
} catch (InterruptedException e) {
System.err.println("等待线程池终止时被中断。");
Thread.currentThread().interrupt();
}
System.out.println("主线程结束.");
}
}
使用 ThreadPoolExecutor 构造函数 (更灵活,推荐用于生产环境)
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.Executors; // 用于获取默认 ThreadFactory
public class ThreadPoolExecutorExample {
public static void main(String[] args) {
System.out.println("直接创建 ThreadPoolExecutor...");
// 核心线程数:即使空闲也保留的线程数
int corePoolSize = 2;
// 最大线程数:池中最多允许的线程数
int maximumPoolSize = 5;
// 非核心线程的空闲存活时间:超过核心线程数且空闲超过此时间,线程会被回收
long keepAliveTime = 60;
// 存活时间单位
TimeUnit unit = TimeUnit.SECONDS;
// 任务队列:用于存放等待执行的任务。这里使用容量为 10 的有界阻塞队列。
BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(10);
// 线程工厂:用于创建新线程
ThreadFactory threadFactory = Executors.defaultThreadFactory(); // 使用默认的线程工厂
// 拒绝策略:当队列满且线程数达到最大时,如何处理新提交的任务。这里使用默认策略 (抛异常)。
// RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy(); // 默认策略
// 创建 ThreadPoolExecutor 实例
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory
// , handler // 可以选择指定拒绝策略,不指定则使用默认的 AbortPolicy
);
// 提交任务
for (int i = 0; i < 15; i++) { // 提交 15 个任务,队列容量 10 + 最大线程 5 = 15,刚好可能不触发拒绝策略
final int taskIndex = i;
try {
executor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 正在执行任务 " + taskIndex);
try {
Thread.sleep(500); // 模拟任务执行时间
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " 任务 " + taskIndex + " 被中断.");
Thread.currentThread().interrupt();
}
System.out.println(Thread.currentThread().getName() + " 完成任务 " + taskIndex);
});
} catch (java.util.concurrent.RejectedExecutionException e) {
System.err.println("任务 " + taskIndex + " 被拒绝: " + e.getMessage());
}
}
// 关闭线程池
System.out.println("提交完所有任务,关闭线程池...");
executor.shutdown();
// 可选:等待线程池终止
try {
if (!executor.awaitTermination(5, TimeUnit.SECONDS)) {
System.err.println("线程池未在规定时间内终止。");
}
} catch (InterruptedException e) {
System.err.println("等待线程池终止时被中断。");
Thread.currentThread().interrupt();
}
System.out.println("主线程结束.");
}
}
177

被折叠的 条评论
为什么被折叠?



