在Java开发中,并发多线程是提升程序性能、优化资源利用率的核心技能。本文将从核心概念入手,逐步拆解线程创建、同步控制、线程池等关键技术,搭配可直接运行的代码示例来学习掌握Java多线程编程。
并发(Concurrency)与并行(Parallelism)容易混淆,二者本质差异在于是否“真正同时执行”:
进程是资源分配的基本单位,拥有独立的内存空间;线程是CPU调度的基本单位,共享所属进程的内存资源,仅拥有程序计数器、栈等独立上下文。一个进程可以包含多个线程,线程间切换的开销远小于进程切换,这也是多线程提升效率的核心原因。
在实际开发中,多线程的应用场景无处不在,核心价值体现在三点:
Java提供了3种线程创建方式,各有适配场景,需根据业务需求选择,避免盲目使用。
最基础的实现方式,核心是继承Thread类并重写run()方法,通过start()启动线程(注意:不可直接调用run()方法,否则会作为普通方法执行)。
package com.apierr.springtest;
// 1. 继承Thread类,实现车票售卖线程
class TicketThread extends Thread {
// 静态变量存储车票总数,保证所有线程共享同一资源
private static int ticketCount = 10;
private static int totalTicketsSold;
// 窗口名称,区分不同售卖窗口
private String windowName;
// 构造方法传入窗口名称
public TicketThread(String windowName) {
this.windowName = windowName;
}
// 2. 重写run()方法,定义车票售卖逻辑
@Override
public void run() {
// 循环售卖,直到车票售罄
while (true) {
if (ticketCount <= 0) {
System.out.println(windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(如用户付款、打印车票)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 售卖车票,车票数量减1
System.out.println(windowName + " 售出1张车票,剩余车票:" + --ticketCount);
System.out.println("一共售出" + ++totalTicketsSold + "张车票");
// 模拟窗口间切换间隔,让并发效果更明显
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TicketThreadDemo {
public static void main(String[] args) {
// 3. 创建3个窗口线程,启动多线程售卖
TicketThread window1 = new TicketThread("窗口1");
TicketThread window2 = new TicketThread("窗口2");
TicketThread window3 = new TicketThread("窗口3");
window1.start();
window2.start();
window3.start();
}
}
优点:实现简单,适合快速验证逻辑;缺点:受Java单继承机制限制,继承Thread后无法再继承其他类,且线程与任务耦合。
注:以上会出现超卖的情况
为解决单继承痛点而生,将任务与线程分离,实现Runnable接口定义任务逻辑。
package com.apierr.springtest;
import java.util.concurrent.FutureTask;
class TickeRunnable implements Runnable{
// 静态变量存储车票总数,保证所有线程共享同一资源
private static int ticketCount = 10;
private static int totalTicketsSold;
// 窗口名称,区分不同售卖窗口
private String windowName;
// 构造方法传入窗口名称
public TickeRunnable(String windowName) {
this.windowName = windowName;
}
@Override
public void run() {
// 循环售卖,直到车票售罄
while (true) {
if (ticketCount <= 0) {
System.out.println(windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(如用户付款、打印车票)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 售卖车票,车票数量减1
System.out.println(windowName + " 售出1张车票,剩余车票:" + --ticketCount);
System.out.println("一共售出" + ++totalTicketsSold + "张车票");
// 模拟窗口间切换间隔,让并发效果更明显
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TicketThreadDemo2 {
public static void main(String[] args) {
TickeRunnable tickeRunnable1 = new TickeRunnable("窗口1");
TickeRunnable tickeRunnable2 = new TickeRunnable("窗口2");
TickeRunnable tickeRunnable3 = new TickeRunnable("窗口3");
Thread window1 = new Thread(tickeRunnable1);
Thread window2 = new Thread(tickeRunnable2);
Thread window3 = new Thread(tickeRunnable3);
window1.start();
window2.start();
window3.start();
}
}
优点:解除线程与任务的耦合,支持多线程共享任务逻辑,不影响类的继承关系,扩展性强。适用场景:大多数业务开发,尤其是多线程共享任务的场景(如多线程处理同一份数据)。
前两种方式的run()方法无返回值、无法抛出受检异常,若需任务执行后返回结果(如复杂计算、远程接口调用),可使用Callable接口,配合FutureTask获取结果。
package com.apierr.springtest;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
class TicketCallable implements Callable{
// 静态变量存储车票总数,保证所有线程共享同一资源
private static int ticketCount = 10;
private static int totalTicketsSold;
// 窗口名称,区分不同售卖窗口
private String windowName;
// 构造方法传入窗口名称
public TicketCallable(String windowName) {
this.windowName = windowName;
}
@Override
public Integer call() throws Exception {
int i = 0;
// 循环售卖,直到车票售罄
while (true) {
if (ticketCount <= 0) {
System.out.println(windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(如用户付款、打印车票)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 售卖车票,车票数量减1
System.out.println(windowName + " 售出1张车票,剩余车票:" + --ticketCount);
System.out.println("一共售出" + ++totalTicketsSold + "张车票");
++i;
// 模拟窗口间切换间隔,让并发效果更明显
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return i;
}
}
public class TicketThreadDemo3 {
public static void main(String[] args) throws Exception{
FutureTask<Integer> futureTask1 = new FutureTask<>(new TicketCallable("窗口1"));
FutureTask<Integer> futureTask2 = new FutureTask<>(new TicketCallable("窗口2"));
FutureTask<Integer> futureTask3 = new FutureTask<>(new TicketCallable("窗口3"));
new Thread(futureTask1).start();
new Thread(futureTask2).start();
new Thread(futureTask3).start();
Integer i1 = futureTask1.get();
Integer i2 = futureTask2.get();
Integer i3 = futureTask3.get();
System.out.println(i1);
System.out.println(i2);
System.out.println(i3);
}
}
支持返回值和受检异常,FutureTask可控制任务状态(取消任务、判断是否完成),适用于需要获取任务执行结果的场景(如异步计算、数据统计)。
Java线程有5种核心状态,理解状态转换是掌握线程调度的关键,避免出现线程阻塞、死锁等问题:
核心转换触发条件:sleep()、wait()会使线程进入阻塞态;notify()/notifyAll()唤醒线程重回就绪态;线程执行完毕自动进入死亡态。
多线程共享资源时,易出现数据错乱(如上面的几个示例都会出现车票超卖的情况),需通过同步机制保证原子性、可见性和有序性,核心方案是锁机制。
synchronized是Java内置的互斥锁,可修饰方法或代码块,保证同一时刻只有一个线程进入临界区,无需手动释放锁(异常或方法结束时自动释放)。
package com.apierr.springtest;
// 1. 继承Thread类,实现车票售卖线程
class TicketThread extends Thread {
// 静态变量存储车票总数,保证所有线程共享同一资源
private static int ticketCount = 10;
private static int totalTicketsSold;
// 窗口名称,区分不同售卖窗口
private String windowName;
// 构造方法传入窗口名称
public TicketThread(String windowName) {
this.windowName = windowName;
}
// 2. 重写run()方法,定义车票售卖逻辑
@Override
public void run() {
// 循环售卖,直到车票售罄
while (true) {
//添加类级别锁
synchronized (TicketThread.class){
if (ticketCount <= 0) {
System.out.println(windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(如用户付款、打印车票)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 售卖车票,车票数量减1
ticketCount--;
totalTicketsSold++;
System.out.println(windowName + " 售出1张车票,剩余车票:" + ticketCount);
System.out.println("一共售出" + totalTicketsSold + "张车票");
// 模拟窗口间切换间隔,让并发效果更明显
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TicketThreadDemo {
public static void main(String[] args) {
// 3. 创建3个窗口线程,启动多线程售卖
TicketThread window1 = new TicketThread("窗口1");
TicketThread window2 = new TicketThread("窗口2");
TicketThread window3 = new TicketThread("窗口3");
window1.start();
window2.start();
window3.start();
}
}
JDK 5引入的Lock接口(常用实现类ReentrantLock),提供比synchronized更灵活的锁控制,支持可中断锁、超时获取锁、公平锁等特性,需手动释放锁(建议在finally中释放)。
package com.apierr.springtest;
import java.util.concurrent.FutureTask;
import java.util.concurrent.locks.ReentrantLock;
class TickeRunnable implements Runnable{
// 静态变量存储车票总数,保证所有线程共享同一资源
private static int ticketCount = 10;
private static int totalTicketsSold;
// 窗口名称,区分不同售卖窗口
private String windowName;
// 静态重入锁:所有线程共享同一把锁,保证线程安全
private static final ReentrantLock lock = new ReentrantLock();
// 构造方法传入窗口名称
public TickeRunnable(String windowName) {
this.windowName = windowName;
}
@Override
public void run() {
// 循环售卖,直到车票售罄
while (true) {
lock.lock();
try {
if (ticketCount <= 0) {
System.out.println(windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(如用户付款、打印车票)
Thread.sleep(300);
// 售卖车票,车票数量减1
System.out.println(windowName + " 售出1张车票,剩余车票:" + --ticketCount);
System.out.println("一共售出" + ++totalTicketsSold + "张车票");
}catch (Exception e){
e.printStackTrace();
}finally {
// 解锁:必须在finally中,避免死锁
lock.unlock();
}
// 模拟窗口间切换间隔,让并发效果更明显
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class TicketThreadDemo2 {
public static void main(String[] args) {
TickeRunnable tickeRunnable1 = new TickeRunnable("窗口1");
TickeRunnable tickeRunnable2 = new TickeRunnable("窗口2");
TickeRunnable tickeRunnable3 = new TickeRunnable("窗口3");
Thread window1 = new Thread(tickeRunnable1);
Thread window2 = new Thread(tickeRunnable2);
Thread window3 = new Thread(tickeRunnable3);
window1.start();
window2.start();
window3.start();
}
}
普通集合(如ArrayList、HashMap)线程不安全,高并发场景下需使用并发集合:
频繁创建/销毁线程会产生大量开销,线程池通过复用线程、管理任务队列,实现线程生命周期的统一管控,是实际开发的首选方案。
手动创建线程池需配置核心参数,避免使用Executors工具类(易导致内存溢出),核心参数如下:
package com.apierr.springtest;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
// 售票任务类:封装售票逻辑
class TicketSaleTask implements Runnable {
// 静态变量:共享车票总数(暂不处理超卖)
private static int ticketCount = 10;
private static int totalTicketsSold;
// 核心:静态重入锁,所有任务共享同一把锁,保证线程安全
private static final ReentrantLock lock = new ReentrantLock();
// 窗口名称
private String windowName;
public TicketSaleTask(String windowName) {
this.windowName = windowName;
}
@Override
public void run() {
// 循环售票,直到车票售罄
while (true) {
lock.lock();
try {
// 暂不处理超卖,仅实现多线程并发逻辑
if (ticketCount <= 0) {
System.out.println( windowName + ":车票已售罄,停止售卖!");
break;
}
// 模拟售票延迟(用户付款/打印车票)
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// 响应线程中断,优雅退出
Thread.currentThread().interrupt();
System.out.println(windowName + " 售票被中断,停止售卖!");
break;
}
// 售卖车票(暂不处理超卖)
System.out.println( windowName + " 售出1张车票,剩余车票:" + --ticketCount);
System.out.println("一共售出" + ++totalTicketsSold + "张车票");
} catch (Exception e) {
throw new RuntimeException(e);
}finally {
lock.unlock();
}
// 模拟窗口切换间隔
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
}
public class TicketThreadPoolDemo {
public static void main(String[] args) {
// 1. 配置ThreadPoolExecutor线程池参数(适配3个窗口场景)
int corePoolSize = 3; // 核心线程数:固定3个(对应3个窗口)
int maximumPoolSize = 3; // 最大线程数:和核心线程数一致,避免创建临时线程
long keepAliveTime = 0; // 空闲线程存活时间:核心线程不超时
TimeUnit unit = TimeUnit.MILLISECONDS;
// 任务队列:无界队列,存放待执行的任务(本场景仅3个任务,队列不会满)
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
// 2. 创建线程池(使用默认的线程工厂和拒绝策略)
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue
);
// 3. 提交3个窗口的售票任务到线程池
threadPool.submit(new TicketSaleTask("窗口1"));
threadPool.submit(new TicketSaleTask("窗口2"));
threadPool.submit(new TicketSaleTask("窗口3"));
// 4. 优雅关闭线程池:先停止接收新任务,再等待所有任务执行完成
threadPool.shutdown();
try {
// 等待60秒,确保所有售票任务执行完成(超时则强制关闭)
if (!threadPool.awaitTermination(60, TimeUnit.SECONDS)) {
threadPool.shutdownNow(); // 超时强制关闭
}
} catch (InterruptedException e) {
threadPool.shutdownNow(); // 响应中断,强制关闭
}
System.out.println("所有窗口售票结束,线程池已关闭!");
}
}
常见线程池类型:FixedThreadPool(固定大小线程池)、CachedThreadPool(可缓存线程池)、ScheduledThreadPool(定时任务线程池),需根据业务场景选择。