Java多线程编程全面解析:从基础概念到线程安全
本文介绍了Java多线程编程的核心概念。线程是程序的执行路径,多线程能提高程序效率和解决并发问题。文章详细讲解了四种创建线程的方式:继承Thread类、实现Runnable/Callable接口和使用线程池。同时阐述了线程的生命周期状态和常用方法,如start()、sleep()等。重点讨论了线程安全与同步机制,包括同步方法、同步代码块和Lock锁的使用。最后介绍了线程间通信的wait()/not
1. 什么是线程?
在Java编程中,线程是指程序的一条执行路径。当我们运行一个Java程序时,代码会按照特定的顺序执行,这个执行流程就是线程。
在学习多线程之前,我们编写的Java程序都只有一条执行路径——`main()`方法,这条执行路径被称为主线程。多线程编程允许我们创建多个执行路径,让程序能够同时处理多个任务。
2. 为什么需要学习多线程?
学习多线程主要有两个原因:
1. 提高程序执行效率:通过并行处理任务,充分利用多核CPU的计算能力
2. 应对并发问题:现代应用程序需要同时处理多个请求,多线程是解决并发问题的关键技术
需要区分几个相关概念:
- 程序:一段静态的代码
- 进程:正在运行的程序
- 线程:进程中的一条执行路径
- 并行:多个CPU同时执行多条路径
- 并发:一个CPU交替执行多条路径
3. 创建线程的四种方式
3.1 继承Thread类
```java
// 创建线程类
public class NumberThread extends Thread {
public NumberThread(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i <= 500; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
// 使用线程类
public class Main {
public static void main(String[] args) {
NumberThread nt = new NumberThread("线程nt");
nt.start(); // 启动线程
}
}
```
3.2 实现Runnable接口
```java
// 创建线程类
public class TicketRunnable implements Runnable {
int ticket = 10;
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "出售一张车票,还剩" + ticket + "张");
}
}
}
}
// 使用线程
public class Main {
public static void main(String[] args) {
TicketRunnable tr = new TicketRunnable();
Thread t1 = new Thread(tr, "窗口A");
Thread t2 = new Thread(tr, "窗口B");
Thread t3 = new Thread(tr, "窗口C");
t1.start();
t2.start();
t3.start();
}
}
```
3.3 实现Callable接口
```java
// 创建线程类
public class TicketCallable implements Callable {
static int ticket = 10;
@Override
public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "出售一张车票,还剩" + ticket + "张");
}
}
return null;
}
}
// 使用线程
public class Main {
public static void main(String[] args) {
TicketCallable tc1 = new TicketCallable();
FutureTask ft = new FutureTask(tc1);
Thread thread1 = new Thread(ft, "窗口A");
// 创建其他线程...
thread1.start();
// 启动其他线程...
}
}
```
3.4 使用线程池
```java
public class ThreadPoolExample {
public static void main(String[] args) {
ThreadPoolExecutor pool = new ThreadPoolExecutor(
5, // 核心线程数
10, // 最大线程数
100, // 空闲线程存活时间
TimeUnit.SECONDS, // 时间单位
new LinkedBlockingQueue<>(10) // 工作队列
);
for (int i = 0; i < 11; i++) {
final int j = i;
pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + ":" + j);
});
}
pool.shutdown(); // 关闭线程池
}
}
```
使用线程池的主要原因是为了减少频繁创建和销毁线程所带来的资源消耗。
4. 线程的生命周期
线程在其生命周期中会经历以下几种状态:
1. 新生状态:通过线程类构造方法创建线程对象
2. 就绪状态:调用`start()`方法后,线程等待CPU调度
3. 运行状态:获得CPU资源,执行`run()`方法中的代码
4. 阻塞状态:运行过程中遇到阻塞事件(如I/O操作、等待锁等)
5. 死亡状态:`run()`方法执行完毕、出现未捕获异常或调用`stop()`方法
5. 线程的常用方法
- `start()`:启动线程,实际调用线程的`run()`方法
- `run()`:线程执行的主体内容,需要重写此方法
- `currentThread()`:静态方法,获取当前正在执行的线程
- `setName()`/`getName()`:设置/获取线程名称
- `setPriority()`:设置线程优先级(1-10)
- `sleep()`:使当前线程暂停指定时间(毫秒)
- `setDaemon()`:设置线程为守护线程(伴随线程)
- `join()`:强制当前线程等待调用此方法的线程执行完毕
- `stop()`:强制终止线程(已过时,不推荐使用)
示例代码:
```java
public class ThreadMethodsExample {
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
if (i == 5) {
try {
Thread.sleep(1000); // 休眠1秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ": " + i);
}
}, "示例线程");
t.setPriority(8); // 设置较高优先级
t.start();
}
}
```
6. 线程安全与同步
当多个线程并发执行时,可能会产生数据安全问题。Java提供了多种机制来解决这个问题:
6.1 同步方法
```java
// 在继承Thread方式的线程类中
public static synchronized void testRun() {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "出售一张车票,还剩" + ticket + "张");
}
}
// 在实现Runnable方式的线程类中
public synchronized void testRun() {
// 同步代码...
}
```
6.2 同步代码块
```java
public void run() {
for (int i = 0; i < 500; i++) {
synchronized (TicketThread.class) { // 同步代码块
if (ticket > 0) {
ticket--;
System.out.println(this.getName() + "出售一张车票,还剩" + ticket + "张");
}
}
}
}
```
6.3 Lock锁
```java
public class TicketLockExample {
static Lock lock = new ReentrantLock();
static int ticket = 10;
public void run() {
for (int i = 0; i < 500; i++) {
lock.lock(); // 加锁
try {
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "出售一张车票,还剩" + ticket + "张");
}
} finally {
lock.unlock(); // 释放锁
}
}
}
}
```
7. 线程通信
线程间通信需要满足两个条件:
1. 多个线程之间要有一个共同的参照物(如boolean或int类型的标志)
2. 使用`wait()`、`notify()`和`notifyAll()`方法实现线程间通信
```java
public class ThreadCommunication {
private static final Object lock = new Object();
private static boolean flag = false;
public static void main(String[] args) {
Thread producer = new Thread(() -> {
synchronized (lock) {
// 生产数据...
flag = true;
lock.notify(); // 通知消费者
}
});
Thread consumer = new Thread(() -> {
synchronized (lock) {
while (!flag) {
try {
lock.wait(); // 等待生产者通知
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费数据...
}
});
producer.start();
consumer.start();
}
}
```
总结
多线程编程是Java中非常重要且复杂的主题。掌握线程的创建方式、生命周期管理、同步机制和线程间通信是编写高效、安全并发程序的关键。在实际开发中,应根据具体需求选择合适的线程实现方式和同步机制,以确保程序的正确性和性能。
随着Java版本的更新,还出现了更多高级的并发工具类(如`java.util.concurrent`包中的各种组件),这些工具可以简化多线程编程并提高程序性能,值得进一步学习和探索。
更多推荐
所有评论(0)