Java线程

JAVA线程

一、多线程基础

进程是操作系统分配资源的最小单元,线程是操作系统调度的最小单元。一个进程中可以包含多个线程,这些线程共享进程的资源。

并发是指处理器通过时间片轮转等方式,在宏观上同时处理多个任务,但微观上这些任务实际上是交替执行的。并行则是指多个处理器核心真正地同时执行多个任务。在多核CPU架构下,多线程不仅能提高程序响应速度(将耗时操作放入后台线程),还能充分利用多核计算能力。

WIN系统(以及所有现代操作系统)实现多线程确实采用了时间片轮转的调度机制。即使在单核CPU上,也可以通过快速切换执行不同的线程,给人“同时”进行的错觉。在多核CPU上,多个线程可以真正并行执行。

二、创建线程的方式

多线程的具体表现形式如下,你执行后就能够发现主线程和输出abc的线程是齐头并进的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Mythread extends Thread{  //继承线程Thread公共类
public void run()
{
System.out.println("abc");
}
}

public class Th{

public static void main(String[] args) {
Mythread t = new Mythread();
t.start ();
System.out.println("1");
System.out.println("2");
System.out.println("3");
System.out.println("4");
}


}

1. 继承Thread类

通过继承Thread类并重写run方法(该方法为线程的入口点)来创建线程。调用start()方法(而非直接调用run方法)来启动线程,start()方法会启动一个新的执行线程并调用run方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class MyThread extends Thread { // 继承Thread类
@Override
public void run() { // 重写run方法
System.out.println("MyThread is running");
}
}

public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start(); // 启动线程,最终由JVM调用run方法
System.out.println("Main thread");
}
}

注意:直接调用run()方法只会像普通方法一样在当前线程执行,而不会启动新线程。

2. 实现Runnable接口(推荐)

实现Runnable接口的类并非线程类,而是任务类。需要将Runnable实例作为参数传递给Thread构造函数来创建线程。这种方式更灵活,避免了单继承的限制,也更符合面向对象的思想。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyRunnable implements Runnable { // 实现Runnable接口
@Override
public void run() {
System.out.println("MyRunnable is running");
}
}

public class Test {
public static void main(String[] args) {
MyRunnable task = new MyRunnable();
Thread t = new Thread(task); // 将Runnable任务传递给Thread构造函数
t.start();
System.out.println("Main thread");
}
}

三、线程的状态与生命周期

Java线程在其生命周期中可能处于以下几种状态(定义在Thread.State枚举中):

状态 说明
NEW (新建) 线程被创建但尚未调用start()方法
RUNNABLE (可运行) 调用start()后,线程正在JVM中执行或等待系统资源(如CPU)
BLOCKED (阻塞) 线程被阻塞,等待获取监视器锁(如进入synchronized块)
WAITING (等待) 线程进入等待状态,需要其他线程显式唤醒(如Object.wait()
TIMED_WAITING (超时等待) 线程在指定的时间内等待(如Thread.sleep(time)
TERMINATED (终止) 线程已执行完毕(run()方法完成)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
stateDiagram-v2
direction LR
[*] --> NEW
NEW --> RUNNABLE : start()
RUNNABLE --> TERMINATED : run()方法执行完毕

RUNNABLE --> BLOCKED : 等待进入synchronized块/方法
BLOCKED --> RUNNABLE : 获取到锁

RUNNABLE --> WAITING : Object.wait()<br/>Thread.join()(无参)
WAITING --> RUNNABLE : Object.notify()/notifyAll()

RUNNABLE --> TIMED_WAITING : Thread.sleep(time)<br/>Object.wait(timeout)<br/>Thread.join(timeout)
TIMED_WAITING --> RUNNABLE : 超时/被中断/被唤醒

四、线程同步

当多个线程访问共享资源时,可能会发生数据竞争(Data Race),导致数据不一致或其他不可预料的错误。Java提供了多种机制来保证线程安全。

1. synchronized 关键字

synchronized是Java中最基本的同步机制,它可以同步代码块方法

  • 同步代码块:需要显式指定一个对象作为
1
2
3
4
5
public void method() {
synchronized (lockObject) { // lockObject可以是任意对象,但所有线程必须共享同一个锁对象才有效
// 需要同步的代码
}
}

同步实例方法:锁是当前实例对象this)。

1
2
3
public synchronized void method() {
// 方法体
}

同步静态方法:锁是当前类的Class对象(如MyClass.class)。

1
2
3
public static synchronized void method() {
// 方法体
}

2. Lock 接口

从Java 5开始,提供了java.util.concurrent.locks.Lock接口(如ReentrantLock),它提供了比synchronized更灵活的锁操作,如尝试非阻塞获取锁、可中断的获取锁以及超时获取锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
private final Lock lock = new ReentrantLock();
private int count = 0;

public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 必须在finally块中释放锁,防止异常导致死锁
}
}
}

synchronized 与 Lock 的对比

特性 synchronized Lock
实现级别 JVM层面,原生语法 Java API层面
锁的释放 自动释放 必须手动调用unlock()
灵活性 相对简单,不灵活 非常灵活,支持多种获取锁的方式
性能 早期性能较差,后续版本有优化 在高竞争环境下性能可能更好
读写分离 不支持 支持(ReadWriteLock

五、线程间通信

线程间通信主要依靠等待/通知机制,核心方法是Object类的wait(), notify(), notifyAll()这些方法必须在synchronized同步块或同步方法内调用,否则会抛出IllegalMonitorStateException异常。

  • wait():使当前线程释放锁并进入等待(WAITING)状态,直到其他线程调用该对象的notify()notifyAll()方法。
  • notify():随机唤醒一个在该对象上等待的线程。
  • notifyAll():唤醒所有在该对象上等待的线程。

一个典型的生产者-消费者模型示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import java.util.LinkedList;

public class ProducerConsumerExample {
private final LinkedList<Integer> buffer = new LinkedList<>();
private final int CAPACITY = 5;
private final Object lock = new Object();

public void produce() throws InterruptedException {
int value = 0;
while (true) {
synchronized (lock) {
while (buffer.size() == CAPACITY) { // 缓冲区满,生产者等待
lock.wait();
}
buffer.add(value++);
System.out.println("Produced: " + value);
lock.notifyAll(); // 通知消费者可以消费了
}
Thread.sleep(1000); // 模拟生产过程耗时
}
}

public void consume() throws InterruptedException {
while (true) {
synchronized (lock) {
while (buffer.isEmpty()) { // 缓冲区空,消费者等待
lock.wait();
}
int value = buffer.removeFirst();
System.out.println("Consumed: " + value);
lock.notifyAll(); // 通知生产者可以生产了
}
Thread.sleep(1000); // 模拟消费过程耗时
}
}
}

六、线程死锁

死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,这些线程都将无法继续执行下去

死锁产生的四个必要条件(缺一不可):

互斥条件:一个资源每次只能被一个线程使用。

请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放。

不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行剥夺。

循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。

避免死锁的常见方法

避免嵌套锁:尽量只获取一个锁。如果必须多个,确保所有线程以相同的顺序获取锁。

使用定时锁:使用Lock接口的tryLock(long time, TimeUnit unit)方法,尝试获取锁超时则失败。

减少同步范围:减小临界区范围,尽快释放锁。

七、线程控制(补充)

join()方法:等待目标线程执行完毕,当前线程才继续执行。可用于线程间同步。

1
2
3
4
5
6
Thread t = new Thread(() -> {
// 一些操作
});
t.start();
t.join(); // 主线程在此等待线程t执行完毕
System.out.println("Thread t has finished.");

sleep()方法:让当前正在执行的线程休眠(暂停执行)指定的时间,但不会释放锁

yield()方法:提示调度器当前线程愿意让出CPU,但调度器可以忽略这个提示。

interrupt()方法:中断目标线程。如果目标线程因wait, sleep, join而阻塞,会抛出InterruptedException并清除中断状态;否则,只是设置一个中断标志位,需要线程自己检查(isInterrupted())并处理

注意suspend(), resume(), stop()等方法已被废弃,因为它们可能导致资源死锁和数据不一致,非常不安全。

八、线程池

频繁创建和销毁线程开销很大。线程池可以预先创建一定数量的线程,放入池中备用。有任务时,从池中取线程执行;任务完成后,线程返回池中复用。这减少了创建销毁的开销,可以控制并发数,更有效地管理线程。

Java通过java.util.concurrent.ExecutorService接口及其实现类(如ThreadPoolExecutor)来提供线程池功能。通常使用Executors工具类来创建常见的线程池:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {
public static void main(String[] args) {
// 创建一个固定大小的线程池(包含4个线程)
ExecutorService executor = Executors.newFixedThreadPool(4);

// 提交任务给线程池执行
for (int i = 0; i < 10; i++) {
int taskId = i;
executor.submit(() -> {
System.out.println("Executing task " + taskId + " via " + Thread.currentThread().getName());
});
}

// 关闭线程池(不再接受新任务,等待所有已提交任务完成)
executor.shutdown();
}
}