13 Java 线程的生命周期与控制

13 Java 线程的生命周期与控制

在 Java 中,线程具有明确的生命周期和状态。理解线程的生命周期对我们编写高效、稳定的多线程程序至关重要。

一、线程的状态

Java 中的线程主要有以下几种状态:

  1. 新建状态 (NEW) - 线程被创建,但尚未启动。
  2. 就绪状态 (RUNNABLE) - 线程已经被启动,准备好运行,但可能因为调度原因暂时未获得 CPU 时间。
  3. 运行状态 (RUNNING) - 线程正在执行。
  4. 阻塞状态 (BLOCKED) - 线程因等待锁而被阻塞,无法继续执行。
  5. 等待状态 (WAITING) - 线程等待其他线程的通知,以恢复执行。
  6. 超时等待状态 (TIMED_WAITING) - 线程在指定时间内等待。
  7. 死亡状态 (TERMINATED) - 线程执行完毕或由于异常终止。

二、线程的生命周期

线程的生命周期从线程的创建开始,通过多个状态间的转换,直到线程的终止。以下是线程状态之间的转换:

  • NEWRUNNABLE:调用 start() 方法使线程进入就绪状态。
  • RUNNABLERUNNING:线程获得 CPU 时间片,开始执行。
  • RUNNINGBLOCKED:线程尝试获取一个已经被其他线程持有的锁。
  • RUNNINGWAITING:线程调用 wait()join()LockSupport.park() 方法进入等待状态。
  • RUNNINGTIMED_WAITING:线程调用 sleep(long millis)wait(long timeout) 等方法进入超时等待状态。
  • BLOCKEDRUNNABLE:当线程获取到锁后,继续执行。
  • WAITINGRUNNABLE:当其他线程调用 notify()notifyAll() 或当前线程的 join 方法时,线程恢复执行。
  • TIMED_WAITINGRUNNABLE:超时时间到达,线程恢复执行。
  • RUNNINGTERMINATED:线程执行结束,进入死亡状态。

三、线程控制

我们可以通过多种方式控制线程的状态与行为,以下是一些常用的方法。

1. 启动线程

使用 Thread 类的 start() 方法启动线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程正在运行");
}
}

public class ThreadExample {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}

2. 线程的休眠

使用 Thread.sleep(long millis) 方法使当前线程休眠指定毫秒数。

1
2
3
4
5
6
7
8
9
10
11
public class SleepExample {
public static void main(String[] args) {
System.out.println("线程即将休眠");
try {
Thread.sleep(2000); // 休眠2秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程已经恢复");
}
}

3. 等待与通知

使用 Object.wait()Object.notify() 来控制线程间的协作。

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
37
38
39
40
41
42
43
44
45
class Resource {
private int count = 0;

public synchronized void produce() throws InterruptedException {
while (count >= 1) {
wait(); // 等待资源可用
}
count++;
System.out.println("生产了一件产品");
notify(); // 通知消费者
}

public synchronized void consume() throws InterruptedException {
while (count <= 0) {
wait(); // 等待资源可用
}
count--;
System.out.println("消费了一件产品");
notify(); // 通知生产者
}
}

public class ProducerConsumerExample {
public static void main(String[] args) {
Resource resource = new Resource();

// 生产者线程
new Thread(() -> {
try {
resource.produce();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();

// 消费者线程
new Thread(() -> {
try {
resource.consume();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}

4. 终止线程

通过设置线程标志位,安全的终止线程。

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
class StoppableThread extends Thread {
private volatile boolean running = true;

@Override
public void run() {
while (running) {
System.out.println("线程正在运行...");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
System.out.println("线程终止");
}

public void stopRunning() {
running = false;
}
}

public class StopThreadExample {
public static void main(String[] args) throws InterruptedException {
StoppableThread thread = new StoppableThread();
thread.start();
Thread.sleep(2000); // 主线程睡眠2秒
thread.stopRunning(); // 发送停止信号
}
}

四、小结

掌握线程的生命周期与控制方法是多线程编程的关键。通过理解线程的状态及其控制机制,可以在并发编程中更有效地管理线程,提高程序的性能和稳定性。使用如 sleep()wait()notify() 等方法,可以灵活地控制线程执行的顺序与状态,确保资源的有效利用。

赋值运算符

赋值运算符

在 Java 中,赋值运算符用于将右边的值赋给左边的变量。赋值运算符的基本形式是 =,也称为 简单赋值运算符。在本节中,我们将详细讨论赋值运算符及其变种,并提供示例代码进行说明。

1. 简单赋值

简单赋值操作将右侧的值复制到左侧的变量中。例如:

1
2
3
int a;          // 声明一个整型变量 a
a = 10; // 将值 10 赋值给变量 a
System.out.println(a); // 输出: 10

在这个例子中,a 被赋值为 10,然后我们打印 a 的值。

2. 加法赋值运算符 +=

加法赋值运算符 += 是一个复合赋值运算符,用于将右侧的值加到左侧的变量上,然后将结果赋值给左侧变量。例如:

1
2
3
int b = 5;     // 初始化变量 b 为 5
b += 3; // 相当于 b = b + 3
System.out.println(b); // 输出: 8

在这个示例中,b 的值从 5 增加了 3,变为 8

3. 减法赋值运算符 -=

减法赋值运算符 -= 允许从左侧变量中减去右侧的值并赋值。例如:

1
2
3
int c = 10;     // 初始化变量 c 为 10
c -= 4; // 相当于 c = c - 4
System.out.println(c); // 输出: 6

在这个例子中,c 的值减去了 4,结果为 6

4. 乘法赋值运算符 *=

乘法赋值运算符 *= 用于将左侧变量的值与右侧变量的值相乘并赋值。例如:

1
2
3
int d = 2;     // 初始化变量 d 为 2
d *= 5; // 相当于 d = d * 5
System.out.println(d); // 输出: 10

在这个示例中,d 的值通过乘以 5 变为 10

5. 除法赋值运算符 /=

除法赋值运算符 /= 用于将左侧变量的值除以右侧的值,并将结果赋值给左侧变量。例如:

1
2
3
int e = 20;    // 初始化变量 e 为 20
e /= 4; // 相当于 e = e / 4
System.out.println(e); // 输出: 5

在这个例子中,e 的值被 4 除,结果是 5

6. 取余赋值运算符 %=

取余赋值运算符 %= 用于将左侧变量的值取右侧值的余数并赋值。例如:

1
2
3
int f = 10;    // 初始化变量 f 为 10
f %= 3; // 相当于 f = f % 3
System.out.println(f); // 输出: 1

在这个例子中,10 除以 3 的余数是 1

7. 赋值运算符总结

赋值运算符在 Java 中是非常重要的。通过简单赋值运算符和复合赋值运算符,我们能够方便地对变量进行赋值和运算。以下是所有赋值运算符的总结:

  • = : 简单赋值
  • += : 加法赋值
  • -= : 减法赋值
  • *= : 乘法赋值
  • /= : 除法赋值
  • %= : 取余赋值

赋值运算符不仅使代码更简洁,而且提高了可读性。记住这些运算符的使用方法对你学习 Java 编程非常重要。

多线程同步与锁机制

多线程同步与锁机制

多线程编程是 Java 语言中一个重要的特性,而在多线程环境下,同步机制则是保障数据一致性和线程安全的关键。下面将详细介绍 Java 的多线程同步与锁机制。

1. 线程安全与同步

1.1 线程安全

线程安全是指多个线程访问同一资源且不会导致数据不一致的状态。在 Java 中,确保线程安全有多种策略,最常见的是 同步

1.2 同步的重要性

当多个线程同时读写同一共享资源时,可能会产生数据不一致的情况。使用 同步 可以确保同一时间只有一个线程可以访问特定代码块。

2. 同步方法与同步代码块

在 Java 中有两种主要的同步机制:同步方法同步代码块

2.1 同步方法

使用 synchronized 关键字修饰方法,表示该方法是同步的。

1
2
3
4
5
6
7
8
9
10
11
public class SynchronizedMethodExample {
private int count = 0;

public synchronized void increment() {
count++;
}

public int getCount() {
return count;
}
}

在上面的示例中,increment 方法是一个同步方法,确保在任何时刻只有一个线程可以执行该方法,从而避免了并发问题。

2.2 同步代码块

有时我们只需要钩子方法中的一部分是同步的,可以使用同步代码块。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class SynchronizedBlockExample {
private int count = 0;

public void increment() {
// 同步块,锁定当前对象
synchronized (this) {
count++;
}
}

public int getCount() {
return count;
}
}

在这个例子中,只有 count++ 操作是同步的,这样可以提高程序的并发性。

3. 自定义锁

除了使用 synchronized,Java 提供了更灵活、更强大的锁机制,如 ReentrantLock

3.1 ReentrantLock 示例

ReentrantLockjava.util.concurrent.locks 包中的一个类,它实现了 Lock 接口。

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

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

public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 确保最终释放锁
}
}

public int getCount() {
return count;
}
}

在这个例子中,我们使用了 ReentrantLock 来确保 increment 方法线程安全。注意,锁的获取需要调用 lock(),释放锁需要在 finally 块中调用 unlock()

4. 读写锁

当读操作远多于写操作时,可以使用 ReadWriteLock 来提高性能。

4.1 使用 ReentrantReadWriteLock

ReentrantReadWriteLock 允许多个读线程同时访问共享资源,但在写线程进行写操作时会阻塞所有读和写线程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
private int count = 0;
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

public void increment() {
rwLock.writeLock().lock();
try {
count++;
} finally {
rwLock.writeLock().unlock();
}
}

public int getCount() {
rwLock.readLock().lock();
try {
return count;
} finally {
rwLock.readLock().unlock();
}
}
}

在这里,我们使用 writeLock()readLock() 来管理对 count 变量的访问,确保线程安全。

5. 总结

5.1 选择合适的同步策略

  • 对于简单的共享数据操作,可以使用 synchronized 关键字进行同步。
  • 对于需要更高灵活性和性能的场景,使用 ReentrantLockReadWriteLock 可能更合适。

5.2 注意死锁

在使用锁时,需谨慎管理锁的获取和释放,避免出现 死锁(Dead Lock)的问题。确保所有锁都在合适的顺序释放,避免一个线程持有一个锁并等待另一个锁。

通过对多线程同步与锁机制的理解和应用,可以提升 Java 程序的并发能力和性能。掌握这些技术,对于进入更高级的 Java 开发阶段是至关重要的。