Java创建线程的方法,看这一篇文章就够了!

185 阅读5分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情

一、继承Thread类创建线程类

(1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。

(2)创建Thread子类的实例,即创建了线程对象。

(3)调用线程对象的start()方法来启动该线程。

public class FirstThreadTest extends Thread {
    int i = 0;

    //重写run方法,run方法的方法体就是现场执行体
    public void run() {
        for (; i < 100; i++) {
            System.out.println(getName() + "  " + i);

        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "  : " + i);
            if (i == 20) {
                new FirstThreadTest().start();
                new FirstThreadTest().start();
            }
        }
    }

}

上述代码中Thread.currentThread()方法返回当前正在执行的线程对象。GetName()方法返回调用该方法的线程的名字。

二、通过Runnable接口创建线程类

(1)定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。

(2)创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。

(3)调用线程对象的start()方法来启动该线程。

public class RunnableThreadTest implements Runnable {
    private int i;

    public void run() {
        for (i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
          
          RunnableThreadTest rtt = new RunnableThreadTest();
          new Thread(rtt, "新线程1").start();
          new Thread(rtt, "新线程2").start();
    }

}

三、通过Callable和Future创建线程

为什么会有Callable和Future

通过实现 Runnable 接口与继承 Thread 类的方式创建的线程是没有返回值的,然而在有些情况下,往往需要通过某个线程计算得到的结果供给另一个线程使用,这个时候采用Runnable 与 Thread 创建线程并通过自行编写代码实现结果返回的方式会不可避免的出现许多错误或性能上的问题

基于此问题,Java1.5开始就提供了CallableFuture,通过它们可以在任务执行完毕之后得到任务执行结果。

CallAble

Callable接口代表一段可以调用并返回结果的代码;

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;
}

Future

Future接口表示异步任务,是还没有完成的任务给出的未来结果。所以说Callable用于产生结果,Future用于获取结果。

Callable接口使用泛型去定义它的返回类型。Executors类提供了一些有用的方法在线程池中执行Callable内的任务。由于Callable任务是并行的(并行就是整体看上去是并行的,其实在某个时间点只有一个线程在执行),我们必须等待它返回的结果。

Executor就是RunnableCallable的调度容器,Future就是对于具体的Runnable或者Callable任务的执行结果进行

取消、 查询是否完成、获取结果、设置结果操作。get方法会阻塞,直到任务返回结果.

/**
* @see FutureTask
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this Future's <tt>get</tt> method
 */
public interface Future<V> {
 
    /**
     * Attempts to cancel execution of this task.  This attempt will
     * fail if the task has already completed, has already been cancelled,
     * or could not be cancelled for some other reason. If successful,
     * and this task has not started when <tt>cancel</tt> is called,
     * this task should never run.  If the task has already started,
     * then the <tt>mayInterruptIfRunning</tt> parameter determines
     * whether the thread executing this task should be interrupted in
     * an attempt to stop the task.     *
     */
    boolean cancel(boolean mayInterruptIfRunning);
 
    /**
     * Returns <tt>true</tt> if this task was cancelled before it completed
     * normally.
     */
    boolean isCancelled();
 
    /**
     * Returns <tt>true</tt> if this task completed.
     *
     */
    boolean isDone();
 
    /**
     * Waits if necessary for the computation to complete, and then
     * retrieves its result.
     *
     * @return the computed result
     */
    V get() throws InterruptedException, ExecutionException;
 
    /**
     * Waits if necessary for at most the given time for the computation
     * to complete, and then retrieves its result, if available.
     *
     * @param timeout the maximum time to wait
     * @param unit the time unit of the timeout argument
     * @return the computed result
     */
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

每个方法的作用依次为: 图片.png

Future提供了三种功能

1)判断任务是否完成;
2)能够中断任务;
3)能够获取任务执行结果。
因为Future只是一个接口,所以是无法直接用来创建对象使用的,因此就有了下面的FutureTask

FutureTask

FutureTask则是一个RunnableFuture,而RunnableFuture实现了Runnbale又实现了Futrue这两个接口

public class FutureTask<V> implements RunnableFuture<V>

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();
}

依赖关系如图:
图片.png 另外它还可以包装Runnable和Callable, 由构造函数注入依赖。

    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }
 
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

可以看到,Runnable注入会被Executors.callable()函数转换为Callable类型,即FutureTask最终都是执行Callable类型的任务。该适配函数的实现如下 :

   public static <T> Callable<T> callable(Runnable task, T result) {
       if (task == null)
           throw new NullPointerException();
       return new RunnableAdapter<T>(task, result);
   }

RunnableAdapter适配器

  /**
   * A callable that runs given task and returns given result
   */
  static final class RunnableAdapter<T> implements Callable<T> {
      final Runnable task;
      final T result;
      RunnableAdapter(Runnable task, T result) {
          this.task = task;
          this.result = result;
      }
      public T call() {
          task.run();
          return result;
      }
  }    

由于FutureTask实现了Runnable,因此它既可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行。并且还可以直接通过get()函数获取执行结果,该函数会阻塞,直到结果返回。因此FutureTask既是FutureRunnable,又是包装了Callable( 如果是Runnable最终也会被转换为Callable ), 它是这两者的合体。

使用方法

方法1 通过Thread包装来直接执行

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

class TestLock {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public String call() throws Exception {
                return "hello Callable and FutureTask!!";
            }
        });
        new Thread(futureTask,"A").start();
        String str = (String) futureTask.get();
        System.out.println(str);
    }
}

首先我们必须构造FutureTask,然后交给线程包裹执行,这是最基本的线程执行方法,其实后边所谓的线程池调用方法也是基于此的封装

方法2 通过线程池submit生成Future

/**
 * 提交Callable, 有返回值, future中能够获取返回值
 */
Future<Integer> result2 = mExecutor.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        return fibc(20);
    }
});

System.out
        .println("future result from callable : " + result2.get());

看下源码其实内部自动帮我们创建了FuterTask

public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

方法3 构造一个FutureTask 再传入线程池

/**
 * FutureTask则是一个RunnableFuture<V>,即实现了Runnbale又实现了Futrue<V>这两个接口,
 * 另外它还可以包装Runnable(实际上会转换为Callable)和Callable
 * <V>,所以一般来讲是一个符合体了,它可以通过Thread包装来直接执行,也可以提交给ExecuteService来执行
 * ,并且还可以通过v get()返回执行结果,在线程体没有执行完成的时候,主线程一直阻塞等待,执行完则直接返回结果。
 */
FutureTask<Integer> futureTask = new FutureTask<Integer>(
        new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return fibc(20);
            }
        });
// 提交futureTask
mExecutor.submit(futureTask) ;
System.out.println("future result from futureTask : "
        + futureTask.get());

效果一样,最终都会调用execute(ftask);也就是进入线程池执行,线程池的内部机制是这么样的呢?可以关注我的文章线程池池源码分析

四、三种创建线程方法适用场景

  • 继承Thread

    因为只能单继承,不方便扩展,对于功能单一的情况可以使用

  • 实现Runable接口

    接口可以多实现,适用于一个类具备多种复杂功能的需求实现

  • FutureTask

    如果关心返回值是最佳选择,如果需要关心多个线程都回调才做处理也是最佳选择