今天我们将深入探索Java并发编程的核心领域 - 线程基础与Java内存模型。无论你是刚入门的Java开发者,还是想要提升并发编程能力的老手,这篇文章都将为你揭开Java并发的神秘面纱,带你掌握高性能并发应用的核心技术!
很多开发者常常混淆进程和线程的概念。让我们从操作系统层面来彻底理解它们的区别:
特性 | 进程 | 线程 |
---|---|---|
定义 | 运行中的程序实例 | 进程中的执行单元 |
资源占用 | 独立的内存空间 | 共享所属进程的内存 |
通信开销 | 高(IPC机制) | 低(直接共享内存) |
切换成本 | 高 | 低 |
安全性 | 高(相互隔离) | 低(共享内存带来风险) |
进程就像是一个独立的「房子」,拥有自己的地址空间、资源;而线程则像是房子里的「居民」,共享房子的资源,但有各自的执行路径。
// 获取当前进程信息
ProcessHandle currentProcess = ProcessHandle.current();
System.out.println("当前进程PID: " + currentProcess.pid());
// 获取当前线程信息
Thread currentThread = Thread.currentThread();
System.out.println("当前线程名称: " + currentThread.getName());
System.out.println("当前线程ID: " + currentThread.getId());
Java线程状态机制非常精妙,理解它对于调试并发问题至关重要。Thread.State枚举定义了6种状态:
这些状态之间的转换是Java并发的核心机制,我们来看一个状态转换的实战示例:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
Object lock = new Object();
Thread thread = new Thread(() -> {
System.out.println("进入RUNNABLE状态");
synchronized (lock) {
try {
System.out.println("进入WAITING状态");
lock.wait();
System.out.println("从WAITING恢复到RUNNABLE");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("即将进入TERMINATED状态");
});
// NEW状态
System.out.println("线程状态: " + thread.getState());
thread.start();
Thread.sleep(100); // 确保线程有足够时间进入wait状态
// WAITING状态
System.out.println("线程状态: " + thread.getState());
synchronized (lock) {
lock.notify();
}
thread.join();
// TERMINATED状态
System.out.println("线程状态: " + thread.getState());
}
}
Java提供了多种创建线程的方式,每种方式都有其适用场景:
class MyThread extends Thread {
@Override
public void run() {
System.out.println("使用Thread类创建线程");
}
}
// 使用
MyThread thread = new MyThread();
thread.start();
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("使用Runnable接口创建线程");
}
}
// 使用
Thread thread = new Thread(new MyRunnable());
thread.start();
// 或使用Lambda表达式(Java 8+)
Thread thread = new Thread(() -> {
System.out.println("使用Lambda表达式创建线程");
});
thread.start();
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "使用Callable接口创建线程,可以有返回值";
}
}
// 使用
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
Thread thread = new Thread(futureTask);
thread.start();
// 获取结果(会阻塞直到任务完成)
try {
String result = futureTask.get();
System.out.println(result);
} catch (Exception e) {
e.printStackTrace();
}
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
return "使用CompletableFuture创建异步任务";
});
// 非阻塞方式处理结果
future.thenAccept(result -> System.out.println(result));
// 组合多个异步任务
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combinedFuture = future1.thenCombine(future2,
(result1, result2) -> result1 + " " + result2);
combinedFuture.thenAccept(System.out::println); // 输出: Hello World
Java内存模型(JMM)是理解Java并发的核心,它定义了线程如何与内存交互,以及多线程程序中变量的可见性、有序性和原子性如何保证。
JMM的核心概念是:
线程操作变量时,会先从主内存加载到工作内存,修改后再刷新回主内存。这种机制导致了并发问题:
public class MemoryVisibilityProblem {
private static boolean flag = false;
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
Thread writerThread = new Thread(() -> {
number = 42; // 1: 写入number
flag = true; // 2: 写入flag
});
Thread readerThread = new Thread(() -> {
while (!flag) { // 3: 读取flag
// 自旋等待
}
System.out.println(number); // 4: 读取number
});
readerThread.start();
writerThread.start();
writerThread.join();
readerThread.join();
}
}
在没有同步的情况下,上面的代码可能输出0而不是42,因为:
JMM定义了happens-before关系,确保一个线程的操作对另一个线程可见:
volatile是Java提供的轻量级同步机制,它保证:
public class VolatileDemo {
// 使用volatile保证可见性
private static volatile boolean flag = false;
private static int number = 0;
public static void main(String[] args) throws InterruptedException {
Thread writerThread = new Thread(() -> {
number = 42;
flag = true; // volatile写,建立内存屏障
});
Thread readerThread = new Thread(() -> {
while (!flag) { // volatile读,建立内存屏障
// 自旋等待
}
System.out.println(number); // 一定输出42
});
readerThread.start();
writerThread.start();
writerThread.join();
readerThread.join();
}
}
并发编程中存在三大核心问题:原子性、可见性和有序性。
原子性指一个操作是不可分割的整体,要么全部执行,要么全部不执行。
public class AtomicityProblem {
private static int counter = 0;
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter++; // 非原子操作!
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Expected: 10000, Actual: " + counter);
}
}
解决方案:使用synchronized或java.util.concurrent.atomic包中的原子类
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicityFixed {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(() -> {
for (int j = 0; j < 1000; j++) {
counter.incrementAndGet(); // 原子操作
}
});
threads[i].start();
}
for (Thread thread : threads) {
thread.join();
}
System.out.println("Expected: 10000, Actual: " + counter.get());
}
}
可见性问题指一个线程修改了共享变量,其他线程可能看不到最新值。
public class VisibilityProblem {
private static boolean flag = false;
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
while (!flag) {
// 死循环,等待flag变为true
}
System.out.println("检测到flag变化!");
});
thread.start();
Thread.sleep(1000);
flag = true; // 主线程修改flag,但worker线程可能永远看不到!
System.out.println("已将flag设置为true");
}
}
解决方案:使用volatile关键字、synchronized或显式锁
有序性问题指程序的执行顺序可能因为指令重排序而改变。
public class OrderingProblem {
private static int x = 0, y = 0;
private static int a = 0, b = 0;
public static void main(String[] args) throws InterruptedException {
int iterations = 0;
while (true) {
iterations++;
x = y = a = b = 0;
Thread t1 = new Thread(() -> {
a = 1;
x = b;
});
Thread t2 = new Thread(() -> {
b = 1;
y = a;
});
t1.start();
t2.start();
t1.join();
t2.join();
// 在顺序执行的世界里,x和y不可能同时为0
// 但由于指令重排序,这种情况是可能的!
if (x == 0 && y == 0) {
System.out.println("第" + iterations + "次迭代发现重排序!x=" + x + ", y=" + y);
break;
}
}
}
}
解决方案:使用volatile、synchronized或显式锁建立内存屏障
通过本文,我们深入探索了Java线程的基础知识和内存模型的核心概念。掌握这些知识对于编写高性能、可靠的并发程序至关重要。
实践建议:
关注我的更多技术内容
如果你喜欢这篇文章,别忘了点赞、收藏和分享!有任何问题,欢迎在评论区留言讨论!
本文首发于我的技术博客,转载请注明出处