实现java多线程的方式(入门向)

88 阅读3分钟

这是我参与更文挑战的第5天,活动详情查看: 更文挑战

之前的文章结合软硬件讲解了计算机中的线程和进程,本篇给大家介绍java中实现多线程的几种方式。

一. 无返回值形式

JDK提供了Thread类和Runnable接口来让我们实现自己的“线程”类。

1. 继承Thread类

public class ThreadDemo {
    public static class TestThread extends Thread {
        @Override
        public void run() {
            System.out.println("TestThread");
        }
    }

    public static void main(String[] args) {
        Thread testThread = new TestThread();
        testThread.start();
    }
}

在调用start方法后,线程启动!

2. 实现Runnable接口

runnable实现如下,因为runnable是函数式接口,所以可以通过函数式编程来实现启动。

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

    public static void main(String[] args) {
        // 传统方式启动。
        new Thread(new ThreadDemo()).start();

        // 函数式编程启动(java1.8+)
        new Thread(() -> {
            System.out.println("Thread 匿名内部类");
        }).start();
    }
}

3. Thread类的几个常用方法

这里介绍一下Thread类的几个常用的方法:

  • currentThread():静态方法,返回对当前正在执行的线程对象的引用;
  • start():开始执行线程的方法,java虚拟机会调用线程内的run()方法;
  • yield():yield在英语里有放弃的意思,同样,这里的yield()指的是当前线程愿意让出对当前处理器的占用。这里需要注意的是,就算当前线程调用了yield()方法,程序在调度的时候,也还有可能继续运行这个线程的;
  • sleep():静态方法,使当前线程睡眠一段时间;
  • join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的wait方法实现的;

4. Thread类与Runnable接口的比较:

实现一个自定义的线程类,可以有继承Thread类或者实现Runnable接口这两种方式,它们之间有什么优劣呢?

由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。 Runnable接口出现更符合面向对象,将线程单独进行对象的封装。 Runnable接口出现,降低了线程对象和线程任务的耦合性。 如果使用线程时不需要使用Thread类的诸多方法,显然使用Runnable接口更为轻量。 所以,我们通常优先使用“实现Runnable接口”这种方式来自定义线程类。

二. 有返回值形式

在使用Runnable和Thread来创建线程时,run方法是不能提供返回值的。如果我们需要返回值的话,可以使用JDK提供的Callable和Future来实现。

1.Callable接口

Callable与Runnable类似,同样是只有一个抽象方法的函数式接口。不同的是,Callable提供的方法是有返回值的,而且支持泛型。Callable一般是配合线程池工具ExecutorService来使用的

// 自定义Callable
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 注意调用get方法会阻塞当前线程,直到得到结果。
        // 所以实际编码中建议使用可以设置超时时间的重载get方法。
        System.out.println(result.get()); 
    }
}

2. Future接口

FutureTask是实现的RunnableFuture接口的,而RunnableFuture接口同时继承了Runnable接口和Future接口。JDK提供了FutureTask类来供我们使用。

// 自定义Callable,与上面一样
class Task implements Callable<Integer>{
    @Override
    public Integer call() throws Exception {
        // 模拟计算需要一秒
        Thread.sleep(1000);
        return 2;
    }
    public static void main(String args[]) throws Exception {
        // 使用
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

在调用get方法时,可以进行取值。 FutureTask能够在高并发环境下确保任务只执行一次。