Java并发编程札记-(一)基础-02创建线程

本文介绍Java中如何创建线程。

目录:

  1. Runnable-定义无返回值的任务
  2. Thread-线程构造器
  3. Callable-定义可以返回值的任务
  4. 总结

在Java中,创建新执行线程有四种方法。

  1. 继承Thread。
  2. 实现Runnable。
  3. 实现Callable。

我认为Runnable和Callable的作用只是定义任务,创建线程还是需要Thread构造器。

Runnable

线程可以驱动任务,所以我们需要定义任务,Runnable可以来实现这个需求。

Runnable是一个接口,其中只声明了一个run方法。

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

要想定义任务,只需要实现Runnable接口,并实现run方法。

下面通过实例看下如何通过实现Runnable创建和启动线程。

例1:

//定义任务
class MyThread2 implements Runnable {
    // 重写run方法
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + "在运行");
        }
    }
}

public class MyRunnableTest {
    public static void main(String[] args) {
        // 创建任务
        MyThread2 mt= new MyThread2();
        //将任务附着在线程上
        Thread t1 = new Thread(mt);
        Thread t2 = new Thread(mt);
        // 启动线程
        t1.start();
        t2.start();
    }
}

通过实现Runnable接口来定义的任务并无线程能力。要实现线程能力,必须显示的将一个任务附着到线程上。

运行结果为:

Thread-1在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-0在运行
Thread-1在运行

Thread

Thread是一个类,它实现了Runnable,并额外提供了一些方法供用户使用。它的定义如下:

public class Thread implements Runnable {...}

在我看来,Thread就是一个线程构造器。
Java并发编程札记-(一)基础-02创建线程_第1张图片

下面通过实例看下如何通过继承Thread创建和启动线程。

例2:

//定义线程类
class MyThread extends Thread {
    //重写run方法
    public void run() {
        for (int i = 0; i < 3; i++) {
                System.out.println(this.getName() + "在运行");
        }
    }
};

public class MyThreadTest {
    public static void main(String[] args) {
        // 创建线程
        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();
        //启动线程
        t1.start();
        t2.start();

        for (int i = 0; i < 3; i++)
        {
            System.out.println(Thread.currentThread().getName() + "在运行");
        }
    }
}

运行结果为:

Thread-1在运行
main在运行
Thread-0在运行
main在运行
Thread-1在运行
main在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行

Thread-0和Thread-1是我们创建的线程,因为我们没有给它们命名,所以JVM为它们分配了两个名字。名字为main的线程是main方法所在的线程,也是主线程。主线程的名字总是mian,非主线程的名字不确定。从执行结果中可以看到,几个线程不是顺序执行的,JVM和操作系统一起决定了线程的执行顺序。所以,你运行后可能看到的不是这样的打印顺序。

Callable

Callable接口类似于Runnable,两者作用都是定义任务。不同的是,被线程执行后,Callable可以返回结果或抛出异常。而Runnable不可以。Callable的返回值可以通过Future来获取。Callable的定义如下:

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

下面通过实例看下如何通过实现Callable创建和启动线程并获取返回值。

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

//定义任务
class MyCallable implements Callable {

    @Override
    public String call() throws Exception {
        return "This is returned string.";
    }
}

public class MyCallabeTest {

    public static void main(String[] args){
        //创建有返回值的任务
        Callable callable = new MyCallable();
        FutureTask task = new FutureTask<>(callable);
        // 创建并启动线程
        new Thread(task).start();
        String result = null;
        try {
            // 调用get()阻塞主线程,并获取返回值
            result = task.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("return : " + result);
    }
}

运行main方法后打印结果为return : This is returned string.

FutureTask实现了Runnable和Callable接口,所以它既可以作为任务附加到线程上(new Thread(task).start();),又可以作为Future获取Callable的返回值(result = task.get();)。

下面介绍另一种使用Callable和Future的方法。

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


public class MyCallabeTest2 {

    public static void main(String[] args){
        //创建有返回值的任务。Callable使用第一个例子中的MyCallable
        Callable callable = new MyCallable();
        //创建线程执行器
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //通过线程执行器执行callable,并通过future获取返回结果
        Future future = threadPool.submit(callable);
        String result = null;
        try {
            // 调用get()阻塞主线程,并获取返回值
            result = future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally{
            threadPool.shutdown();
        }
        System.out.println("return : " + result);
    }
}

运行main方法后打印结果为return : This is returned string.

Java SE5的Java.util.concurrent包中的Executor(执行器)可以为我们管理线程,从而简化了并发编程。ExecutorService继承自Executor。下面会详细讲执行器。

下面介绍一种使用Callable和Future的获取多个返回值的方法。

import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;


//定义任务
class MyCallable3 implements Callable {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt(100);
    }
}

public class MyCallabeTest3 {

    public static void main(String[] args){
        //创建线程执行器
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //创建Future类型的集合
        ArrayList> results = new ArrayList>();
        //将Executor提交的任务的返回值添加到集合中
        for(int i = 0;i<5;i++)
            results.add(threadPool.submit(new MyCallable3()));
        //遍历集合取出数据
        try {
            // 调用get()阻塞主线程,并获取返回值
            for(Future result:results)
                System.out.println(result.get());
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        } finally{
            threadPool.shutdown();
        }
    }
}

运行main方法后打印结果为

4
4
94
63
11

注释已经写得很清楚了,这里不再多说。

线程池

在上面的例子中使用了线程执行器,也就是线程池。有人说线程池也是创建线程的一种方式,但看过线程池的源码就知道,线程池创建线程执行任务也是依赖于Thread的。

总结

Thread和Runnable该如何选择?

  • 因为Java不支持多继承,但支持多实现。所以从这个方面考虑Runnable比Thread有优势。
  • Thread中提供了一些基本方法。而Runnable中只有run方法。如果只想重写run()方法,而不重写其他Thread方法,那么应使用Runnable接口。除非打算修改或增强Thread类的基本行为,否则应该选择Runnable。

从上面的分析可以看到,一般情况下Runnable更有优势。

run方法与start方法的区别

启动线程的方法是start方法。线程t启动后,t从新建状态转为就绪状态, 但并没有运行。 t获取CPU权限后才会运行,运行时执行的方法就是run方法。此时有t和主线程两个线程在运行,如果t阻塞,可以直接继续执行主线程中的代码。

直接运行run方法也是合法的,但此时并没有新启动一个线程,run方法是在主线程中执行的。此时只有主线程在运行,必须等到run方法中的代码执行完后才可以继续执行主线程中的代码。

可以将例2中代码t1.start();改为t1.run()t2.start();改为t2.run(),你会发现打印结果会变为

Thread-0在运行
Thread-0在运行
Thread-0在运行
Thread-1在运行
Thread-1在运行
Thread-1在运行
main在运行
main在运行
main在运行

说明执行顺序为t1、t2、主线程(main)?不!this.getName()并不能代表当前运行的线程名,只有Thread.currentThread().getName()才能代表。可以将例2中代码this.getName()改为Thread.currentThread().getName(),会发现打印结果变为:

main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行
main在运行

这说明run方法根本就没有启动线程,从始至终运行的都只是主线程。

线程只能被启动一次

一个线程一旦被启动,它就不能再次被启动。在例2中在代码t1.start();后再加一句t1.start();,再运行会抛出java.lang.IllegalThreadStateException。

本文就讲到这里,想了解更多内容请参考

  • Java并发编程札记-目录

END.

你可能感兴趣的:(Java并发,Java并发编程札记)