Java线程实现的4种方式

走出舒适圈,你会成为最好的自己


前言

Java多线程实现方式主要有四种:继承Thread类、实现Runnable接口、实现Callable接口通过FutureTask包装器来创建Thread线程、使用ExecutorService、Callable、Future实现有返回结果的多线程。
其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。


一、线程实现的四种方式

1. 继承Thread类创建线程

Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。

示例:

public class MyThread extends Thread {
    
    
    @Override
    public void run() {
    
    
        System.out.println("This is a thread.");
    }

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

2. 实现Runable接口创建线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口

public class MyThread1 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
    
    
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
    }
}

2. 实现Runable接口创建线程

如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口

public class MyThread1 implements Runnable{
    
    

    @Override
    public void run() {
    
    
        System.out.println(Thread.currentThread().getName());
    }

    public static void main(String[] args) {
    
    
        MyThread1 myThread1 = new MyThread1();
        Thread thread = new Thread(myThread1);
        thread.start();
    }
}

3. 实现Callable接口通过FutureTask包装器来创建Thread线程

Callable接口(也只有一个方法)定义如下:

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

public class MyCallable implements Callable {
    
    
    @Override
    public Object call() throws Exception {
    
    
        return "This is a callable";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        MyCallable myCallable = new MyCallable();
        FutureTask oneTask = new FutureTask(myCallable);
        Thread thread = new Thread(oneTask);
        thread.start();
        //获取返回值 
        System.out.println(oneTask.get());
    }

}

4. 使用ExecutorService、Callable、Future实现有返回结果的线程

ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。

示例:

扫描二维码关注公众号,回复: 13127658 查看本文章
import java.util.concurrent.*;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
 * 有返回值的线程
 */
@SuppressWarnings("unchecked")
public class Test {
    
    
    public static void main(String[] args) throws ExecutionException,
            InterruptedException {
    
    
        System.out.println("----程序开始运行----");
        Date date1 = new Date();

        int taskSize = 5;
        // 创建一个线程池  
        ExecutorService pool = Executors.newFixedThreadPool(taskSize);
        // 创建多个有返回值的任务  
        List<Future> list = new ArrayList<Future>();
        for (int i = 0; i < taskSize; i++) {
    
    
            Callable c = new MyCallable(i + " ");
            // 执行任务并获取Future对象  
            Future f = pool.submit(c);
            // System.out.println(">>>" + f.get().toString());  
            list.add(f);
        }
        // 关闭线程池  
        pool.shutdown();

        // 获取所有并发任务的运行结果  
        for (Future f : list) {
    
    
            // 从Future对象上获取任务的返回值,并输出到控制台  
            System.out.println(">>>" + f.get().toString());
        }

        Date date2 = new Date();
        System.out.println("----程序结束运行----,程序运行时间【"
                + (date2.getTime() - date1.getTime()) + "毫秒】");
    }
}

class MyCallable implements Callable<Object> {
    
    
    private String taskNum;

    MyCallable(String taskNum) {
    
    
        this.taskNum = taskNum;
    }

    public Object call() throws Exception {
    
    
        System.out.println(">>>" + taskNum + "任务启动");
        Date dateTmp1 = new Date();
        Thread.sleep(1000);
        Date dateTmp2 = new Date();
        long time = dateTmp2.getTime() - dateTmp1.getTime();
        System.out.println(">>>" + taskNum + "任务终止");
        return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
    }
}

二、Thread中的run()与start()

1.run()与start()区别

  1. start:用start方法来启动线程,真正实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片,就开始执行run()方法,这里方法 run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
  2. run: run()方法只是类的一个普通方法而已,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要顺序执行,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到写线程的目的。总结:调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行。这两个方法应该都比较熟悉,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法,这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。

2.线程状态

Java 的线程是通过java.lang.Thread类来实现的。VM启动时会有一个由主方法所定义的线程。可以通过创建Thread的实例来创建新的线程。每个线程都是通过某个特定Thread对象所对应的方法run()来完成其操作的,方法run()称为线程体。通过调用Thread类的start()方法来启动一个线程。

在Java 当中,线程通常都有五种状态,创建、就绪、运行、阻塞和死亡。

  1. 第一是创建状态。在生成线程对象,并没有调用该对象的start方法,这是线程处于创建状态。
  2. 第二是就绪状态。当调用了线程对象的start方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
  3. 第三是运行状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行run函数当中的代码。
  4. 第四是阻塞状态。线程正在运行的时候,被暂停,通常是为了等待某个事件的发生(比如说某项资源就绪)之后再继续运行。sleep,suspend,wait等方法都可以导致线程阻塞。
  5. 第五是死亡状态。如果一个线程的run方法执行结束或者调用stop方法后,该线程就会死亡。对于已经死亡的线程,无法再使用start方法令其进入就绪。

3. 线程的启动方式

3.1 启动线程方式

  1. 写一个类继承自Thread类,重写run方法。用start方法启动线程
  2. 写一个类实现Runnable接口,实现run方法。用new Thread(Runnable target).start()方法来启动

3.2 start与run启动

调用start()后,线程会被放到等待队列,等待CPU调度,并不一定要马上开始执行,只是将这个线程置于可动行状态。然后通过JVM,线程Thread会调用run()方法,执行本线程的线程体。先调用start后调用run,这么麻烦,为了不直接调用run?就是为了实现多线程的优点,没这个start不行。
8. start()方法来启动线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;通过调用Thread类的start()方法来启动一个线程,
这时此线程是处于就绪状态, 并没有运行。 然后通过此Thread类调用方法run()来完成其运行操作的,
这里方法run()称为线程体,它包含了要执行的这个线程的内容, Run方法运行结束, 此线程终止。然后CPU再调度其它线程。
9. run()方法当作普通方法的方式调用。程序还是要顺序执行,要等待run方法体执行完毕后,才可继续执行下面的代码;
程序中只有主线程——这一个线程, 其程序执行路径还是只有一条, 这样就没有达到写线程的目的。

三、Executors类多线程

未完待续


参考文档

JAVA多线程实现的四种方式

猜你喜欢

转载自blog.csdn.net/bj_ameng/article/details/115266696
今日推荐