引言
在现代计算机程序设计中,多线程是实现并发和提高程序性能的关键技术之一。线程作为操作系统中最小的可调度执行单元,能够在单个进程中独立执行特定任务,使得程序能够同时处理多项逻辑,充分利用多核处理器的计算能力。本文将首先阐述线程的基本概念,并详细介绍Java中线程的创建和管理方式,随后通过实例代码详细剖析如何运用Thread类及Runnable接口来开启和控制线程。
一、线程的基本概念
线程是进程内部的一个执行流,它拥有独立的执行上下文,包括程序计数器、栈以及其他寄存器状态。在一个进程中,多个线程可以共享全局变量、堆内存等资源,但每个线程有自己的程序计数器和栈空间。这意味着即使在同一进程中,不同线程也可以执行不同的代码序列,并且在需要的时候可以通过同步机制安全地访问共享资源。
二、Java中的线程创建与管理
1. 使用Thread类创建线程
在Java中,可以通过直接继承java.lang.Thread类并重写其run()方法来创建线程:
class MyThread extends Thread {
@Override
public void run() {
// 这里编写线程执行的任务
System.out.println("Thread executing in " + this.getName());
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程,会调用run()方法
}
}
2. 使用Runnable接口创建线程
另一种更为灵活的方式是实现Runnable接口,这允许我们的类同时继承其他父类:
class Task implements Runnable {
@Override
public void run() {
// 线程执行的具体逻辑
System.out.println("Runnable task executing.");
}
public static void main(String[] args) {
Task task = new Task();
Thread thread = new Thread(task);
thread.start();
}
}
3. 使用Callabble接口创建线程
还有一种Callable接口是用于创建多线程的一种高级形式,相比于Runnable接口,Callable不仅可以定义一个线程要执行的任务,而且还可以生成一个返回值,并且允许抛出受检异常。
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallable线程执行的内容");
return "返回的结果";
}
public static void main(String[] args) {
try {
//创建FutureTask对象,传入Callable的实例。
FutureTask<String> futureTask = new FutureTask<>(new MyCallable());
//创建并启动线程,将FutureTask作为线程的执行体
Thread workerThread = new Thread(futureTask);
workerThread.start();
//当线程执行完毕后,可以通过FutureTask的get()方法获取到call()方法的返回结果,同时这个方法会阻塞直到结果可用或者线程执行过程中发生异常。
String result = futureTask.get();
System.out.println("The result is: " + result);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
三、线程的生命周期
- 新建(New):
当使用 new 关键字创建一个 Thread 对象时,线程处于新建状态,但是尚未启动。此时,线程仅存在于内存中,其生命周期尚未开始。 - 就绪(Runnable):
当调用线程对象的 start() 方法后,线程进入就绪状态。此时线程已经准备好运行,但在某一时刻可能并未真正执行,这是因为线程调度器还未将其分配给CPU来执行。 - 运行(Running)/运行状态(Runnable):
就绪状态的线程一旦被线程调度器选中并分配了CPU时间片后,就会进入运行状态,开始执行 run() 方法。 - 阻塞(Blocked):
等待阻塞(Waiting):当线程调用了某个对象的 wait() 方法后,它会释放持有的对象锁并进入等待状态,直到被其他线程调用 notify() 或 notifyAll() 方法唤醒。
同步阻塞(Blocked on Synchronization):线程试图获取一个已被其他线程持有的同步锁时,将无法继续执行,从而进入同步阻塞状态,直至锁被释放。
其他阻塞(Sleeping, IO Blocked, etc.):线程也可以通过调用 sleep() 方法主动让自身进入休眠状态,或者在等待IO操作完成时进入阻塞状态,还有可能是调用了 join() 方法等待另一个线程结束等。
线程在运行过程中,可能因为各种原因而进入阻塞状态: - 等待(Waiting):
这个状态指的是线程不会被CPU调度,直到满足特定条件。除了上述等待阻塞之外,还包括无超时限制的等待,如调用 Object.wait() 方法但不设置超时时间。 - 定时等待(Timed Waiting):
线程进入了有限期等待状态,会在指定的时间过后自动唤醒。例如,调用 Thread.sleep(long millis) 方法设定等待时间,或调用 Object.wait(long timeout) 设置超时等待。 - 终止(Terminated / Dead):
线程完成了 run() 方法的执行,或者因为异常而提前终止,线程就进入了终止状态。终止状态是线程生命周期的最后一个阶段,结束后线程对象仍可能存在,但不再参与任何调度和执行。
四、并发控制与同步机制
在多线程环境下,为了保证数据的一致性和正确性,必须引入并发控制与同步机制。Java提供了多种同步工具和方法:
synchronized关键字:
可以修饰方法或代码块,确保同一时刻仅有一个线程能访问被修饰的部分。
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Lock接口与ReentrantLock类:
相较于synchronized,Java的java.util.concurrent.locks包提供了更高级别的锁定机制,例如可重入锁ReentrantLock。
import java.util.concurrent.locks.ReentrantLock;
public class AdvancedCounter {
private final ReentrantLock 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();
}
}
}
条件变量(Condition):结合锁使用,允许线程在满足特定条件时被唤醒。
信号量(Semaphore):用于控制同时访问特定资源的线程数量。
并发容器:如ConcurrentHashMap、CopyOnWriteArrayList等,它们内置了线程安全机制,适合多线程环境下的数据存储和访问。
阻塞队列(BlockingQueue):在生产者消费者模式中扮演重要角色,提供线程安全的队列操作。
同步辅助类(如CountDownLatch、CyclicBarrier、Semaphore):用于协调一组线程之间的同步行为。
综上所述,理解和掌握Java中的线程模型以及并发控制与同步机制对于构建高效、安全的多线程应用至关重要。