Java多线程编程:原理、同步与实践
立即解锁
发布时间: 2025-08-18 02:26:29 订阅数: 17 

### Java 多线程编程:原理、同步与实践
#### 1. 线程执行顺序与阻塞
线程的执行顺序可以从输出中观察到,实际顺序取决于操作系统调度器和处理器数量,因此不同机器可能不同。在`main()`方法中调用的`read()`方法会被阻塞,直到按下`Enter`键,而其他线程会继续执行。按下`Enter`键后,`main`线程继续执行并返回,由于其他线程是守护线程,当创建它们的线程死亡时,它们也会死亡,但可能会在`main`线程最后一次输出后继续运行一小段时间。
#### 2. 停止线程
如果在之前的示例中没有将线程创建为守护线程,它们将独立于`main`线程继续执行。可以通过注释掉构造函数中对`setDaemon()`的调用来演示这一点。按下`Enter`键会结束`main`线程,但其他线程会无限期继续执行。
可以通过调用`Thread`对象的`interrupt()`方法向另一个线程发出停止执行的信号。这本身不会停止线程,只是设置一个标志,表示已请求中断。必须在`run()`方法中检查此标志才能生效,然后线程应自行终止。`Thread`类定义的`isInterrupted()`方法在设置了中断标志时返回`true`,该方法不会重置标志,而调用`interrupted()`方法会测试标志并在设置时重置它。
示例代码如下:
```java
public static void main(String[] args) {
// Create three threads
Thread first = new TryThread("Hopalong ", "Cassidy ", 200L);
Thread second = new TryThread("Marilyn ", "Monroe ", 300L);
Thread third = new TryThread("Slim ", "Pickens ", 500L);
System.out.println("Press Enter when you have had enough...\n");
first.start(); // Start the first thread
second.start(); // Start the second thread
third.start(); // Start the third thread
try {
System.in.read(); // Wait until Enter key pressed
System.out.println("Enter pressed...\n");
// Interrupt the threads
first.interrupt();
second.interrupt();
third.interrupt();
} catch (IOException e) { // Handle IO exception
System.out.println(e); // Output the exception
}
System.out.println("Ending main()");
return;
}
```
当按下`Enter`键后,`main`方法会调用每个线程的`interrupt()`方法,每个线程中的`sleep()`方法会检测到线程已被中断并抛出`InterruptedException`,该异常会被`run()`方法中的`catch`块捕获,从而使每个线程的`run()`方法返回并终止线程。
可以通过调用线程的`isInterrupted()`方法检查线程是否被中断,该方法返回`true`表示线程已被调用过`interrupt()`。要测试线程是否仍在运行,可以调用其`isAlive()`方法。
#### 3. 连接线程
如果在一个线程中需要等待另一个线程死亡,可以调用该线程的`join()`方法。无参数的`join()`方法会暂停当前线程,直到指定的线程死亡:
```java
thread1.join(); // Suspend the current thread until thread1 dies
```
也可以传递一个`long`值给`join()`方法,指定等待线程死亡的毫秒数:
```java
thread1.join(1000); // Wait up to 1 second for thread1 to die
```
还有一个带有两个参数的`join()`版本,第一个参数是毫秒数,第二个参数是纳秒数。当前线程会等待参数总和指定的持续时间。`join()`方法可能会抛出`InterruptedException`,因此应该将其放在`try`块中并捕获异常。
#### 4. 线程调度
线程的调度在一定程度上取决于操作系统,但每个线程在其他线程“休眠”(即调用`sleep()`方法)时都有机会执行。如果操作系统使用抢占式多任务处理(如 Microsoft Windows 和 Linux),或者硬件有多个受操作系统支持的处理器,程序可以在`run()`方法中不调用`sleep()`的情况下工作。否则,若`run()`方法中没有`sleep()`调用,第一个线程会独占单个处理器并无限期继续执行。
`Thread`类中还有一个`yield()`方法,它可以让其他线程有机会执行。当只想让其他等待的线程参与执行,但不想为当前线程暂停特定时间段时,可以使用该方法。
#### 5. 实现 Runnable 接口
作为定义`Thread`新子类的替代方法,可以在类中实现`Runnable`接口。这通常比从`Thread`派生类更方便,因为可以从其他类派生,并且该类仍然可以表示一个线程。
示例代码如下:
```java
import java.io.IOException;
public class JumbleNames implements Runnable {
// Constructor
public JumbleNames(String firstName, String secondName, long delay) {
this.firstName = firstName; // Store the first name
this.secondName = secondName; // Store the second name
aWhile = delay; // Store the delay
}
// Method where thread execution will start
public void run() {
try {
while(true) { // Loop indefinitely...
System.out.print(firstName); // Output first name
Thread.sleep(aWhile); // Wait aWhile msec.
System.out.print(secondName+"\n"); // Output second name
}
} catch(InterruptedException e) { // Handle thread interruption
System.out.println(firstName + secondName + e); // Output the exception
}
}
public static void main(String[] args) {
// Create three threads
Thread first = new Thread(new JumbleNames("Hopalong ", "Cassidy ", 200L));
Thread second = new Thread(new JumbleNames("Marilyn ", "Monroe ", 300L));
Thread third = new Thread(new JumbleNames("Slim ", "Pickens ", 500L));
// Set threads as daemon
first.setDaemon(true);
second.setDaemon(true);
third.setDaemon(true);
System.out.println("Press Enter when you have had enough...\n");
first.start(); // Start the first thread
second.start(); // Start the second thread
third.start(); // Start the third thread
try {
System.in.read(); // Wait until Enter key pressed
System.out.println("Enter pressed...\n");
} catch (IOException e) { // Handle IO exception
System.err.println(e); // Output the exception
}
System.out.println("Ending main()");
return;
}
private String firstName; // Store for first name
private String secondName; // Store for second name
private long aWhile; // Delay in milliseconds
}
```
在这个示例中,`JumbleNames`类实现了`Runnable`接口,在`main`方法中创建`Thread`对象时,使用了接受`Runnable`类型对象作为参数的构造函数。
#### 6. 线程名称
线程有一个名称,在示例中使用的`Thread`构造函数创建的线程默认名称由字符串`“Thread*”`和一个序列号组成。如果想为线程选择自己的名称,可以使用接受`String`对象指定名称的`Thread`构造函数。例如:
```java
Thread first = new Thread(new JumbleNames("Hopalong ", "Cassidy ", 200L), "firstThread");
```
可以通过调用`Thread`对象的`getName()`方法获取线程名称,也可以通过调用`setName()`方法更改线程名称。
#### 7. 线程管理
在之前的示例中,线程启动后会竞争计算机资源,导致输出混乱。在大多数使用线程的情况下,需要管理线程的执行方式,以确保它们的活动协调且不相互干扰。
例如,在银行交易场景中,银行柜员向账户存入支票,同时客户通过 ATM 机取款,可能会出现账户余额计算错误的问题。当两个或多个线程共享一个公共资源(如文件或内存块)时,需要采取措施确保一个线程在另一个线程仍在使用该资源时不会修改它。
#### 8. 同步
同步的目标是确保当多个线程想要访问单个资源时,任何给定时间只有一个线程可以访问它。可以通过两种方式管理线程执行的同步:
- **同步方法**:可以使类对象的部分(或全部)方法互斥,即任何给定时间只有一个方法可以执行。通过在类中使用`synchronized`关键字声明方法来实现。例如:
```java
class MyClass {
synchronized public void method1() {
// Code for the method...
}
synchronized public void method2() {
// Code for the method...
}
public void method3() {
// Code for the method...
}
}
```
在这个示例中,`method1()`和`method2()`是同步方法,任何给定时间只有一个同步方法可以执行。同步过程使用每个对象关联的内部锁,当同步方法开始执行时,会设置锁标志,其他同步方法会检查该标志,直到锁被重置才会开始执行。
需要注意的是,同一类的两个不同对象的同步方法可以同时执行,只有对同一个对象的并发访问才受同步控制。
- **同步代码块**:除了同步方法,还可以指定程序中的语句或代码块为同步的,这样可以指定特定对象受益于同步,而不仅仅是包含代码的对象。例如:
```java
synchronized(theObject)
statement; // Synchronized with respect to theObject
```
当同步代码块执行时,其他对同一对象同步的代码块或方法不能执行。
下面是一个银行交易的示例,展示了如何使用同步代码块:
```java
public class Bank {
// Perform a transaction
public void doTransaction(Transaction transaction) {
switch(transaction.getTransactionType()) {
case CREDIT:
synchronized(transaction.getAccount()) {
// Get current balance
int balance = transaction.getAccount().getBalance();
// Credits require a lot of checks...
try {
Thread.sleep(100);
} catch(InterruptedException e) {
System.out.println(e);
}
balance += transaction.getAmount(); // Increment the balance
transaction.getAccount().setBalance(balance); // Restore A/C balance
break;
}
case DEBIT:
synchronized(transaction.getAccount()) {
// Get current balance
int balance = transaction.getAccount().getBalance();
// Debits require even more checks...
try {
Thread.sleep(150);
} catch(InterruptedException e) {
System.out.println(e);
}
balance -= transaction.getAmount(); // Decrement the balance...
transaction.getAccount().setBalance(balance);// Restore A/C balance
break;
}
default: // We should never get here
System.out.println("Invalid transaction");
System.exit(1);
}
}
}
```
在这个示例中,`doTransaction`方法根据交易类型对账户对象进行同步,确保同一账户的操作不会同时进行。
#### 9. 银行交易示例
为了更好地理解线程同步,下面详细介绍一个银行交易的示例,该示例包含以下几个类:
- **Bank 类**:表示银行的计算机,负责执行账户交易。
```java
// Define the bank
public class Bank {
// Perform a transaction
public void doTransaction(Transaction transaction) {
int balance = transaction.getAccount().getBalance(); // Get current balance
switch(transaction.getTransactionType()) {
case CREDIT:
// Cred
```
0
0
复制全文
相关推荐









