Java 有三种方式实现多线程,继承 Thread 类、实现 Runnable 接口、实现 Callable 接口。还有匿名内部类方式,Lambda 表达式方式简化开发。
1、Thread
Thread 创建线程方式:创建线程类
-
start() 方法底层其实是给 CPU 注册当前线程,并且触发 run() 方法执行
-
线程的启动必须调用 start() 方法,如果线程直接调用 run() 方法,相当于变成了普通类的执行,此时主线程将只有执行该线程
-
建议线程先创建子线程,主线程的任务放在之后,否则主线程(main)永远是先执行完
Thread 构造器:
-
public Thread()
-
public Thread(String name)
public class p1 {public static void main(String[] args) {// 创建线程类方式Thread t1 = new MyThread();t1.start();}
}
class MyThread extends Thread{@Overridepublic void run() {for(int i = 0 ; i < 100 ; i++ ) {// 子线程输出System.out.println(Thread.currentThread().getName() + "->" + i);}}
}
继承 Thread 类的优缺点:
-
优点:编码简单
-
缺点:线程类已经继承了 Thread 类无法继承其他类了,功能不能通过继承拓展(单继承的局限性)
2、Runnable
Runnable 创建线程方式:创建线程类
Thread 的构造器:
-
public Thread(Runnable target)
-
public Thread(Runnable target, String name)
public class p2 {public static void main(String[] args) {Runnable target = new MyRunnable();Thread t1 = new Thread(target,"Runnable线程");t1.start();Thread t2 = new Thread(target);//Thread-0}
}public class MyRunnable implements Runnable{@Overridepublic void run() {for(int i = 0 ; i < 10 ; i++ ){System.out.println(Thread.currentThread().getName() + "->" + i);}}
}
Thread 类本身也是实现了 Runnable 接口,Thread 类中持有 Runnable 的属性,执行线程 run 方法底层是调用 Runnable#run:
public class Thread implements Runnable {private Runnable target;public void run() {if (target != null) {// 底层调用的是 Runnable 的 run 方法target.run();}}}
Runnable 方式的优缺点:
-
缺点:代码复杂一点。
-
优点:
-
线程任务类只是实现了 Runnable 接口,可以继续继承其他类,避免了单继承的局限性
-
同一个线程任务对象可以被包装成多个线程对象
-
适合多个多个线程去共享同一个资源
-
实现解耦操作,线程任务代码可以被多个线程共享,线程任务代码和线程独立
-
线程池可以放入实现 Runnable 或 Callable 线程任务对象
-
3、Callable
实现 Callable 接口:
定义一个线程任务类实现 Callable 接口,申明线程执行的结果类型
重写线程任务类的 call 方法,这个方法可以直接返回执行的结果
创建一个 Callable 的线程任务对象
把 Callable 的线程任务对象包装成一个未来任务对象
把未来任务对象包装成线程对象
调用线程的 start() 方法启动线程
public FutureTask(Callable<V> callable)
:未来任务对象,在线程执行完后得到线程的执行结果
-
FutureTask 就是 Runnable 对象,因为 Thread 类只能执行 Runnable 实例的任务对象,所以把 Callable 包装成未来任务对象
public V get()
:同步等待 task 执行完毕的结果,如果在线程中获取另一个线程执行结果,会阻塞等待,用于线程同步
-
get() 线程会阻塞等待任务执行完成
-
run() 执行完后会把结果设置到 FutureTask 的一个成员变量,get() 线程可以获取到该变量的值
public class p3 {public static void main(String[] args) {Callable<String> call = new MyCallable();FutureTask<String> task = new FutureTask<>(call);Thread t1 = new Thread(task);t1.start();try {// 获取call方法返回的结果(正常/异常结果)String s = task.get(); System.out.println(s);} catch (Exception e) {e.printStackTrace();}}public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {return Thread.currentThread().getName() + "->" + "MyCallable String";}
}
优缺点:
-
优点:同 Runnable,并且能得到线程执行的结果
-
缺点:编码复杂
4、匿名内部类方式、Lambda 表达式方式
public class p1 {public static void main(String[] args) {// 匿名内部类方式Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("t1 线程执行中");}});t1.start();// Lambda 表达式创建匿名内部类的线程对象Thread t2= new Thread(() -> {System.out.println("t2 线程执行中");});t2.start();}
}
使用匿名内部类的方式可以方便地定义并实例化线程对象,并实现线程的执行逻辑。它对于一些简单的线程任务可以简洁地表达,但对于复杂的线程逻辑,建议使用具名的内部类或者单独定义一个类来实现Runnable接口。
使用 lambda 表达式代替了匿名内部类,使得代码更加简洁。适用于只包含一个抽象方法的接口,例如 Runnable 接口和 Callable 接口。对于其他接口,如果包含多个抽象方法,就无法使用 lambda 表达式来创建匿名内部类。
借鉴:https://github.com/Seazean/JavaNote/blob/main/Prog.md