前言和大纲
计算机系统里每个进程(Process)都代表着一个运行着的程序,进程是对运行时程序的封装,系统进行资源调度和分配的基本单位。
一个进程下可以有很多个线程,线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发,线程同时也是操作系统可识别的最小执行和调度单位。
在 Java 里线程是程序执行的载体,我们写的代码就是由线程运行的。有的时候为了增加程序的执行效率,我们不得不使用多线程进行编程,虽然多线程能最大化程序利用 CPU 的效率,但也是程序事故多发、程序员脱发的最大诱因。主要是平时我们的思维默认是单线程的,写多线程的时候得可以切换一下才行,这就要求我们对线程的基础知识了解的比较透彻。
这篇文章咱们总结一下 Java线程的基础,多掌握点,以后就少掉点头发,不光省下植发的钱,工资还能往上涨,这么一想简直双赢。本文的大纲如下:
好了让我们开始今天的正文,直接上代码吧。
Java 中的线程
到目前为止,我们写的所有 Java 程序代码都是在由JVM给创建的 Main Thread 中单线程里执行的。Java 线程就像一个虚拟 CPU,可以在运行的 Java 应用程序中执行 Java 代码。当一个 Java 应用程序启动时,它的入口方法 main() 方法由主线程执行。主线程(Main Thread)是一个由 Java 虚拟机创建的运行你的应用程序的特殊线程。
因为 Java 里一切皆对象,所以线程也是用对象表示的,线程是类 java.lang.Thread 类或者其子类的实例。在 Java 应用程序内部, 我们可以通过 Thread 实例创建和启动更多线程,这些线程可以与主线程并行执行应用程序的代码。
创建和启动线程
在 Java 中创建一个线程,就是创建一个 Thread 类的实例
代码语言:javascript复制 Thread thread = new Thread();
启动线程就是调用线程对象的 start() 方法
代码语言:javascript复制 thread.start();
当然,这个例子没有指定线程要执行的代码,所以线程将在启动后立即停止。
指定线程要执行的代码
有两种方法可以给线程指定要执行的代码。
- 第一种是,创建 Thread 的子类,覆盖父类的 run() 方法,在run() 方法中指定线程要执行的代码。
- 第二种是,将实现 Runnable (java.lang.Runnable) 的对象传递给 Thread 构造方法,创建Thread 实例。
其实,还有第三种用法,不过细究下来可归类到第二种的特殊使用方式,下面我们看看这三种的用法和区别。
通过 Thread 子类指定要执行的代码
通过继承 Thread 类创建线程的步骤:
- 定义 Thread 类的子类,并覆盖该类的 run() 方法。run() 方法的方法体就代表了线程要完成的任务,因此把 run 方法称为执行体。
- 创建 Thread 子类的实例,即创建了线程对象。
- 调用线程对象的 start 方法来启动该线程。
package com.learnthread;
public class ThreadDemo {
public static void main(String[] args) {
// 实例化线程对象
MyThread threadA = new MyThread("Thread 线程-A");
MyThread threadB = new MyThread("Thread 线程-B");
// 启动线程
threadA.start();
threadB.start();
}
static class MyThread extends Thread {
private int ticket = 5;
MyThread(String name) {
super(name);
}
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() " 卖出了第 " ticket " 张票");
ticket--;
}
}
}
}
上面的程序,主线程启动调用A、B两个线程的 start() 后,并没有通过wait() 等待他们执行结束。A、B两个线程的执行体,会并发地被系统执行,等线程都直接结束后,程序才会退出。
通过实现 Runnable 接口指定要执行的代码
Runnable 接口里,只有一个 run() 方法的定义:
代码语言:javascript复制package java.lang;
public interface Runnable {
public abstract void run();
}
其实,Thread 类实现的也是 Runnable 接口。 在 Thread 类的重载构造方法里,支持接收一个实现了 Runnale 接口的对象作为其 target 参数来初始化线程对象。
代码语言:javascript复制public class Thread implements Runnable {
...
public Thread(Runnable target) {
init(null, target, "Thread-" nextThreadNum(), 0);
}
...
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
...
}
通过实现 Runnable 接口创建线程的步骤:
- 定义 Runnable 接口的实现,实现该接口的 run 方法。该 run 方法的方法体同样是线程的执行体。
- 创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
- 调用线程对象的 start 方法来启动线程并执行。
package com.learnthread;
public class RunnableDemo {
public static void main(String[] args) {
// 实例化线程对象
Thread threadA = new Thread(new MyThread(), "Runnable 线程-A");
Thread threadB = new Thread(new MyThread(), "Runnable 线程-B");
// 启动线程
threadA.start();
threadB.start();
}
static class MyThread implements Runnable {
private int ticket = 5;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() " 卖出了第 " ticket " 张票");
ticket--;
}
}
}
}
运行上面例程会有以下输出,同样程序会在所以线程执行完后退出。
代码语言:javascript复制Runnable 线程-B 卖出了第 5 张票
Runnable 线程-B 卖出了第 4 张票
Runnable 线程-B 卖出了第 3 张票
Runnable 线程-B 卖出了第 2 张票
Runnable 线程-B 卖出了第 1 张票
Runnable 线程-A 卖出了第 5 张票
Runnable 线程-A 卖出了第 4 张票
Runnable 线程-A 卖出了第 3 张票
Runnable 线程-A 卖出了第 2 张票
Runnable 线程-A 卖出了第 1 张票
Process finished with exit code 0
既然是给 Thread 传递 Runnable 接口的实现对象即可,那么除了普通的定义类实现接口的方式,我们还可以使用匿名类和 Lambda 表达式的方式来定义 Runnable 的实现。
- 使用 Runnable 的匿名类作为参数创建 Thread 对象:
Thread threadA = new Thread(new Runnable() {
private int ticket = 5;
@Override
public void run() {
while (ticket > 0) {
System.out.println(Thread.currentThread().getName() " 卖出了第 " ticket " 张票");
ticket--;
}
}
}, "Runnable 线程-A");
- 使用实现了 Runnable 的 Lambda 表达式作为参数创建 Thread 对象:
Runnable runnable = () -> { System.out.println("Lambda Runnable running"); };
Thread threadB = new Thread(runnable, "Runnable 线程-B");
因为,Lambda 是无状态的,定义不了内部属性,这里就举个简单的打印一行输出的例子了,理解一下这种用法即可。