创建线程的三种方式

线程的创建方式

  线程的创建方式有四种,分别是继承Thread类、实现Runnable接口、实现callable接口、线程池,在这里我们只探讨前面三种方式。

1. 继承Thread类

  首先是使用继承Thread类创建线程,我们需要继承Thread类还要重写run方法,然后在main方法中创建线程并调用start()方法来启动线程。

public class Demo {
    public static class MyThread extends Thread {
        @Override
        public void run() {//重写run方法
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        Thread myThread = new MyThread();
        myThread.start();//调用start方法
    }
}

2. 实现callable接口

  使用实现Runnable接口来创建线程,首先我们创建一个类来实现Runnable接口的run方法,然后也需要在main方法中调用start方法来启动线程。

public class Demo {
    public static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println("MyThread");
        }
    }

    public static void main(String[] args) {
        new MyThread().start();

        //因为Runnable接口是一个函数式接口,所以我们可以使用这种方式来创建线程。
        new Thread(() -> {
            System.out.println("线程运行");
        }).start();
    }
}

3. 实现callable接口

  通常我们都是我们使用Runnable和Thread来创建一个新的线程。但是它们有一个弊端,就是run方法是没有返回值的。
  但是有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成后有一个返回值。
  这时候我们就可以使用实现callable接口的方式来创建线程。


  我们可以使用ExecutorService可以使用submit方法来让一个Callable接口执行。
  它会返回一个Future,我们后续的程序可以通过这个Future的get方法得到结果。

class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 使得线程睡眠一秒
        Thread.sleep(1000);
        //返回一个2
        return 2;
    }
    public static void main(String args[]){
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        System.out.println(result.get()); 
    }
}

  我们还可以使用一个类FutureTask配合实现callable接口的方式来创建线程。

class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 使得线程睡眠一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]){
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

两种方式在使用上有一点区别。首先,调用submit方法是没有返回值的,我们不用接收返回值去调用get方法。
这里实际上是调用的submit(Runnable task)方法,而上面的Demo,调用的是submit(Callable task)方法。
然后,这里是使用FutureTask直接取get取值,而上面的Demo是通过submit方法返回的Future去取值。
在很多高并发的环境下,有可能Callable和FutureTask会创建多次。FutureTask能够在高并发环境下确保任务只执行一次。

三种方式之间的比较

  • 实现接口方式的好处
    1. 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
    2. Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
    3. Runnable接口出现,降低了线程对象和线程任务的耦合性。
    4. 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。
  • Runnable和Callable接口的比较
    1. Callable接口可以获取线程运行的信息以及中止线程,Runnable只能提供基本的线程运行工作,Callable的功能更丰富一些
    2. Callable的call()方法允许抛出异常,Runnable的run()方法则不允许
    3. 当使用FutureTask.get()方法时,主线程会阻塞,因为该方法返回的是该线程的运行结果,只有等到该线程结束才可以返回结果,而该方法写在主线程中,主线程会因为该方法等待线程结束而阻塞,直到返回出了运行结果,主程序才会继续运行,所以FutureTask.get()要在不需要并发的时候去调用
上一篇:JDK1.8线程池 ThreadPoolExecutor详解


下一篇:Java中线程池的使用