java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java创建线程

如何在Java 中创建线程

作者:Boilermaker1992

Java中创建线程的唯一方式是调用Thread类的start()方法,通过继承Thread类或实现Runnable接口可以为线程提供可执行任务,实现Callable接口并将其包装在FutureTask中,可以获取异步任务的执行结果,本文介绍在Java中创建线程的方法,感兴趣的朋友跟随小编一起看看吧

事实上,Java 有且仅有一种方式可以真正创建出线程,那就是通过调用 Thread 类中的 start() 方法。因为只有这个方法会去真正调用 JVM 本地方法,进而进行操作系统创建线程的系统调用,并让 Java 线程与操作系统线程产生映射关系,最后将实现了 Runnable 接口的任务传递给线程。只要线程获得了 CPU 时间片,就进入 run() 方法执行具体的代码逻辑。

​ 所以,仅仅有 start 方法我们就可以创建一个线程了,如下面的代码所示。只不过这个线程什么都不会做,它是一个空线程,因为我们没有以任何方式提供给其 “可执行任务”。

public class Demo1 {
    public static void main(String[] args) {
        Thread thread = new Thread();
        System.out.println(thread.getName()); // Thread-0
        System.out.println(thread.getState()); // NEW
        thread.start();
        System.out.println(thread.getState()); // RUNNABLE
    }
}

​ 在下面介绍的所谓创建线程的方式,其实就是在介绍为线程提供 “可执行任务” 的方式,这些方式最终都会调用到 Thread 类中的 start() 方法。

1. 继承 Thread 类

​ 这种方式是通过重写 Thread 类中的 run() 方法来为线程提供可执行任务。

class MyThread extends Thread {
    public void run() {
        System.out.println(currentThread().getName());
    }
}
public class Demo2 {
    public static void main(String[] args) {
        Thread myThread1 = new MyThread();
        Thread myThread2 = new MyThread();
        myThread1.start(); // Thread-0
        myThread2.start(); // Thread-1
    }
}

​ 这种方式一般是不推荐使用的,因为它在逻辑上不能将 ”线程“ 的概念和 ”线程任务“ 的概念很好地分离开。假设现在我有一个数字,我想要使用两个线程同时对这个数字进行增加,直至这个数字变为 100。如果要通过继承 Thread 类实现,这个数字就只能作为 Thread 子类的一个静态属性存在,只有这样这个变量才能在类实例之间共享。但是,这样做就会导致代码可读性和可维护性都很差,因为自增数字是一个任务,现在它被耦合在线程中。因此我们大部分情况还是要使用接下来介绍的方式。

​ 不过,通过继承 Thread 类实现共享数字自增的代码我也放在下面,感兴趣的可以看一下,这里使用 CAS 保证线程安全,不懂的话可以先略过这块。

class MyThread extends Thread {
    private final static AtomicInteger goodsAmount = new AtomicInteger(0);
    public void run() {
        while (true) {
            // 使用 current 作为线程内部的临时变量,相当于快照,无线程安全问题
            int current = goodsAmount.get();
            if (current >= 200) {
                break;
            }
            // 当且仅当 goodsAmount 的值没有被其他线程修改过时自增,
            // 如果被修改过那它的值肯定不是刚刚记录过的 current 了。
            if (goodsAmount.compareAndSet(current, current + 1)) {
                // 每个线程只会在成功 CAS 的时候打印一次,并且打印的是增加后的值。
                // 所以这里的日志应该严格打印 200 次
                System.out.println(currentThread().getName() + ":" + (current + 1));
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        Thread myThread1 = new MyThread();
        Thread myThread2 = new MyThread();
        Thread myThread3 = new MyThread();
        myThread1.start();
        myThread2.start();
        myThread3.start();
        myThread1.join();
        myThread2.join();
        myThread3.join();
    }
}

2. 实现 Runnable 接口

​ 我们先来观察 Thread 类中 run() 方法的源码。如果我们不选择重写它,那么可以看出这个方法主要进行了一个逻辑判断,如果 task 为空就什么都不做,如果 task 不为空就调用 runWith 真正执行该任务。那么 task 什么时候不为空呢,就是当我们在 Thread 的构造器中传入任务的时候,这才引出了下面的几种方式。

    @Override
    public void run() {
        Runnable task = holder.task;
        if (task != null) {
            Object bindings = scopedValueBindings();
            runWith(bindings, task);
        }
    }

​ 如下展示了三种通过实现 Runnable 接口来为线程传递可执行任务的方式:

class Task implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName());
    }
}
public class Demo2 {
    public static void main(String[] args) {
        new Thread(new Task()).start(); // Thread-2
        new Thread(new Runnable() { // 使用匿名内部类
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        }).start(); // Thread-3
        new Thread(() -> { // 使用 lambda 表达式
                System.out.println(Thread.currentThread().getName());
        }).start(); // Thread-4
    }
}

3. 获取异步执行结果

​ 上面的两种方式都不能获取异步执行结果,因为 run() 方法返回 void,这表示我们没有管控线程执行任务的能力:我们无法中途停止任务,也无法获取任务的执行状态,更无法获取任务执行结果。

​ 在 Java 中,获取异步任务执行信息的功能由 Future 接口进行规范,而一个可执行任务由 Runnable 接口进行规范。这意味着,只有同时实现这两个接口的类才可以作为一个 “可获取异步执行结果的可执行任务” 传入 Thread 的构造器。

​ 这个类就是 FutureTask,它真正实现了上述功能,这依赖于它的两个关键属性 Callable<V> callableObject outcome。前者是我们传入的带返回值的异步任务;后者是 FutureTask 保存的任务执行后的返回值。FutureTask 提供了 get() 方法供我们取出 outcome,同时还提供了取消任务、判断任务是否执行完、判断任务是否被取消等获取异步任务执行信息的功能。

所以,现在的逻辑变成了:我们需要先让自己的任务实现 Callable 接口,这表示我们的任务是一个经 Callable 规范的、有返回值的任务。接下来,我们还要把任务包装在 FutureTask 中,传入 Thread 的构造器。Thread 不管你提供的到底是什么任务,只要实现了 Runnable 即可,因为这规范了你传入任务一定会有一个 run() 方法。Thread 先调用自己的 run() 方法,在内部调用到 FutureTask 的 run() 方法,FutureTask 的 run() 方法又会调用到我们最初实现 Callable 接口时实现的 call() 方法,这里才是我们真正定义的代码逻辑。执行完 call() 后,FutureTask 将返回值保存在 outcome 属性中,供我们取出。

class CallableTask implements Callable<String> {
    @Override
    public String call() {
        System.out.println(Thread.currentThread().getName());
        return "任务执行成功!";
    }
}
public class Demo2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<String> futureTask = new FutureTask<>(new CallableTask());
        new Thread(futureTask).start();
        System.out.println(futureTask.get()); // 任务执行成功!
    }
}

到此这篇关于在 Java 中创建线程的文章就介绍到这了,更多相关java创建线程内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文