文章目录
- 进程和线程
- 进程(Process)
- 线程(Thread)
- 线程的创建
- 1)继承 Thread 类
- 2)实现 Runnable 接口
- 3)使用 Lambda 表达式
- 4)总结
- 线程的状态
- 状态的分类
- 状态间转换
多线程是一种 同时执行多个线程的机制,它使得程序能够 更有效地利用 CPU 资源,提高系统的响应性。在 Java 中,多线程是一项强大的特性,允许程序在同一时间执行多个独立的任务。
进程和线程
进程(Process)
进程是程序的一次动态执行,需要经历从代码加载,代码执行以及执行完毕的一个完整的过程。由于 CPU
的具备分时机制,也即把 CPU
划分为无数个小的时间片,每个时间片去执行一个进程(程序),让我们感觉程序在同时运行一样。
例如,我们可以在电脑上同时打开多个 World,每个 World 就是一个进程。
线程(Thread)
线程是进程中的一个执行单元,负责执行程序中的代码。一个进程可以包含多个线程,它们共享进程的资源。线程之间共享同一份内存,因此线程间通信更加容易。
例如,我们在一个 World 里在打字的同时,World 还可以为我们做拼写检查。
这里的打字和检查都是一个线程,当 World 关闭的时候,线程也会跟着消失。
线程的创建
1)继承 Thread 类
通过继承 Thread
类,可以创建一个线程类,然后重写 run()
方法,该方法包含线程要执行的代码
实例代码:
public class Demo {
public static void main(String[] args) {
// 创建线程
ThreadDemo thread1 = new ThreadDemo();
ThreadDemo thread2 = new ThreadDemo();
// 启动线程
thread1.start();
thread2.start();
}
}
class ThreadDemo extends Thread {
public void run() {
// 线程执行的任务
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value " + i);
}
}
}
输出结果:
21 Value 0
20 Value 0
20 Value 1
21 Value 1
21 Value 2
21 Value 3
21 Value 4
20 Value 2
20 Value 3
20 Value 4
2)实现 Runnable 接口
通过实现 Runnable
接口,可以将线程的任务封装在一个类中,然后创建 Thread
对象并将该类的实例传递给 Thread
的构造函数
实例代码:
public class Demo {
public static void main(String[] args) {
// 创建线程
Thread thread1 = new Thread(new RunnableDemo());
Thread thread2 = new Thread(new RunnableDemo());
// 启动线程
thread1.start();
thread2.start();
}
}
class RunnableDemo implements Runnable {
public void run() {
// 线程执行的任务
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value " + i);
}
}
}
输出结果:
20 Value 0
20 Value 1
21 Value 0
21 Value 1
21 Value 2
21 Value 3
21 Value 4
20 Value 2
20 Value 3
20 Value 4
3)使用 Lambda 表达式
在 Java 8 及以后的版本,可以使用 Lambda
表达式简化创建线程的代码
实例代码:
public class Demo {
public static void main(String[] args) {
// 使用Lambda表达式创建线程1
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value " + i);
}
});
// 使用Lambda表达式创建线程2
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getId() + " Value " + i);
}
});
// 启动线程
thread1.start();
thread2.start();
}
}
输出结果:
20 Value 0
21 Value 0
20 Value 1
20 Value 2
20 Value 3
21 Value 1
20 Value 4
21 Value 2
21 Value 3
21 Value 4
4)总结
无论采用哪种方式,都需要调用 start()
方法来启动线程。 start()
方法会在一个新的线程中调用 run()
方法。避免直接调用 run()
方法,因为这样并不会在新线程中执行,而只是在当前线程中作为普通的方法调用。
推荐使用 Runnable
接口的方式,因为 Java 不支持多重继承,而通过实现接口更为灵活可以避免这个限制。 此外,Runnable
接口可以被多个线程共享,提高代码的可复用性。
线程的状态
状态的分类
多线程的状态主要包括以下几种:
- 新建(New): 线程被创建但尚未启动。
- 就绪(Runnable): 线程处于就绪状态,等待系统调度执行。
- 运行(Running): 线程正在执行其任务。
- 阻塞(Blocked): 线程被阻塞,等待获取某个锁或等待某个资源。
- 等待(Waiting): 线程无限期等待另一个线程执行特定操作。
- 超时等待(Timed Waiting): 线程等待另一个线程执行特定操作,但具有等待超时时间。
- 终止(Terminated): 线程已经执行完毕或因异常而终止。
这些状态构成了线程的生命周期,线程在这些状态之间来回转换。
示例代码:
public class ThreadStateDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
// 在新建状态
printThreadState("New");
// 启动线程,进入就绪状态
Thread.yield();
printThreadState("Runnable");
// 线程获取锁,进入运行状态
synchronized (ThreadStateDemo.class) {
printThreadState("Running");
// 线程调用wait(),进入等待状态
try {
ThreadStateDemo.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState("Waiting");
// 等待超时后重新进入运行状态
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
printThreadState("Running");
}
});
// 新建状态
printThreadState("New");
// 启动线程,进入就绪状态
thread.start();
Thread.yield();
printThreadState("Runnable");
// 主线程等待一会儿
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 线程被唤醒,进入就绪状态
synchronized (ThreadStateDemo.class) {
ThreadStateDemo.class.notify();
}
Thread.yield();
printThreadState("Runnable");
// 主线程等待线程执行完毕
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 终止状态
printThreadState("Terminated");
}
private static void printThreadState(String state) {
long tid = Thread.currentThread().getId();
System.out.println("Thread State: " + state + ", Thread ID: " + tid);
}
}
输出结果:
Thread State: New, Thread ID: 1
Thread State: Runnable, Thread ID: 1
Thread State: New, Thread ID: 20
Thread State: Runnable, Thread ID: 20
Thread State: Running, Thread ID: 20
Thread State: Waiting, Thread ID: 20
Thread State: Runnable, Thread ID: 1
Thread State: Running, Thread ID: 20
Thread State: Terminated, Thread ID: 1
在这个例子中,通过一个新建的线程演示了新建、就绪、运行、等待等状态的转换。注意到在等待状态时,通过notify()
方法唤醒线程,然后等待超时后重新进入运行状态。最后,主线程等待新建的线程执行完毕,线程进入终止状态。这个例子模拟了多线程状态的典型转换过程。
状态间转换
下面是线程状态之间的转换:
- 新建 -> 就绪: 调用线程的
start()
方法。 - 就绪 -> 运行: 线程被系统调度执行。
- 运行 -> 就绪: 线程调用
yield()
方法,主动让出CPU时间。 - 运行 -> 阻塞: 线程调用阻塞式的IO操作,等待锁,或者调用
sleep()
等方法。 - 阻塞 -> 就绪: 阻塞的原因消失。
- 运行/阻塞 -> 终止: 线程执行完
run()
方法或者因为异常退出了run()
方法。 - 等待 -> 就绪/阻塞: 调用了
notify()
、notifyAll()
方法,或者等待的时间到了。 - 超时等待 -> 就绪/阻塞: 等待时间到了,或者调用
notify()
、notifyAll()
方法。
多线程编程是一门复杂而有趣的艺术,合理的多线程设计能够提高程序的性能和响应性。在进行多线程编程时,了解线程的基本概念、合理使用同步和通信机制,以及注意最佳实践,将有助于编写出高质量、可维护的多线程程序。