Java中实现多线程的几种方式总结
作者:夜夜流光相皎洁_小宁
文章介绍了三种创建线程的方式:继承Thread类、实现Runnable接口和实现Callable接口,并比较了它们之间的区别和优缺点
Java实现多线程的几种方式
1)继承Thread类
实现的步骤:
- 1)创建Thread类的子类
- 2)重写run方法
- 3)创建线程对象
- 4)启动线程
代码示例:
package com.bobo.thread; public class ThreadDemo02 { /** * 线程的第一种实现方式 * 通过创建Thread类的子类来实现 * @param args */ public static void main(String[] args) { System.out.println("main方法执行了1..."); // Java中的线程 本质上就是一个Thread对象 Thread t1 = new ThreadTest01(); // 启动一个新的线程 t1.start(); for(int i = 0 ; i< 100 ; i++){ System.out.println("main方法的循环..."+i); } System.out.println("main方法执行结束了3..."); } }
/** * 第一个自定义的线程类 * 继承Thread父类 * 重写run方法 */ class ThreadTest01 extends Thread{ @Override public void run() { System.out.println("我们的第一个线程执行了2...."); for(int i = 0 ; i < 10 ; i ++){ System.out.println("子线程:"+i); } } }
注意点:
1)启动线程是使用start方法而不是run方法
2)线程不能启动多次,如果要创建多个线程,那么就需要创建多个Thread对象
2)实现Runnable接口
在第一种实现方式中,我们是将线程的创建和线程执行的业务都封装在了Thread对象中,我们可以通过Runable接口来实现线程程序代码和数据有效的分离。
实现的步骤:
- 1)创建Runable的实现类
- 2)重写run方法
- 3)创建Runable实例对象(通过实现类来实现)
- 4)创建Thread对象,并把第三步创建的Runable实例作为Thread构造方法的参数
- 5)启动线程
代码示例:
package com.bobo.runable; public class RunableDemo01 { /** * 线程的第二种方式 * 本质是创建Thread对象的时候传递了一个Runable接口实现 * @param args */ public static void main(String[] args) { System.out.println("main执行了..."); // 创建一个新的线程 Thread对象 Runnable r1 = new RunableTest(); Thread t1 = new Thread(r1); // 启动线程 t1.start(); System.out.println("main结束了..."); } } /** * 线程的第二种创建方式 * 创建一个Runable接口的实现类 */ class RunableTest implements Runnable{ @Override public void run() { System.out.println("子线程执行了..."); } }
实现Runable接口的好处:
1)可以避免Java单继承带来的局限性
2)适合多个相同的程序代码处理同一个资源的情况,把线程同程序的代码和数据有效的分离,较好的体现了面向对象的设计思想
3)实现Callable接口(JDK1.5>=)
前面我们介绍的两种创建线程的方式都是重写run方法,而且run方法是没有返回结果的,也就是main方法是不知道开启的线程什么时候开始执行,什么时候结束执行,也获取不到对应的返回结果。而且run方法也不能把可能产生的异常抛出。
在JDK1.5之后推出了通过实现Callable接口的方式来创建新的线程,这种方式可以获取对应的返回结果。
代码示例:
package com.bobo.callable; import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class CallableDemo01 { /** * 创建线程的第三种实现方式: * Callable方式 */ public static void main(String[] args) throws Exception { // 创建一个Callable实例 Callable<Integer> callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable); // 获取一个线程 肯定是要先创建一个Thread对象 futureTask本质上是Runable接口的实现 Thread t1 = new Thread(futureTask); System.out.println("main方法start...."); t1.start(); // 本质还是执行的 Runable中的run方法,只是 run方法调用了call方法罢了 // 获取返回的结果 System.out.println(futureTask.get()); // 获取开启的线程执行完成后返回的结果 System.out.println("main方法end ...."); } } /** * 创建Callable的实现类 * 我们需要指定Callable的泛型,这个泛型是返回结果的类型 */ class MyCallable implements Callable<Integer>{ /** * 线程自动后会执行的方法 * @return * @throws Exception */ @Override public Integer call() throws Exception { int sum = 0; for(int i = 1 ; i <= 100 ; i ++){ sum += i; } return sum; } }
实现Runnable接口和实现Callable接口的区别:
- 1)Runnable是自从java1.1就有了,而Callable是1.5之后才加上去的
- 2)Callable规定的方法是call(),Runnable规定的方法是run()
- 3)Callable的任务执行后可返回值,而Runnable的任务是不能返回值(是void)
- 4)call方法可以抛出异常,run方法不可以
- 5)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
4)线程池方式创建
加入线程池运行,Runnable使用ExecutorService的execute方法,Callable使用submit方法。
其实Callable接口底层的实现就是对Runable接口实现的封装,线程启动执行的也是Runable接口实现中的run方法,只是在run方法中有调用call方法罢了
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。