Java一大特色就是支持多线程编程。
可以使用三种方式实现多线程,分别是:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
1. 继承 Thread 类
java.lang.Thread 是一个线程操作的核心类。
1.1 创建线程
新建一个线程最简单的方法就是直接继承 Thread类,然后再覆写该类中的 run()方法,run方法 相当于主方法的 main,线程中的所有操作均在 run() 方法中实现。
class MyThread extends Thread {
@Override
// 覆写 run 方法,并在其内添加该线程所要执行的业务代码
public void run() {
System.out.println("创建线程");
}
}
1.2 启动进程
直接在主方法中调用覆写的 run 方法是不能启动线程的,其效果相当于把 run 当作普通方法进行调用。
正确启动多线程的方法:
调用 public symchronized void start() 方法。即调用 Therad类 的 start 方法。
创建线程有三种方法,可是启动线程只能使用 start 方法。
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
// 使用 Thread类 的 start方法 启动线程
myThread.start();
}
}
1.3 start 方法启动线程具体流程
启动线程流程:
- start方法 调用 start0() 方法,这个方法是一个只声明而未实现的方法,同时使用了 native 进行了修饰
private native void start0();
- 该方法调用了本地方法(native ,调用本地方法,C语言实现本地方法,一般为底层方法),Thread类 中有一个 registrNative 本地方法,该方法主要作用是注册一些本地方法供 Thread类 使用,如 start0(),stop0() 等,可以说,所有操作本地线程的本地方法都由它注册。
这个方法存放在一个 static块 中,当该类被加载到 JVM 中时,他就会被调用,注册相应的本地方法
所以 start0 实际调用的是本地方法 registrNative - registrNative方法 又调用了其中的 JVM_StartThread方法
- 而该方法又调用了 run方法
所以,总的流程是 start -> start0 -> registrNative -> JVM_StartThread -> run
1.4 不能重复启动线程
当重复启动同一个线程时 ,start 会抛出一个 IllegalThreadStateException 异常 。  因为 java.lang.Thread.start()方法一开始有一个状态检测,如果检测到当前线程已经启动,就会抛出这个异常。 2. 实现 Runnable 接口
如果一个类为了实现多线程直接去继承 Thread类 就会有单继承局限,因为子类继承了 Thread类 就不能再继承其他的父类了。2.1 创建线程
通过 实现 Runnable接口 来创建新的进程。class MyThread implements Runnable {
@Override
// 覆写 run 方法,并在其内添加该线程所要执行的业务代码
public void run() {
System.out.println("创建线程");
}
}
2.2 启动线程
Thread类 中有一个重载构造方法,可以接受一个 Runnable 对象。public Thread(Runnable target);
所以我们可以通过这个构造来启动线程。
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
}
}
也可以给 Thread类 构造传入 Runnable接口 的匿名内部类或 Lambda表达式 来启动线程。
public class Test {
public static void main(String[] args) {
// Runnable接口的匿名内部类
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("创建线程");
}
});
thread.start();
// Lambda表达式
Runnable runnable = () -> System.out.println("创建线程");
Thread thread2 = new Thread(runnable);
thread2.start();
}
}
2.3 继承 Therad类 和实现 Runnable接口 创建线程的区别
- 从使用形式上来说, Runnable 实现要比 Thread 好,因为 Runnable 可以避免单继承限制,一个子类可以实现多个接口。
- 另外,Thread 是一个实现 Runnable接口 的子类,并且 Thread 覆写了 Runnable接口的run方法。
所以 实现Runnable接口 创建线程的方式其实上一个代理设计模式。
实现子类为 真正业务类。
Thread类 为代理类。
两个类都实现 Runnable接口,组成代理设计模式。
- 实现 Runnable接口 创建线程的方式可以更好的描述线程之间的程序概念(Thread 不好描述)
3. 实现 Callable接口
从 JDK1.5 追加了新的开发包:java.uti.concurrent,这个包只要用来进行高并发编程使用,在这个包中有一个新的接口 Callable。
public interface Callable<V> {
V call() throws Exception;
}
Callable接口 是一个泛型接口,它的 run方法 有一个返回值。
3.1 创建线程
覆写 Callable接口 的 call方法,记得要传入具体类型参数。
class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
System.out.println("创建线程");
return true;
}
}
3.2 启动线程
将 Callable接口 接口的子类的实例化传入 FutureTask 的构造方法,来创建 FutureTask 的一个对象。
FutureTask task = new FutureTask<>(new MyThread());
将 FutureTask 的对象传入 Thread 构造,然后使用 start 启动线程。
new Thread(task).start();
public class Test12 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
FutureTask<Boolean> task = new FutureTask(new MyThread());
new Thread(task).start();
// get 方法或抛出异常
System.out.println(task.get());
}
}