Java多线程(一) 使用线程

148 阅读3分钟

Thread类和Runnable接口

JDK提供了Thread类和Runnable接口供我们实现线程。

  • 继承Thread类,重写run()方法
  • 实现Runnable接口的run()方法

继承Thread类

继承Thread类之后,调用start()方法才算启动了线程。调用start()方法之后,虚拟机会创建一个线程,然后等到这个线程第一次得到时间片时再调用实现的run()方法。

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

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

实现Runnable接口

Runnbale是一个函数式接口

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

Java中真正能创建新线程的只有Thread类对象,通过实现Runnable的方式,最终也时还是通过Thread类对象创建线程并启动。

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

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
        
        // Java 8 函数式编程,可以省略MyThread类
        new Thread(() -> {
            System.out.println("Java 8 匿名内部类");
        }).start();
    }
}

Thread类和Runnable接口的比较

  1. 由于Java“单继承,多实现”的特性,Runnable接口使用起来比Thread更灵活。
  2. Runnable接口出现更符合面向对象,将线程单独进行对象的封装。
  3. Runnable接口出现,降低了线程对象和线程任务的耦合性。
  4. 如果使用线程时不需要使用Thread类的诸多方法,使用Runnable接口更为轻量。

Callable、Future和FutureTask

使用Runnable和Thread创建新的线程有一个弊端,就是run()方法没有返回值。JDK提供了Callable和Future类解决线程获取返回值的问题,也就是“异步模型”。

Callable接口

Callable也是只有一个抽象方法的函数式接口,Callable提供的call()方法是有返回值的,而且支持泛型。

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable一般配合线程池工具ExecutorService来使用,ExecutorService可以使用submit()方法来让一个Callable接口执行,它会返回一个Future,后续的程序可以通过这个Future的get方法得到线程的返回结果。

public class Demo {
    // 自定义Callable
    static class Task implements Callable<Integer> {
        @Override
        public Integer call() throws Exception {
            // 模拟计算需要一秒
            Thread.sleep(1000);
            return 2;
        }
    }

    public static void main(String args[]) throws ExecutionException, InterruptedException, TimeoutException {
        // 线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executor.submit(task);
        // 调用get方法会阻塞当前线程,直到得到结果。
        System.out.println(result.get());
        // 使用带有时间的get方法可以设置超时时间
        // System.out.println(result.get(500, TimeUnit.MILLISECONDS));
    }
}

Future接口

Future接口只有几个比较简单的方法。

public abstract interface Future<V> {
    // 试图取消线程执行
    public abstract boolean cancel(boolean paramBoolean);
    // 判断线程在正常结束之前是否有被取消
    public abstract boolean isCancelled();
    // 判断线程是否结束
    public abstract boolean isDone();
    public abstract V get() throws InterruptedException, ExecutionException;
    public abstract V get(long paramLong, TimeUnit paramTimeUnit)
            throws InterruptedException, ExecutionException, TimeoutException;
}

如果让任务有能够取消的功能,可以使用Callable代替Runnable,如果为了线程可取消性而使用Future但又不对外提供结果,则可以声明为Future<?>形式类型,并返回null作为返回结果。

FutureTask类

FutureTask是一个JDK提供的实现了Future上诉方法的一个类,FutureTask实现了RunnableFuture接口,而RunnableFuture接口同时继承了Runnable接口和Future接口,并且有一个抽象方法run()

public class Demo {
    static class Task implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            // 模拟计算需要一秒
            Thread.sleep(1000);
            return 2;
        }
    }
    public static void main(String args[]) throws ExecutionException, InterruptedException {
        // 线程池
        ExecutorService executor = Executors.newCachedThreadPool();
        FutureTask<Integer> futureTask = new FutureTask<>(new Task());
        executor.submit(futureTask);
        System.out.println(futureTask.get());
    }
}

该方式是使用submit(Runnable task)提交的线程任务,而在Callable中提交任务是使用submit(Callable<T> task)。并且该方式获取返回值时,可以直接从FutureTask对象中获取。