Java多线程:优先级调度、竞争条件与同步机制
立即解锁
发布时间: 2025-08-21 00:56:28 阅读量: 2 订阅数: 12 


Java编程艺术:从初学者到大师的进阶指南
# Java多线程:优先级调度、竞争条件与同步机制
## 1. 线程优先级与调度
### 1.1 并发与调度基础
在Java中,线程并发并非在所有情况下都能真正实现。只有当运行程序的机器拥有多个处理器时,多线程才能真正同时运行。若没有多处理器,JVM会通过时间调度来模拟并发,即线程轮流使用CPU。JVM使用基于优先级的调度算法来决定在任何时刻运行哪个线程。
线程的优先级范围从`Thread.MIN_PRIORITY`到`Thread.MAX_PRIORITY`。当创建一个线程时,它会被赋予与创建它的线程相同的优先级,但之后可以随时更改。线程的实际优先级受其线程组的最大优先级限制,线程组的最大优先级可以通过`ThreadGroup.setMaxPriority()`方法设置。
以下是线程和线程组与优先级相关的方法:
| 类 | 方法名 | 用途 |
| ---- | ---- | ---- |
| Thread | `public final int getPriority()` | 获取该线程的优先级 |
| Thread | `public final void setPriority(int newPriority)` | 将该线程的优先级设置为指定值或其线程组的最大优先级(取较小值) |
| ThreadGroup | `public final int getMaxPriority()` | 获取该线程组的最大优先级 |
| ThreadGroup | `public final void setMaxPriority(int newMaxPriority)` | 将该线程组的优先级设置为指定值或(如果有父线程组)其父线程组的最大优先级(取较小值) |
### 1.2 JVM的线程调度算法
JVM根据以下算法选择和调度线程:
1. 如果一个线程的优先级高于其他所有线程,那么JVM将运行该线程。
2. 如果有多个线程具有最高优先级,JVM将选择其中一个运行。
3. 一个正在运行的线程将继续运行,直到满足以下条件之一:
- 它通过`yield()`或`sleep()`方法自愿释放CPU。
- 它因等待资源而被阻塞。
- 它完成了`run()`方法。
- 一个优先级更高的线程变为可运行状态。
### 1.3 线程阻塞与抢占
如果一个线程的执行暂停以等待资源可用,则称该线程被阻塞。例如,一个正在写入文件的线程可能会在系统执行一些底层磁盘管理例程时多次阻塞。如果JVM停止运行一个线程以运行一个优先级更高的线程,则称该线程被抢占。
### 1.4 自私线程与线程饥饿
如果当前运行的线程优先级等于或高于其竞争对手,并且从不阻塞或自愿释放CPU,它可能会阻止其竞争对手运行,直到它完成。这样的线程被称为“自私”线程,而其他线程则被称为“饥饿”线程。线程饥饿通常是不可取的,例如,如果一个自私线程的优先级高于AWT/Swing线程,用户界面可能会冻结。因此,程序员有责任确保调度的公平性,可以通过在长时间运行的线程的`run`方法中定期调用`yield()`(或在适当的时候调用`sleep()`)来实现。
### 1.5 示例代码:PriorityPiPanel.java
以下是一个示例代码,展示了如何通过组合框控制线程的优先级:
```java
package chap16.priority;
import java.awt.BorderLayout;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;
import javax.swing.JComboBox;
import javax.swing.JFrame;
import chap16.pi.PiPanel1;
public class PriorityPiPanel extends PiPanel1 {
public PriorityPiPanel() {
Vector choices = new Vector();
for (int i = Thread.MIN_PRIORITY; i <= Thread.MAX_PRIORITY; ++i) {
choices.add(new Integer(i));
}
final JComboBox cb = new JComboBox(choices);
cb.setMaximumRowCount(choices.size());
cb.setSelectedItem(new Integer(producerThread.getPriority()));
cb.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
Integer item = (Integer)cb.getSelectedItem();
int priority = item.intValue();
producerThread.setPriority(priority);
}
});
buttonPanel.add(cb, BorderLayout.WEST);
}
public static void main(String[] arg) {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().setLayout(new GridLayout(2, 1));
f.getContentPane().add(new PriorityPiPanel());
f.getContentPane().add(new PriorityPiPanel());
f.pack();
f.show();
}
}
```
编译和执行该示例的命令如下:
```sh
javac –d classes -sourcepath src src/chap16/priority/PriorityPiPanel.java
java –cp classes chap16.priority.PriorityPiPanel
```
### 1.6 系统对线程的处理差异
不同系统处理多线程和线程优先级的方式存在差异。一些系统采用时间切片的线程管理方案,通过强制自私线程共享CPU时间来防止它们完全控制CPU。一些系统将线程优先级范围映射到较小的范围,导致优先级相近的线程被同等对待。一些系统采用老化方案或其他基本算法的变体,以确保低优先级线程也有机会运行。因此,程序的正确性不应依赖于底层系统的行为,应采用防御性编程,使用`Thread.sleep()`、`Thread.yield()`或自己的机制来确保应用程序线程按预期共享CPU。
### 1.7 示例代码:SliceMeter.java
以下是一个用于确定系统是否采用时间切片的示例代码:
```java
package chap16.timeslicing;
public class SliceMeter extends Thread {
private static int[] vals = new int[2];
private static boolean usesTimeSlicing = false;
private int myIndex;
private int otherIndex;
private SliceMeter(int myIndex, int otherIndex) {
this.myIndex = myIndex;
this.otherIndex = otherIndex;
setPriority(Thread.MAX_PRIORITY);
}
public void run() {
int lastOtherVal = vals[otherIndex];
for (int i = 1; i <= Integer.MAX_VALUE; ++i) {
if (usesTimeSlicing) {
return;
}
int curOtherVal = vals[otherIndex];
if (curOtherVal != lastOtherVal) {
usesTimeSlicing = true;
int numLoops = curOtherVal - lastOtherVal;
lastOtherVal = curOtherVal;
System.out.println(
("While meter" + myIndex + " waited, ")
+ ("meter" + otherIndex + " looped " + numLoops + " times"));
}
vals[myIndex] = i;
}
}
public static void main(String[] arg) {
SliceMeter meter0 = new SliceMeter(0, 1);
SliceMeter meter1 = new SliceMeter(1, 0);
meter0.start();
meter1.start();
try {
meter0.join();
meter1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("usesTimeSlicing = " + usesTimeSlicing);
}
}
```
编译和执行该示例的命令如下:
```sh
javac –d classes -sourcepath src src/chap16/timeslicing/SliceMeter.java
java –cp classes chap16.timeslicing.SliceMeter
```
### 1.8 线程的join()方法
该示例还展示了`join()`方法的使用。如果没有在第39和40行调用`join()`方法,主线程会在`meter0`和`meter1`完成工作之前报告`usesTimeSlicing`为`false`并终止。通过调用`join()`方法,`SliceMeter`指示当前线程(即`main`线程)等待`meter0`和`meter1`终止后再继续执行。
以下是线程的`join()`方法:
| 方法名 | 用途 |
| ---- | ---- |
| `public final void join()` throws InterruptedException | 使当前线程等待,直到该线程终止 |
| `public final void join(long millis)` throws InterruptedException | 使当前线程等待不超过指定的时间,直到该线程终止 |
| `public final void join(long millis, int nanos)` throws InterruptedException | 使当前线程等待不超过指定的时间,直到该线程终止 |
### 1.9 线程调度快速回顾
- JVM采用抢占式算法来调度等待运行的线程。
- 设置线程的优先级会影响JVM调度该线程的方式和时间,进而影响线程的性能。
- 线程应调用`yield()`或`sleep()`或采取其他措施与竞争线程共享CPU。
- 不采取措施与竞争线程共享CPU的线程被称为自私线程。
- 底层系统不能保证防止自私线程完全控制CPU。
- 程序的正确性不应依赖于任何系统的具体细节。
## 2. 竞争条件与同步机制
### 2.1 竞争条件示例
考虑以下`LongSetter`类:
```java
package chap16.race;
public final class LongSetter {
private long x;
public boolean set(long xval) {
x = xval;
return (x == xval);
}
}
```
问题是:`LongSetter`的实例是否可能在其`set()`方法返回`false`的程序中使用?答案是肯定的。如果只有一个线程一次调用`LongSetter`的`set()`方法,一切正常,但如果两个或多个线程同时调用该方法,就会出现问题。
### 2.2 示例代码:Breaker.java
以下是一个旨在“破坏”`LongSetter`的程序:
```java
package chap16.race;
public class Breaker extends Thread {
private final static LongSetter longSetter = new LongSetter();
private final long value;
public Breaker(long value) {
this.value = value;
}
public void run() {
long count = 0;
boolean success;
while (true) {
success = longSetter.set(value);
count++;
if (!success) {
System.out.println(
"Breaker " + value + " broke after " + count + " tries.");
System.exit(0);
}
}
}
public static void main(String[] arg) {
new Breaker(1).start();
new Breaker(2).start();
new Breaker(3).start();
new Breaker(4).start();
}
}
```
编译和执行该示例的命令如下:
```sh
javac –d classes -sourcepath src src/c
```
0
0
复制全文
相关推荐










