创建线程的三种方式(包括例子和源码解释)

xiaoxiao2021-02-28  51

一般来说创建线程的方法有三种,分别是:

1.继承Thread类来创建一个线程

第一步:定义一个类继承Thread类,并实现run()方法,run()方法中的内容即为线程需要完成的功能

class MyThread extends Thread{ String name; public MyThread(String name) { this.name = name; System.out.println("继承Thread类"); } @Override public void run() { for(int i = 1; i < 50; i++) { System.out.println(this.name + ":" + i); } } }

第二步:构建子类对象,并调用对象的start()方法启动线程

public class createThread { public static void main(String[] args) { //创建子类的对象 MyThread thread1 = new MyThread("one"); MyThread thread2 = new MyThread("two");                 //启动线程 thread1.start(); thread2.start(); } }

2.实现Runnable接口来创建一个线程

第一步:定义一个类实现Runnable接口,并实现接口中的run()方法

class MyThread2 implements Runnable{ @Override public void run() { // System.out.println("this"+this); // System.out.println("Thread" + Thread.currentThread()); for(int i = 0; i < 50; i++) { System.out.println(Thread.currentThread().getName()+":"+i); } } }

第二步:创建该类对象,再创建一个Thread类对象,并把实现Runnable接口的类的对象作为参数传给Thread类对象,并调用start()方法

public class createThread { public static void main(String[] args) { //创建Runnable类对象 MyThread2 t1 = new MyThread2(); MyThread2 t2 = new MyThread2(); //把对象作为实参传递给Thread类 Thread thread3 = new Thread(t1,"three"); Thread thread4 = new Thread(t2,"four"); thread3.start(); thread4.start(); }

注意:创建的Runnable实现类只是一个target,不是线程对象,它用来指明线程运行时需要做的任务,Thread类的对象才是线程对象。

实现Runnable接口来创建一个类的好处:

一个Runnable实现类可以传给多个线程对象,所以适合多个相同程序代码的线程去处理同一个资源的情况,把线程和代码、数据有效的分离。Thread类创建线程是采用继承的方式,而Java中只能单继承。如果某个类的子类需要创建线程,那么就只能采用实现Runnable接口或者实现Callable接口的方式。

3.实现Callable接口来创建一个线程 

先定义一个Callable的实现类,并重写call()方法。call()有返回值。

class MyThread3 implements Callable<Integer>{ @Override public Integer call() throws Exception { int a = new Random().nextInt(100); System.out.println("线程执行结果 "+a); return a; } }

Callable类型的任务可以有两种执行方式:

第一种是借助FutureTask执行

创建Callable实现类的对象,并作为参数传给FutureTask对象,FutureTask作为参数传给Thread类对象,并执行start()方法

Callable<Integer> t4 = new MyThread3(); FutureTask<Integer> futuretask = new FutureTask<Integer>(t4); new Thread(futuretask).start();

futuretask.get()可以获取Callable实现类的返回值

第二种是借助线程池来执行

先创建一个线程池,然后调用线程池的submit方法,并将Callable实现类作为参数传入。线程池的submit()方法返回值为Future对象,所以用一个Future对象来接收。

ExecutorService exe = Executors.newCachedThreadPool(); Future<Integer> future = exe.submit(new MyThread3());

future.get()可以获取Callable实现类的返回值

特别的是,当执行多个Callable任务,有多个返回值时,我们可以定义一个Future类的集合来接收

//Callable实现类 class ManyCallableTasks implements Callable<String>{ private int id; //重写构造方法,为每个线程加上id public ManyCallableTasks(int id) { this.id = id; } //重写call()方法,返回值为字符串 @Override public String call() throws Exception { for(int i = 0; i < 5; i++) { System.out.println("Thread"+id); Thread.sleep(1000); } return "Result of callable" + id; } } public class ManyCallableThreads { public static void main(String[] args) { //创建线程池 ExecutorService exe = Executors.newCachedThreadPool(); //创建Future的集合,用来存放所有线程 ArrayList<Future<String>> results = new ArrayList<Future<String>>(); //线程作为线程池的参数,传入后线程池执行submit()方法,返回值装入results for(int i = 0; i < 5; i++) { results.add(exe.submit(new ManyCallableTasks(i))); } for(Future<String> fs : results) { if(!fs.isDone()) { try { System.out.println(fs.get()); }catch (Exception e) { e.printStackTrace(); } }else { System.out.println("任务未完成"); } } exe.shutdown(); } }

代码来自:https://blog.csdn.net/sunp823/article/details/51569314

Callable的好处:

有返回值call()可以抛出异常运行Callable任务可以得到一个Future对象,表示异步计算的结果。它提供了检测计算是否完成的方法(isDone())以等待计算的完成,并检索计算的结果。

三种方式比较

这三种方式在底层实现上有什么区别呢,我们来看看它们的源码。(本文在引用源码过程中删除了部分注释,但是删除的部分所说的内容我都做了中文的说明,如果有遗漏的地方还请各位指出,想看注释的可以自行查看源码)

Runnable接口:

@FunctionalInterface public interface Runnable { /** * When an object implementing interface <code>Runnable</code> is used * to create a thread, starting the thread causes the object's * <code>run</code> method to be called in that separately executing * thread. * <p> * The general contract of the method <code>run</code> is that it may * take any action whatsoever. * * @see java.lang.Thread#run() */ public abstract void run(); }

我们可以发现Runnable接口中只有一个run()方法,注释中说:当一个类实现Runnable接口来创建一个线程时,开启线程会导致对象的run方法在单独执行的线程中被调用,run方法主要功能是指明线程做什么工作,参考Thread类中的run 方法。那么我们来看Thread类:

public class Thread implements Runnable

我们可以发现Thread类其实也实现了Runnable接口,我们看看它重写的run()方法:

/** * If this thread was constructed using a separate * <code>Runnable</code> run object, then that * <code>Runnable</code> object's <code>run</code> method is called; * otherwise, this method does nothing and returns. * <p> * Subclasses of <code>Thread</code> should override this method. * * @see #start() * @see #stop() * @see #Thread(ThreadGroup, Runnable, String) */ @Override public void run() { if (target != null) { target.run(); } }

这个target就是Runnable实现类的对象,所以这个方法的意思就是如果线程是通过实现Runnable接口来创建的话,那么就会调用Runnable对象的run()方法。其实这个也印证了为什么我们在使用Runnable接口创建线程时需要先实例化Runnable接口的实现类,然后再把这个对象作为参数传入Thread类中,其实是把任务的运行方式传入线程中

Thread类:

接下来我们看一下Thread类:

Thread类有很多构造方法,但是所有的构造方法本质上都是调用的同一个init方法,只是传入的参数不同。

/** * Initializes a Thread. * * @param g the Thread group    group:线程组 * @param target the object whose run() method gets called  target:表示谁的run方法被调用 * @param name the name of the new Thread    name:新建线程的名字 * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. stackSize:指定的新建线程的大小,如果是0表示这个参数被忽略了 * @param acc the AccessControlContext to inherit, or * AccessController.getContext() if null    acc:继承的AccessControlContext,如果为空表示这个值是get方法获取的值 */ private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc)     

Callable接口

package java.util.concurrent; @FunctionalInterface public interface Callable<V> { /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }

这个接口和Runnable接口很像,里面只有一个call方法,返回类型是泛型,返回的是计算结果,如果不能计算结果那就抛出异常。适合会返回结果而且可能抛出异常的一种任务,它的实现类只需要定义一个没有参数的方法call(),Callable接口很像Runnable接口,他们都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常。Executors 类包含一些从其他普通形式转换成 Callable 类的实用方法。

我们在使用Callable接口创建线程时会用到Future接口

Future接口
package java.util.concurrent; public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }

源码注释中说:Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get 方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future 但又不提供可用的结果,则可以声明 Future<?> 形式类型、并返回 null 作为底层任务的结果。注释中还提供了示例,这里不再展示。

说一下Future中的几个方法:

boolean cancel(boolean mayInterruptIfRunning);

取消对此任务的执行,但是如果遇见如下情况:1、该任务已被取消;2、该任务已经完成;3、因为某些原因无法取消,那么取消会失败。调用此方法时如果该任务还未启动,那么这个任务将永不运行;如果该任务已经启动,那么该任务是停止还是继续执行将取决于mayInterruptIfRunning,如果值为true,该任务的线程需中断,否则允许该任务执行完成。

boolean isCancelled();

判断该任务在正常完成前是否被取消。

boolean isDone();

判断该任务是否完成,任务正常完成、正常终止、异常或取消都是完成,将返回true。

V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;

这两个方法都是等待计算完成并获取它的结果,但是第二个方法传入了一个时间,表示最多等待timeout时间就获取它的结果( 如果结果可用)。(TimeUnit是timeout的单位,为枚举类,有固定取值)

在使用Future创建线程时,使用了Executors中的submit方法,

我们再来看看FutureTask类

FutureTask类

FutureTask类表示可取消的异步计算。其实在Future接口中,有一段注释讲了FutureTask类:FutureTask 类是 Future 的一个实现, Future 可实现 Runnable,所以可通过 Executor 来执行。

此类中有开始计算、取消计算、查询计算是否完成和获取计算结果的方法,此类提供了对 Future 的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算(除非调用runAndReset  方法)。

public class FutureTask<V> implements RunnableFuture<V>

FutureTask实现的是RunnableFuture接口,

public interface RunnableFuture<V> extends Runnable, Future<V> { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }RunnableFuture接口其实继承了Runnable接口和Future接口,有这两个接口的特性,而且有run()方法。所以可以把FutureTask类的对象作为参数传给Thread类,它作为参数时和Runnable实现类对象等效。
转载请注明原文地址: https://www.6miu.com/read-2622428.html

最新回复(0)