文章目录
- 进程和线程
- 进程(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表达式创建线程1Thread thread1 = new Thread(() -> {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getId() + " Value " + i);}});// 使用Lambda表达式创建线程2Thread 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()
方法。
多线程编程是一门复杂而有趣的艺术,合理的多线程设计能够提高程序的性能和响应性。在进行多线程编程时,了解线程的基本概念、合理使用同步和通信机制,以及注意最佳实践,将有助于编写出高质量、可维护的多线程程序。