序言:
相对于传统的单线程,多线程在操作系统多核配置的基础上,能够更好地利用服务器的多个CPU资源,使程序运行起来更加高效。Java通过提供对多线程的支持来在一个进程内并发执行多个线程,每个线程都并行执行不同的任务,以满足编写高效率程序的要求。
Java中的线程
基本介绍:
操作系统在运行一个程序时,会为其创建一个进程。进程是操作系统分配资源的基本单位,而线程也叫轻量级进程(Light Weight Process),是处理器调度的基本单位,一个进程里可以创建多个线程,这些线程都拥有各自的计数器、堆栈和局部变量等属性,并且能够访问共享的内存变量。在Java中,每个已经调用过start()方法且还未结束的java.lang.Thread类的实例就代表着一个线程,每一个Java线程都是直接映射到一个操作系统原生线程。
创建方式:
常见的Java线程的创建方式有4种,下面依次介绍:
- 继承Thread类
说明:Thread类实现了Runnable接口并定义了操作线程的一些方法,我们可以通过继承Thread类的方式创建一个线程。
步骤:创建一个类并继承Thread类,然后实例化线程对象并调用start方法启动线程。
代码演示:
// 通过继承Thread类创建线程
class MyThread extends Thread {
@Override
public void run() {
// 该线程要执行的任务
}
}
// 创建线程的实例
MyThread myThread = new MyThread();
// 调用start方法启动线程
myThread.start();
- 实现Runnable接口
说明:基于Java编程语言的规范,如果子类已经继承(extends)了一 个类,就无法再直接继承Thread类,此时可以通过实现Runnable接 口创建线程。
步骤:通过实现Runnable接口创建SubClassThread线程,实例化名称为subClassThread的线程实例,创建Thread类的实例并传入subClassThread线程实例,调用线程的start方法启动线程。
代码演示:
class SubClassThread extends SuperClass implements Runnable{
@Override
public void run() {
// 该线程要执行的任务
}
}
// 父类引用指向子类实例
Runnable subClassThread = new SubClassThread();
Thread thread = new Thread(subClassThread);
// 调用start方法启动线程
thread.start();
- 通过ExecutorService和Callable实现有返回值的线程
说明:有时,我们需要在主线程中开启多个线程并发执行一个任务,然后收集各个线程执行返回的结果并将最终结果汇总起来,这时就要用到Callable接口。
步骤::创建一个类并实现Callable接口,在call方法中实现具体的运算逻辑并返回计算结果。创建一个线程池、一个用于接收返回结果的Future List及Callable线程实例,使用线程池提交任务并将线程执行之后的结果保存在Future中,在线程执行结束后遍历Future List中的Future对象,在该对象上调用get方法就可以获取Callable线程任务返回的数据并汇总结果。
代码演示:
class MyCallable implements Callable<String> {
private String name;
public MyCallable(String name){
this.name = name;
}
@Override
public String call() throws Exception {
// 执行计算任务
// ......
return name;
}
}
// 创建数量为10的固定线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 创建有多个返回值的任务列表futureList
List<Future> futureList = new ArrayList<>(10);
for (int i = 1; i <= 10; i++) {
// 创建一个有返回值的线程实例
Callable callable = new MyCallable("Thread-"+i);
// 提交线程,获取Future对象并保存到futureList中
Future future = threadPool.submit(callable);
futureList.add(future);
}
// 关闭线程池,等待线程执行结束
threadPool.shutdown();
// 汇总结果
for (Future future :
futureList) {
future.get();
// ......
}
- 基于线程池
说明:线程是非常宝贵的计算资源,在每次需要时创建并在运行结束后销毁是非常浪费资源的。我们可以使用缓存策略并使用线程池来创建线程。
步骤:创建一个线程池并用该线程池提交线程任务。
代码演示:
// 创建数量为10的固定线程池
ExecutorService threadPool = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
threadPool.submit(() -> System.out.println(Thread.currentThread().getName()+"is running"));
}
生命周期:
java.lang.Thread类中定义了一个枚举类Sate,封装了线程的6种状态,如下图所示,
下面依次介绍这6种状态的含义:
- NEW(新建状态,即线程还没有调用start方法启动)
- RUNNABLE(可运行状态,即调用了start方法之后进入该状态,包括正在运行中的和就绪的线程)
- BLOCKED(阻塞状态,如线程调用了wait方法;线程等待锁的获取)
- WAITING(等待状态,如线程调用了join方法;除非被其他线程唤醒,否则永久等待)
- TIMED_WAITING(等待状态,如线程调用了sleep方法;带时间的等待,超时会自动苏醒,当然期间也可提前被唤醒)
- TERMINATED(终止状态)
基本方法:
线程相关的基本方法有wait、notify、notifyAll、sleep、join、yield等,这些方法控制线程的运行,并影响线程的状态变化。
-
wait方法(线程等待)
说明:调用wait方法的线程会进入WATING状态,只有等到其他线程的通知或被中断后才会返回。需要注意的是,在调用wait方法后会释放对象的锁,因此wait方法一般被用于同步方法或同步代码块中。 -
sleep方法(线程睡眠)
说明:调用sleep方法会导致当前线程休眠。与wait方法不同的是,sleep 方法不会释放当前占有的锁 , 会导致线程进入TIMED- WATING状态,而wait方法会导致当前线程进入WATING状态。 -
yield方法(线程让步)
说明:调用yield方法会使当前线程让出(释放)CPU执行时间片,与其他线程一起重新竞争CPU时间片。在一般情况下,优先级高的线程更有可能竞争到CPU时间片,但这不是绝对的,有的操作系统对线程的优先级并不敏感。 -
interrupt方法(线程中断)
说明:interrupt方法用于向线程发行一个终止通知信号,会影响该线程内部的一个中断标识位,这个线程本身并不会因为调用了interrupt方法而改变状态(阻塞、终止等)。状态的具体变化需要等待接收到中断标识的程序的最终处理结果来判定。 -
join方法(线程加入)
说明:join方法用于等待其他线程终止,如果在当前线程中调用一个线程的join方法,则当前线程转为阻塞状态,等到另一个线程结束,当前线程再由阻塞状态转为就绪状态,等待获取CPU的使用权。 -
notify方法(线程唤醒)
说明:notify方法用于唤醒在此对象监视器上等待的一个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的。
至此,Java中的线程介绍到这里,最后附上一张线程状态转换图