Java 是一种多线程编程语言,这意味着我们可以使用 Java 开发多线程程序。一个多线程程序包含两个或更多可以并发运行的部分,并且每个部分可以同时处理不同的任务,从而充分利用可用资源,特别是在计算机有多核处理器的情况下。
按照定义,多任务处理指的是多个进程共享共同的处理资源,例如 CPU。多线程则将多任务处理的概念进一步扩展到应用程序中,在单一应用程序内你可以将特定的操作细分为单独的线程。每一个线程都可以并行运行。操作系统不仅会在不同的应用程序之间分配处理时间,还会在单个应用程序内的各个线程之间进行分配。
Java 多线程
多线程使你能够以一种方式编写代码,使得多个活动可以在同一个程序中同时进行。为了实现多线程(或者编写多线程代码),你需要使用 java.lang.Thread
类。
Java 中线程的生命周期
一个线程在其生命周期中会经历不同的阶段。例如,一个线程会从创建开始,启动后运行,最后结束其生命。下图显示了一个线程的完整生命周期。
Java 线程生命期 以下是线程生命周期的不同阶段:
-
新建 - 一个新的线程在其生命周期中处于新建状态,直到程序启动这个线程为止。这也被称为出生的线程。
-
可运行 - 在一个新生的线程被启动之后,线程变为可运行状态。在这个状态下,线程被认为是在执行其任务。
-
等待 - 有时候,当线程等待另一个线程完成任务时,线程会转换到等待状态。只有当另一个线程向等待的线程发出信号继续执行时,线程才会返回到可运行状态。
-
定时等待 - 一个可运行的线程可以在指定的时间间隔内进入定时等待状态。当这段时间过去或者它所等待的事件发生时,处于此状态下的线程会回到可运行状态。
-
终止(死亡) - 当一个可运行的线程完成其任务或其他原因终止时,它进入终止状态。
线程优先级
每个 Java 线程都有一个优先级,这有助于操作系统确定线程调度的顺序。
Java 线程优先级的范围是 MIN_PRIORITY
(常量值为 1)到 MAX_PRIORITY
(常量值为 10)。默认情况下,每个线程都会被赋予 NORM_PRIORITY
(常量值为 5)。
优先级较高的线程对于程序来说更重要,并且应该比优先级较低的线程先获得处理器时间。然而,线程优先级并不能保证线程执行的顺序,并且很大程度上依赖于平台。
通过实现 Runnable 接口创建线程
如果你的类打算作为一个线程执行,则可以通过实现 Runnable 接口来达到目的。你需要遵循以下三个基本步骤:
步骤 1: 实现 run() 方法
首先,你需要实现 Runnable 接口提供的 run() 方法。这个方法为线程提供了一个入口点,并且你会在这个方法里放置你的所有业务逻辑。run() 方法的简单语法如下:
public void run()
步骤 2: 实例化 Thread 对象
其次,你将使用以下构造函数实例化一个 Thread 对象:
Thread(Runnable threadObj, String threadName);
其中,threadObj 是实现了 Runnable 接口的一个类的实例,threadName 是给新线程命名的名字。
步骤 3: 使用 start() 方法启动线程
一旦创建了 Thread 对象,你就可以通过调用 start() 方法来启动它,这会执行对 run() 方法的调用。start() 方法的简单语法如下:
void start()
示例:通过实现 Runnable 接口创建线程
这里有一个创建新线程并启动它的例子:
class RunnableDemo implements Runnable {
private Thread t;
private String threadName;
RunnableDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}
public void run() {
System.out.println("Running " + threadName);
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
RunnableDemo R1 = new RunnableDemo("Thread-1");
R1.start();
RunnableDemo R2 = new RunnableDemo("Thread-2");
R2.start();
}
}
输出
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
通过继承 Thread 类创建线程
创建线程的第二种方法是创建一个新的类来继承 Thread 类,使用以下两个简单的步骤。这种方法提供了更多的灵活性来处理使用 Thread 类中可用的方法创建的多个线程。
步骤 1: 重写 run() 方法
你需要覆盖 Thread 类中提供的 run() 方法。这个方法为线程提供了一个入口点,并且你会在这个方法里放置你的所有业务逻辑。run() 方法的简单语法如下:
public void run()
步骤 2: 使用 start() 方法启动线程
一旦创建了 Thread 对象,你就可以通过调用 start() 方法来启动它,这会执行对 run() 方法的调用。start() 方法的简单语法如下:
void start()
示例:通过继承 Thread 类创建线程
下面是使用继承 Thread 类的方式重写的前面的程序:
class ThreadDemo extends Thread {
private Thread t;
private String threadName;
ThreadDemo(String name) {
threadName = name;
System.out.println("Creating " + threadName);
}
public void run() {
System.out.println("Running " + threadName);
try {
for(int i = 4; i > 0; i--) {
System.out.println("Thread: " + threadName + ", " + i);
Thread.sleep(50);
}
} catch (InterruptedException e) {
System.out.println("Thread " + threadName + " interrupted.");
}
System.out.println("Thread " + threadName + " exiting.");
}
public void start() {
System.out.println("Starting " + threadName);
if (t == null) {
t = new Thread(this, threadName);
t.start();
}
}
}
public class TestThread {
public static void main(String args[]) {
ThreadDemo T1 = new ThreadDemo("Thread-1");
T1.start();
ThreadDemo T2 = new ThreadDemo("Thread-2");
T2.start();
}
}
输出
Creating Thread-1
Starting Thread-1
Creating Thread-2
Starting Thread-2
Running Thread-1
Thread: Thread-1, 4
Running Thread-2
Thread: Thread-2, 4
Thread: Thread-1, 3
Thread: Thread-2, 3
Thread: Thread-1, 2
Thread: Thread-2, 2
Thread: Thread-1, 1
Thread: Thread-2, 1
Thread Thread-1 exiting.
Thread Thread-2 exiting.
线程方法
以下是 Thread
类中重要的方法列表:
序号 |
方法 & 描述 |
1 |
public void start() |
|
启动一个独立执行路径的线程,然后在这个 Thread 对象上调用 run() 方法。 |
2 |
public void run() |
|
如果这个 Thread 对象是使用一个单独的 Runnable 目标实例化的,那么将在该 Runnable 对象上调用 run() 方法。 |
3 |
public final void setName(String name) |
|
更改 Thread 对象的名称。也有一个 getName() 方法用于检索名称。 |
4 |
public final void setPriority(int priority) |
|
设置这个 Thread 对象的优先级。可能的值介于 1 到 10 之间。 |
5 |
public final void setDaemon(boolean on) |
|
如果参数为 true 表示将此 Thread 标记为守护线程。 |
6 |
public final void join(long millisec) |
|
当前线程在此方法上调用第二个线程,导致当前线程阻塞直到第二个线程终止或指定的毫秒数过去。 |
7 |
public void interrupt() |
|
中断这个线程,导致如果它因为任何原因而阻塞则继续执行。 |
8 |
public final boolean isAlive() |
|
如果线程还活着则返回 true ,即从线程启动后直到其运行完毕之前的时间段。 |
静态方法
前面提到的方法是在特定的 Thread
对象上调用的。下面列出的 Thread
类中的方法是静态的。调用这些静态方法将在当前运行的线程上执行操作。
静态方法列表
序号 |
方法 & 描述 |
1 |
public static void yield() |
|
导致当前正在运行的线程放弃执行权给任何相同优先级的正在等待被调度的其他线程。 |
2 |
public static void sleep(long millisec) |
|
导致当前正在运行的线程至少阻塞指定的毫秒数。 |
3 |
public static boolean holdsLock(Object x) |
|
如果当前线程持有给定对象的锁则返回 true 。 |
4 |
public static Thread currentThread() |
|
返回当前正在运行的线程的引用,即调用此方法的那个线程。 |
5 |
public static void dumpStack() |
|
打印当前运行线程的堆栈跟踪,这对于调试多线程应用程序非常有用。 |
示例
下面的 ThreadClassDemo
程序演示了 Thread
类的一些方法。考虑一个实现了 Runnable
的类 DisplayMessage
:
public class DisplayMessage implements Runnable {
private String message;
public DisplayMessage(String message) {
this.message = message;
}
@Override
public void run() {
while (true) {
System.out.println(message);
}
}
}
下面是一个扩展 Thread
类的类:
public class GuessANumber extends Thread {
private int number;
public GuessANumber(int number) {
this.number = number;
}
@Override
public void run() {
int counter = 0;
int guess = 0;
do {
guess = (int) (Math.random() * 100 + 1);
System.out.println(this.getName() + " guesses " + guess);
counter++;
} while (guess != number);
System.out.println("** Correct! " + this.getName() + " in " + counter + " guesses. **");
}
}
下面是主程序,它利用上面定义的类:
public class ThreadClassDemo {
public static void main(String[] args) {
Runnable hello = new DisplayMessage("Hello");
Thread thread1 = new Thread(hello);
thread1.setDaemon(true);
thread1.setName("hello");
System.out.println("Starting hello thread...");
thread1.start();
Runnable bye = new DisplayMessage("Goodbye");
Thread thread2 = new Thread(bye);
thread2.setPriority(Thread.MIN_PRIORITY);
thread2.setDaemon(true);
System.out.println("Starting goodbye thread...");
thread2.start();
System.out.println("Starting thread3...");
Thread thread3 = new GuessANumber(27);
thread3.start();
try {
thread3.join();
} catch (InterruptedException e) {
System.out.println("Thread interrupted.");
}
System.out.println("Starting thread4...");
Thread thread4 = new GuessANumber(75);
thread4.start();
System.out.println("main() is ending...");
}
}
输出
Starting hello thread...
Starting goodbye thread...
Hello
Hello
Hello
Hello
Hello
Hello
Goodbye
Goodbye
Goodbye
Goodbye
Goodbye
.......
Java 多线程的主要概念
在 Java 中进行多线程编程时,需要熟悉以下几个主要概念:
-
什么是线程同步?
线程同步是指控制多个线程访问共享资源或通信的一种机制,以防止数据竞争条件和确保数据一致性。
-
处理线程间通信
线程间通信通常涉及一个线程向另一个线程发送消息或信号,或者线程间共享某些信息。Java 提供了多种机制来支持线程间的同步通信,如使用 wait()
和 notify()
方法。
-
处理线程死锁
线程死锁是指两个或多个线程永远等待对方持有的资源而不释放自己的资源,从而导致它们都无法继续执行的情况。避免死锁通常需要精心设计资源共享策略。
-
主要的线程操作
主要的线程操作包括创建线程、启动线程、停止线程、设置线程优先级等。了解这些操作对于有效地管理多线程环境至关重要。