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

来源:互联网 发布:ubuntu双系统磁盘分区 编辑:程序博客网 时间:2024/06/05 06:33

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

目录:

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

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

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

我认为Runnable和Callable的作用只是定义任务,创建线程还是需要Thread构造器或者Executor执行器来完成。

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就是一个线程构造器。
MarkdownPhotos/master/CSDNBlogs/concurrency/0102/threadConstruction.png

下面通过实例看下如何通过继承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的定义如下:

@FunctionalInterfacepublic 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<String> {    @Override    public String call() throws Exception {        return "This is returned string.";    }}public class MyCallabeTest {    public static void main(String[] args){        //创建有返回值的任务        Callable<String> callable = new MyCallable();        FutureTask<String> 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<String> callable = new MyCallable();        //创建线程执行器        ExecutorService threadPool = Executors.newSingleThreadExecutor();        //通过线程执行器执行callable,并通过future获取返回结果        Future<String> 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<Integer> {    @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<Future<Integer>> results = new ArrayList<Future<Integer>>();        //将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方法后打印结果为

44946311

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

Executor

刚刚用到了ExecutorService,现在来详细讲下。Java SE5的Java.util.concurrent包中的Executor(执行器)可以为我们管理线程,从而简化了并发编程。Executor在客户端和任务执行之间提供了一个间接层,任务的执行由Executor而不是客户端来完成。Executor允许我们管理异步任务的执行,而无需显示地线程的生命周期。Executor是Java SE5中启动线程的优选方法。

执行器内容很多,以后会详细讲解。

总结

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.

阅读全文
1 0
原创粉丝点击