在Java编程中,线程并发是一个核心且复杂的话题,它允许开发者利用现代多核处理器的计算能力,通过并行执行多个任务来优化应用程序的性能。然而,线程并发也带来了数据竞争、死锁、线程饥饿等问题,需要开发者深入理解并发模型及其相关机制。本文将详细介绍Java中线程并发的内容,包括基本概念、使用场景以及常用的关键字和对应的代码。
一、Java线程并发基础
在深入探讨Java线程并发之前,有必要先区分线程(Thread)与进程(Process)的概念。进程是系统进行资源分配和调度的一个独立单元,它拥有独立的内存空间和系统资源。而线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的独立运行的单位。一个进程可以拥有多个线程,这些线程共享进程的资源,包括内存、文件句柄等。
在Java中,线程可以通过继承Thread类或实现Runnable接口来创建。Java虚拟机(JVM)负责线程的调度和执行,它采用抢占式调度模型,即线程的执行时间和顺序由JVM的线程调度器决定,而非由程序员控制。
// 继承Thread类创建线程
class MyThread extends Thread {
public void run() {
System.out.println("Thread is running");
}
}
// 实现Runnable接口创建线程
class MyRunnable implements Runnable {
public void run() {
System.out.println("Runnable is running");
}
}
// 创建并启动线程
MyThread thread = new MyThread();
thread.start();
Thread runnableThread = new Thread(new MyRunnable());
runnableThread.start();
二、线程同步与通信
在多线程环境下,由于多个线程可能同时访问共享资源,因此必须采取适当的同步机制来避免数据竞争和一致性问题。Java提供了多种同步机制,包括synchronized关键字、volatile关键字、Lock接口及其实现类(如ReentrantLock)、以及wait、notify、notifyAll等方法。
1、 synchronized
synchronized是Java中用于控制多个线程对共享资源的访问,确保同一时刻只有一个线程可以执行某个方法或代码块。它可以修饰方法或代码块。
代码示例:
public class Counter {
private int count = 0;
// synchronized方法
public synchronized void increment() {
count++; // 复合操作,但在synchronized方法中认为是原子的
}
// synchronized代码块
public void decrement() {
synchronized (this) {
count--;
}
}
public synchronized int getCount() {
return count;
}
}
2、volatile
volatile关键字用于确保变量的修改对所有线程立即可见,它禁止了指令重排序优化,并确保了变量修改的原子性(但仅限于单个变量的读写操作)。
代码示例:
public class FlagController {
private volatile boolean running = true;
public void stopRunning() {
running = false;
}
public boolean isRunning() {
return running;
}
public static void main(String[] args) throws InterruptedException {
FlagController controller = new FlagController();
Thread thread = new Thread(() -> {
while (controller.isRunning()) {
// 执行任务
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("Thread stopped");
});
thread.start();
Thread.sleep(500); // 让线程运行一段时间
controller.stopRunning(); // 停止线程
}
}
注意: 实际上,volatile在这里仅用于演示变量可见性,而线程的停止通常不建议使用volatile标志位控制,因为这种方式不够优雅且难以处理所有情况(如线程阻塞时)。
3、Lock接口及其实现
Lock接口提供了比synchronized更灵活的锁定机制,它允许在尝试获取锁时设置超时时间、响应中断,以及尝试非阻塞地获取锁。ReentrantLock是Lock接口的一个实现,支持重入性。
代码示例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class CounterWithLock {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
4、wait/notify/notifyAll
这些方法用于在synchronized块或方法内部实现线程间的通信。wait()使当前线程等待,直到另一个线程调用同一对象的notify()或notifyAll()方法。notifyAll()唤醒在该对象上等待的所有线程。
代码示例:
public class ProducerConsumerQueue {
private final Object lock = new Object();
private int[] queue = new int[10];
private int head = 0, tail = 0;
public void produce(int value) throws InterruptedException {
synchronized (lock) {
while ((tail + 1) % queue.length == head) {
lock.wait(); // 等待队列不满
}
queue[tail] = value;
tail = (tail + 1) % queue.length;
lock.notifyAll(); // 通知等待的消费者
}
}
public int consume() throws InterruptedException {
synchronized (lock) {
while (head == tail) {
lock.wait(); // 等待队列不为空
}
int value = queue[head];
head = (head + 1) % queue.length;
lock.notifyAll(); // 通知等待的生产者
return value;
}
}
}
三、线程通信与协作
除了上述同步机制外,Java还提供了join、interrupt等方法来实现线程间的通信与协作。
1、join
join方法用于使当前线程等待另一个线程完成。这常用于控制线程的执行顺序,确保某个线程在继续执行之前,另一个线程已经完成其任务。
代码示例:
public class Main {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("Thread is running");
try {
Thread.sleep(1000); // 模拟耗时操作
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Thread is finished");
});
thread.start();
thread.join(); // 等待thread线程完成
System.out.println("Main thread continues");
}
}
2、interrupt
interrupt方法用于中断线程。当一个线程被中断时,它的中断状态会被设置为true。线程可以通过检查中断状态或捕获InterruptedException来响应中断。
注意: 实际上,interrupt并不直接停止线程的执行,而是给线程发送一个中断信号。线程需要通过检查中断状态或捕获InterruptedException来响应这个信号,并适当地停止自己的执行。
四、使用场景
线程并发的使用场景非常广泛,包括但不限于以下几个方面:
1、服务器应用:
在Web服务器、数据库服务器等应用中,通过多线程处理客户端请求,提高并发处理能力。
2、图形用户界面(GUI):
在Java Swing或JavaFX等GUI框架中,事件处理通常是在单独的线程中进行的,以避免界面冻结。
3、数据处理与分析:
在处理大量数据时,可以利用多线程并行处理数据块,加速数据处理速度。
4、游戏开发:
在游戏开发中,多线程可以用于处理游戏逻辑、渲染图形、处理网络通信等任务,提升游戏的响应性和流畅度。
五、总结
Java中的线程并发是一个强大而复杂的特性,它允许开发者编写出高效、响应性强的应用程序。然而,线程并发也带来了数据竞争、死锁、线程饥饿等问题,需要开发者深入理解同步机制、线程间通信与协作等内容。本文介绍了Java中线程并发的基础概念、使用场景,并详细解释了synchronized、volatile、Lock、wait、join、notifyAll等关键字的用法。