第一部分:Java线程的创建和启动
如何创建线程
在Java中,创建线程有两种常见的方法:
-
实现
Runnable接口:创建一个实现了Runnable接口的类,并重写run方法。然后,将这个类的实例传递给Thread类的构造函数,并启动新线程。 -
继承
Thread类:创建一个继承自Thread的子类,并重写run方法。直接在子类中启动新线程。
如何启动线程
无论使用哪种方法创建线程,都需要调用start()方法来启动线程。start()方法会调用线程的run方法,从而开始线程的执行。
线程的生命周期
线程在Java中有一个明确的生命周期,包括以下几个状态:
- 新建(New):线程对象被创建,但
start()尚未被调用。 - 可运行(Runnable):线程可以在JVM的调度下执行。这包括了就绪(Ready)和运行(Running)两种状态。
- 阻塞(Blocked):线程因为某种原因无法执行,通常是等待获取锁。
- 等待(Waiting):线程进入等待状态,直到另一个线程执行某个动作。
- 超时等待(Timed Waiting):线程在一定时间后返回到可运行状态。
- 终止(Terminated):线程执行完毕或者被中断。
案例源码:
// 实现Runnable接口创建线程
class MyRunnable implements Runnable {
public void run() {
System.out.println("Hello from a runnable!");
}
}
public class ThreadCreationDemo {
public static void main(String[] args) {
// 使用Runnable接口创建线程
Thread thread1 = new Thread(new MyRunnable());
thread1.start(); // 启动线程
// 继承Thread类创建线程
Thread thread2 = new MyThread();
thread2.start(); // 启动线程
}
}
// 继承Thread类创建线程
class MyThread extends Thread {
public void run() {
System.out.println("Hello from a thread!");
}
}
在实际开发中,通常推荐使用实现Runnable接口的方式来创建线程,因为这种方式更加灵活,也更符合面向对象的设计原则。继承Thread类会使得类的功能和线程的生命周期耦合在一起,这限制了类继承其他类的能力。
线程的生命周期对于理解线程调度和资源管理非常重要。了解线程在何时以及如何从一种状态转换到另一种状态,有助于编写出更加健壮和高效的多线程程序。
启动线程时,需要注意线程的优先级、守护线程(daemon thread)的使用,以及线程的中断(interruption)处理。这些特性对于控制线程的行为和资源的合理使用至关重要。
第二部分:线程同步和锁
线程安全的概念
线程安全是指在多线程环境中,代码能够正确地工作,不会出现数据不一致或者状态不可预测的问题。
同步方法和同步代码块
Java提供了两种同步机制来确保线程安全:
-
同步方法:使用
synchronized关键字修饰方法,使得每次只有一个线程可以执行该方法。 -
同步代码块:使用
synchronized关键字和锁对象,只同步代码的一部分,而不是整个方法。
synchronized关键字的使用
synchronized关键字可以用来修饰方法或者代码块,以确保线程同步。
案例源码:同步方法
public class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
}
案例源码:同步代码块
public class Counter {
private int count = 0;
private final Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
}
显式锁:ReentrantLock
Java并发API提供了java.util.concurrent.locks.ReentrantLock,这是一个显式锁,它比synchronized关键字提供了更多的灵活性。
案例源码:
import java.util.concurrent.locks.ReentrantLock;
public class Counter {
private int count = 0;
private final ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 显式获取锁
try {
count++;
} finally {
lock.unlock(); // 显式释放锁
}
}
}
线程同步是多线程编程中的核心问题之一。正确的同步策略可以避免竞态条件和数据不一致的问题。synchronized关键字是实现线程同步的一种简单而强大的方式,但它的使用也有一些限制,例如不能设置超时时间,也不能中断等待锁的线程。
相比之下,ReentrantLock提供了更高的灵活性。它允许开发者设置锁的超时时间,也支持在等待锁的过程中中断等待。此外,ReentrantLock可以被用作条件变量,这是synchronized关键字不支持的。
然而,显式锁的使用也带来了额外的复杂性,尤其是在异常处理和资源释放方面。不正确的使用可能导致锁无法释放,造成死锁。因此,在使用ReentrantLock时,需要特别小心,确保在所有可能的执行路径上都能够释放锁。
第三部分:线程间通信
线程间的共享资源
在多线程编程中,线程间通信通常涉及到对共享资源的访问。共享资源可以是内存、文件或者任何可以被多个线程访问的变量。
等待/通知机制:wait(), notify(), notifyAll()
Java提供了等待/通知机制来实现线程间的协调。这些机制通过Object类中的wait()、notify()和notifyAll()方法实现。
wait():使当前线程等待,直到另一个线程调用相同对象的notify()或notifyAll()。notify():唤醒在此对象监视器上等待的单个线程。notifyAll():唤醒在此对象监视器上等待的所有线程。
案例源码:
public class ThreadCommunicationDemo {
private final Object lock = new Object();
private boolean ready = false;
public void thread1() {
synchronized (lock) {
while (!ready) {
try {
lock.wait(); // 等待,直到被通知
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 执行后续操作
}
}
public void thread2() {
synchronized (lock) {
ready = true;
lock.notify(); // 通知一个等待的线程
}
}
public static void main(String[] args) {
ThreadCommunicationDemo demo = new ThreadCommunicationDemo();
Thread t1 = new Thread(() -> demo.thread1());
Thread t2 = new Thread(() -> demo.thread2());
t1.start();
t2.start();
}
}
volatile关键字的使用
volatile关键字可以确保变量的读写操作对所有线程都是可见的,即当一个线程修改了一个volatile变量的值,其他线程能够立即看到这个改变。
案例源码:
public class VolatileDemo {
private volatile boolean running = true;
public void start() {
new Thread(() -> {
while (running) {
// 执行任务
}
}).start();
}
public void stop() {
running = false; // 改变状态,将被所有线程立即看到
}
}
ThreadLocal的使用
ThreadLocal提供了线程内的局部变量,这些变量对于每个线程都是独立的,不会与其他线程共享。
案例源码:
public class ThreadLocalDemo {
private static final ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
new Thread(() -> {
threadLocal.set(10); // 每个线程设置自己的值
System.out.println("Value in this thread: " + threadLocal.get());
}).start();
new Thread(() -> {
threadLocal.set(20);
System.out.println("Value in this thread: " + threadLocal.get());
}).start();
}
}
线程间通信是多线程编程中的一个关键概念,它允许线程协调它们的工作和共享数据。等待/通知机制是线程间通信的基础,它允许线程在特定条件下挂起,并在条件满足时被唤醒。
volatile关键字是一种轻量级同步机制,它适用于不需要原子操作的共享变量,如状态标志。它确保变量的读写操作对所有线程都是即时可见的,但并不保证复合操作的原子性。
ThreadLocal提供了一种线程安全的共享资源方式,每个线程可以访问到自己的副本,而不需要担心与其他线程发生冲突。这在处理线程特有数据时非常有用,如事务ID、用户会话等。
第四部分:Java并发API
java.util.concurrent包的介绍
java.util.concurrent包是Java并发编程的核心,它提供了一系列用于同步、线程安全集合、并发执行任务的工具类。
线程安全的集合
这个包提供了多种线程安全的集合类,可以在多线程环境中安全使用而不需要额外的同步措施。
ConcurrentHashMap:线程安全的HashMap实现。ConcurrentLinkedQueue:线程安全的非阻塞队列。BlockingQueue:支持阻塞操作的队列接口。
案例源码:
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;
public class ConcurrentCollectionDemo {
public static void main(String[] args) {
ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();
map.put("key1", "value1");
map.put("key2", "value2");
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<>();
queue.offer("item1");
queue.offer("item2");
}
}
并发工具类
java.util.concurrent包还包含了一些并发工具类,用于简化并发任务的执行和管理。
ExecutorService:线程池接口,用于管理线程池和任务的执行。Callable:与Runnable类似,但可以返回结果和抛出异常。Future:表示异步计算的结果,可用于跟踪Callable任务的状态和结果。
案例源码:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorDemo {
public static void main(String[] args) throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(3);
Future<String> future = executor.submit(() -> {
Thread.sleep(1000);
return "World";
});
System.out.println("Hello " + future.get());
executor.shutdown();
}
}
CountDownLatch:允许一个或多个线程等待一组事件。CyclicBarrier:类似于CountDownLatch,但所有线程必须到达屏障点后才能继续执行。Semaphore:用于控制同时访问特定资源的线程数量。
案例源码:使用CountDownLatch
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws Exception {
int threadCount = 5;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " is running");
try {
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
latch.countDown(); // 计数器减一
}
}).start();
}
latch.await(); // 等待所有线程完成
System.out.println("All tasks completed");
}
}
java.util.concurrent包极大地简化了Java中的并发编程。线程安全集合类减少了在多线程环境中手动同步集合操作的需要。并发工具类如ExecutorService、Callable和Future提供了强大的异步任务执行和管理机制。
CountDownLatch、CyclicBarrier和Semaphore等同步辅助工具,为线程间的协调提供了灵活的控制手段,它们可以在复杂的多线程场景中实现精确的同步控制。
第五部分:线程池的使用和管理
线程池的基本概念和组件
线程池是一种执行器(Executor),用于在一个后台线程中执行任务。线程池的主要目的是减少在创建和销毁线程时所产生的性能开销。线程池的核心组件包括:
- 线程工厂(ThreadFactory):用于创建新线程。
- 拒绝策略(RejectedExecutionHandler):当任务太多,无法被线程池及时处理时,采取的策略。
- 任务队列:用于存放待执行任务的阻塞队列。
如何创建线程池
Java提供了Executors类来创建预定义配置的线程池,或者使用ThreadPoolExecutor类来自定义线程池参数。
案例源码:使用Executors创建线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
public class ExecutorsDemo {
public static void main(String[] args) {
// 创建一个固定大小的线程池
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
// 创建一个单线程的线程池,用于定时任务
ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
}
}
案例源码:使用ThreadPoolExecutor构造器创建线程池
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
public static void main(String[] args) {
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 1L;
TimeUnit unit = TimeUnit.MINUTES;
LinkedBlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>();
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue
);
}
}
线程池的工作原理
线程池按照以下步骤工作:
- 线程池维护一定数量的线程(核心线程池)。
- 当提交一个任务时,线程池会尝试使用空闲的核心线程来执行任务。
- 如果核心线程都忙,任务会被放入任务队列等待。
- 如果任务队列满了,线程池会创建新的非核心线程来处理任务,直到达到最大线程数。
- 如果线程池满了且任务队列也满了,任务会被拒绝,线程池会调用拒绝策略。
线程池的调优和关闭
线程池的调优是生产环境中的重要环节,合理的参数设置可以提高程序的响应速度和资源利用率。
案例源码:线程池的关闭
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolShutdown {
public static void main(String[] args) {
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(3);
// 提交任务到线程池
// 程序结束前,关闭线程池
executor.shutdown(); // 非强制性关闭
// executor.shutdownNow(); // 强制性关闭,尝试立即停止所有正在执行的任务
}
}
线程池是多线程编程中的核心概念,它可以有效地管理和复用线程,提高程序的并发性能。通过合理地配置线程池参数,可以避免资源浪费,如线程创建和销毁的开销,以及过多的线程竞争导致的上下文切换开销。
线程池的关闭也是一个重要的环节。在应用程序关闭时,应该优雅地关闭线程池,以确保所有提交的任务都能够完成。shutdown()方法允许线程池在处理完队列中的任务后关闭,而shutdownNow()方法则尝试立即停止所有正在执行的任务,并返回等待执行的任务列表,以便开发者可以处理这些任务。