Java 并发编程是后端开发中至关重要的一部分,它涉及到如何有效地使用多核处理器、提高程序的执行效率,并确保线程安全。无论是面试还是实际项目开发,掌握多线程编程的核心要点都至关重要。本文将围绕 Java 多线程编程的四个核心要点展开,帮助读者深入理解并发编程的基本原理、应用场景以及常见的注意事项。
一、线程的创建与启动
在 Java 中,线程是程序执行的基本单位。线程的创建和启动是进行多线程编程的第一步,掌握线程创建的方式和启动机制对理解并发编程至关重要。
1.1 线程的创建方式
Java 提供了两种常见的创建线程的方式:继承 Thread
类和实现 Runnable
接口。
1.1.1 继承 Thread
类
通过继承 Thread
类并重写 run()
方法来创建线程。启动线程时,调用 start()
方法。
class MyThread extends Thread {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
1.1.2 实现 Runnable
接口
实现 Runnable
接口比继承 Thread
类更灵活,因为 Java 允许一个类实现多个接口,但只能继承一个类。因此,推荐使用 Runnable
接口。
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Thread is running");
}
}
public class ThreadExample {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start(); // 启动线程
}
}
1.2 启动线程的 start()
方法
start()
方法用于启动线程,它会调用线程的 run()
方法。需要注意的是,调用 start()
后线程并不立即执行,而是进入就绪状态,具体的执行顺序由操作系统的调度器决定。
注意事项:
- 不能直接调用
run()
方法,因为这只会在当前线程中执行run()
方法,而不会启动一个新的线程。 - 线程一旦结束后,不能重启。同一个线程调用
start()
多次会抛出IllegalThreadStateException
异常。
二、线程同步机制
在多线程环境中,多个线程可能会同时访问共享资源,导致数据竞争和不一致的结果。为了解决这些问题,Java 提供了多种线程同步机制来保证线程安全。
2.1 synchronized
关键字
synchronized
是 Java 最常用的同步机制,使用它可以确保一个时间点只有一个线程可以访问某个资源。synchronized
可以用在方法上或代码块中。
2.1.1 同步方法
通过在方法声明中加上 synchronized
关键字,可以让该方法在一个时间点内只允许一个线程访问。
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
2.1.2 同步代码块
使用同步代码块可以更加精细地控制同步范围,避免整个方法都被锁住,提升程序的性能。
class Counter {
private int count = 0;
public void increment() {
synchronized (this) {
count++;
}
}
public int getCount() {
synchronized (this) {
return count;
}
}
}
2.2 Lock
接口
除了 synchronized
,Java 还提供了更灵活的锁机制——Lock
接口。与 synchronized
不同,Lock
提供了显式锁的控制,可以在不同的代码块之间手动获取和释放锁。
2.2.1 使用 ReentrantLock
ReentrantLock
是 Lock
接口的常用实现,它提供了更丰富的功能,比如尝试获取锁、可中断的锁等。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Counter {
private int count = 0;
private final Lock lock = new ReentrantLock();
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保释放锁
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
}
注意事项:
- 使用
Lock
时需要显式地调用unlock()
释放锁,最好放在finally
块中,以确保在发生异常时也能释放锁。 ReentrantLock
可以实现可重入锁,即同一线程可以多次获取同一个锁。
三、线程间通信
在多线程编程中,线程往往需要进行协作和通信,以实现更复杂的功能。Java 提供了多种方式来实现线程间的通信,常见的有 wait()
和 notify()
方法以及 Condition
接口。
3.1 wait()
和 notify()
wait()
和 notify()
是 Object
类中定义的方法,必须在同步代码块或同步方法中使用。wait()
会让当前线程进入等待状态,直到其他线程调用 notify()
或 notifyAll()
唤醒它。
3.1.1 使用 wait()
和 notify()
class SharedResource {
private boolean ready = false;
public synchronized void produce() throws InterruptedException {
while (ready) {
wait(); // 如果资源已经准备好,则等待
}
ready = true;
System.out.println("Produced");
notify(); // 唤醒消费者线程
}
public synchronized void consume() throws InterruptedException {
while (!ready) {
wait(); // 如果资源未准备好,则等待
}
ready = false;
System.out.println("Consumed");
notify(); // 唤醒生产者线程
}
}
3.1.2 注意事项
wait()
和notify()
必须在同步块或同步方法中使用。wait()
会释放对象的锁,线程进入等待池。notify()
唤醒一个等待的线程,notifyAll()
唤醒所有等待的线程。
3.2 Condition
接口
Condition
是 java.util.concurrent.locks
包中的一个接口,提供了更为灵活的线程间通信机制。通过 Condition
,可以在多个线程间进行更复杂的信号传递和协作。
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
class SharedResource {
private boolean ready = false;
private final Lock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void produce() throws InterruptedException {
lock.lock();
try {
while (ready) {
condition.await(); // 如果资源已经准备好,等待
}
ready = true;
System.out.println("Produced");
condition.signal(); // 唤醒消费者线程
} finally {
lock.unlock();
}
}
public void consume() throws InterruptedException {
lock.lock();
try {
while (!ready) {
condition.await(); // 如果资源未准备好,等待
}
ready = false;
System.out.println("Consumed");
condition.signal(); // 唤醒生产者线程
} finally {
lock.unlock();
}
}
}
四、线程池的使用与优化
线程池是管理和复用线程的机制,可以有效地减少线程创建和销毁的开销,并且能够更好地管理线程的生命周期。Java 提供了 ExecutorService
接口和多个实现类来管理线程池。
4.1 使用线程池
Java 提供了 Executors
工厂类来创建线程池。常见的线程池包括:
- FixedThreadPool:固定大小的线程池。
- CachedThreadPool:可缓存的线程池,适用于任务数量不确定的场景。
- SingleThreadExecutor:单线程池,所有任务按顺序执行。
import java.util.concurrent.*;
public class ThreadPoolExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(2); // 创建固定大小的线程池
Callable<String> task = () -> {
Thread.sleep(1000);
return "Task finished";
};
Future<String> future = executor.submit(task);
System.out.println(future.get()); // 获取任务执行结果
executor.shutdown(); // 关闭
线程池 } }
### 4.2 线程池优化
线程池的优化主要体现在以下几个方面:
- **线程池大小的调整**:合理配置线程池的大小,避免线程过多或过少的情况。可以通过任务的数量和处理时间来调整线程池大小。
- **队列选择**:可以选择不同类型的队列(如 `LinkedBlockingQueue`、`ArrayBlockingQueue`),根据任务的特性进行优化。
- **拒绝策略**:当线程池任务队列满时,可以设置不同的拒绝策略(如 `AbortPolicy`、`CallerRunsPolicy`)。
```java
ExecutorService executor = new ThreadPoolExecutor(
4, 10, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>(100),
new ThreadPoolExecutor.CallerRunsPolicy() // 设置拒绝策略
);
结语
掌握 Java 并发编程的核心要点,不仅能帮助你编写高效的并发代码,还能提升你在面试中的竞争力。本文通过介绍线程的创建与启动、线程同步机制、线程间通信以及线程池的使用与优化,为你构建了一个完整的 Java 并发编程框架。通过不断实践和深入理解这些知识点,你将能够更好地应对各种并发编程的挑战。
标签:Java,synchronized,lock,void,编程,线程,多线程,public From: https://blog.csdn.net/m0_38141444/article/details/144781665