创建线程的几种方式

创建线程的几种方式

继承Thread类创建线程

​ 通过继承Thread类创建一个线程类,新的 线程子类重写了Thread的run()方法,实现了用户业务代码的并发执行,具体如下:

public class javaJUCstudy {
    public static final int MAX_TURN = 5;
    public static String getCurThreadName(){
        return Thread.currentThread().getName();
    }
    static int threadId = 1;
    public static void main(String[] args) {
        Thread h;
        for (int i = 0; i < 2; i++) {
            h = new TestThread();
            h.start();
        }
        System.out.println(getCurThreadName()+"运行结束");
    }
}
class TestThread extends Thread{
    public TestThread(){
        super("TestThread:"+javaJUCstudy.threadId++);
    }
    public void run(){
        for (int i = 0; i < javaJUCstudy.MAX_TURN; i++) {
            System.out.println(getName()+",轮次:"+i);
        }
        System.out.println(getName()+"结束");
    }
}

​ 执行结果:

main运行结束
TestThread:2,轮次:0
TestThread:1,轮次:0
TestThread:2,轮次:1
TestThread:1,轮次:1
TestThread:2,轮次:2
TestThread:1,轮次:2
TestThread:2,轮次:3
TestThread:1,轮次:3
TestThread:1,轮次:4
TestThread:2,轮次:4
TestThread:2结束
TestThread:1结束

​ TestThread类的关键点是重写了Thread类的run()方法, 将需要并发执行的用户业务代码编写在继承的run()方法中。

实现Runnable接口创建线程目标类

​ 通过继承Thread类并重写它的run()方法只是创建Java线程的一种方式。接下来要介绍的一种方式是通过实现现Runnable接口创建线程目标类,在这之前我们先来看看Thread的run方法。

public class Thread implements Runnable {
    ......
    private Runnable target;//执行目标
    public void run() {
        if (target != null) {
            target.run();//调用执行目标的run方法
        }
    }
    public Thread(Runnable target) {//包含执行目标的构造器
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
}

​ 在Thread类的run()方法中,如果target(执行目标)不为空,就 执行target属性的run()方法。而target属性是Thread类的一个实例属 性,并且target属性的类型为Runnable。

​ Thread类的target属性在什么情况下非空呢?Thread类有一系列 的构造器,其中有多个构造器可以为target属性赋值,这些构造器包 括如下两个:

	1. public Thread(Runnable target)
	2. public Thread(Runnable target,String name)

​ 使用这两个构造器传入target执行目标实例(Runnable实例), 就可以直接通过Thread类的run()方法以默认方式实现。而我们可以通过创建一个类来实现Runnbale接口来创建Runnable实例。通过实现Runnable接口创建线程类的代码如下:

package suan.JUC;

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

public class RunnableTest {
    public static final int MAX_TURN = 5;
    public static String getCurThreadName(){
        return Thread.currentThread().getName();
    }
    static int threadId = 1;

    public static void main(String[] args) throws InterruptedException {
        Thread h;
        for (int i = 0; i < 2; i++) {
            Runnable r = new TestRunnable();
            h = new Thread(r,"TestThread:"+threadId++);
            h.start();
        }
        Thread.sleep(1000);
        System.out.println(getCurThreadName()+"结束");
    }
}
class TestRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < RunnableTest.MAX_TURN; i++) {
            System.out.println(Thread.currentThread().getName()+",轮次:"+i);
        }
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}

执行结果:

TestThread:1,轮次:0
TestThread:2,轮次:0
TestThread:1,轮次:1
TestThread:2,轮次:1
TestThread:1,轮次:2
TestThread:2,轮次:2
TestThread:1,轮次:3
TestThread:2,轮次:3
TestThread:2,轮次:4
TestThread:1,轮次:4
TestThread:2结束
TestThread:1结束
main结束

​ 实例中静态内部类RunTarget执行目标类,不再是继承Thread线程 类,而是实现Runnable接口,需要异步并发执行的代码逻辑被编写在 它的run()方法中。通过实现Runnable接口的方式创建的执行目标类,如果需要访问 线程的任何属性和方法,必须通过Thread.currentThread()获取当前 的线程对象,通过当前线程对象间接访问。

使用实现Runnable接口创建线程目标类的简便写法

​ 因为Runnable是一个函数式接口,在接口实现时可以使用Lambda 表达式提供匿名实现,来简化我们的代码。改进后如下:

public class RunnableTest {
    public static final int MAX_TURN = 5;
    public static String getCurThreadName(){
        return Thread.currentThread().getName();
    }
    static int threadId = 1;

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                for (int j = 0; j < RunnableTest.MAX_TURN; j++) {
                    System.out.println(Thread.currentThread().getName()+",轮次:"+j);
                }
                System.out.println(Thread.currentThread().getName()+"结束");
            },"name"+threadId++).start();
        }
        Thread.sleep(1000);
        System.out.println(getCurThreadName()+"结束");
    }
}

执行结果如下:

name1,轮次:0
name2,轮次:0
name2,轮次:1
name2,轮次:2
name2,轮次:3
name2,轮次:4
name2结束
name1,轮次:1
name1,轮次:2
name1,轮次:3
name1,轮次:4
name1结束
main结束

使用Callable和FutureTask 创建线程

​ 前面已经介绍了继承Thread类或者实现Runnable接口这两种方式 来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执 行的结果。为了解决异步执行的结果问题,Java语言在1.5版本之后提供了一 种新的多线程创建方法:通过Callable接口和FutureTask类相结合创 建线程。

Callable接口

Callable接口位于java.util.concurrent包中,查看它的Java源 代码,如下:

package java.util.concurrent;
@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

Callable接口是一个泛型接口,也是一个“函数式接口”。其唯 一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型 形参类型。call()抽象方法还有一个Exception的异常声明,容许方法 的实现版本的内部异常直接抛出,并且可以不予捕获。但Callable接口不能像Runnable接口那样作为Thread线程实例的taiget使用,这就要引入RunnableFuture接口了。

RunnableFuture接口

RunnableFuture接口与 Runnable接口、Thread类紧密相关,RunnableFuture接口实现了两个目标:一是可以作为Thread线程 实例的target实例,二是可以获取异步执行的结果。

package java.util.concurrent;
public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run();
}

RunnableFuture继承了Runnable接口,从 而保证了其实例可以作为Thread线程实例的target目标;同时, RunnableFuture通过继承Future接口,保证了可以获取未来的异步执 行结果。这里又需要引入Future接口了。

Future接口

Future接口至少提供了三大功能:

(1)能够取消异步执行中的任务。

(2)判断异步任务是否执行完成。

(3)获取异步任务完成后的执行结果。

Future接口的代码如下:

package java.util.concurrent;
public interface Future<V> {
    boolean cancel(boolean mayInterruptIfRunning);//取消异步任务的执行。
    boolean isCancelled();//获取异步任务的取消状态。如果任务完成前被取消,就返回true。
    boolean isDone();//获取异步任务的执行状态。如果任务执行结束,就返回true。
    V get() throws InterruptedException, ExecutionException;
    //获取异步任务执行的结果。注意,这个方法的调用是阻塞性的。如果异步任务没有执行完成,异步结果获取线程(调用线程)会一直被阻塞,一直阻塞到异步任务执行完成,其异步结果返回给调用线程。
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
    //设置时限,(调用线程)阻塞性地获取异步任务执行的结果。该方法的调用也是阻塞性的,但是结果获取线程(调用线程)会有一个阻塞时长限制,不会无限制地阻塞和等待,如果其阻塞时间超过设定的timeout时间,该方法将抛出异常,调用线程可捕获此异常。
}
FutureTask类

​ FutureTask类是Future接口的实现类,提供了对异步任务的操作 的具体实现。但是,FutureTask类不仅实现了Future接口,还实现了 Runnable接口,或者更加准确地说,FutureTask类实现了 RunnableFuture接口。

​ FutureTask实现了 RunnableFuture接口,而RunnableFuture接口继承了Runnable接口和 Future接口,所以FutureTask既能作为一个Runnable类型的target执 行目标直接被Thread执行,又能作为Future异步任务来获取Callable 的计算结果。

机构图如下:

在这里插入图片描述

使用Callable和FutureTask创建线程的具体步骤

通过FutureTask类和Callable接口的联合使用可以创建能够获取 异步执行结果的线程,具体步骤如下:

(1)创建一个Callable接口的实现类,并实现其call()方法,编 写好异步执行的具体逻辑,可以有返回值。

(2)使用Callable实现类的实例构造一个FutureTask实例。

(3)使用FutureTask实例作为Thread构造器的target入参,构造 新的Thread线程实例。

(4)调用Thread实例的start()方法启动新线程,启动新线程的 run()方法并发执行。其内部的执行过程为:启动Thread实例的run() 方法并发执行后,会执行FutureTask实例的run()方法,最终会并发执 行Callable实现类的call()方法。

(5)调用FutureTask对象的get()方法阻塞性地获得并发线程的 执行结果。

代码如下:

package suan.JUC;

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

public class FutureTaskTest {
    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;
    public static String getCurThreadName(){
        return Thread.currentThread().getName();
    }

    public static void main(String[] args) throws InterruptedException {
        ReturnTask task = new ReturnTask();
        FutureTask<Long> futureTask = new FutureTask<>(task);
        Thread thread = new Thread(futureTask,"有返回值的异步线程");
        thread.start();
        Thread.sleep(1000);
        System.out.println(getCurThreadName()+"等一会");
        for (int i = 0; i < COMPUTE_TIMES/2; i++) {
            int j = i * 10000;
        }
        System.out.println(getCurThreadName()+"获取异步执行的结果");
        try {
            System.out.println(thread.getName()+"线程占用时间"+futureTask.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        System.out.println("运行结束");
    }
}
class ReturnTask implements Callable<Long> {

    @Override
    public Long call() throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        Thread.sleep(1000);
        for (int i = 0; i < FutureTaskTest.COMPUTE_TIMES; i++) {
            int j = i * 10000;
        }
        long useTime = System.currentTimeMillis() - startTime;
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
        return useTime;
    }
}

运行结果如下:

有返回值的异步线程线程开始运行
main等一会
有返回值的异步线程线程运行结束
main获取异步执行的结果
有返回值的异步线程线程占用时间1010
运行结束

通过线程池创建线程

前面的示例中,所创建的Thread实例在执行完成之后都销毁了, 这些线程实例都是不可复用的。实际上创建一个线程实例在时间成 本、资源耗费上都很高,在高并发的场景中,断然不 能频繁进行线程实例的创建与销毁,而是需要对已经创建好的线程实 例进行复用,这就涉及线程池的技术。Java中提供了一个静态工厂来 创建不同的线程池,该静态工厂为Executors工厂类。

向ExecutorService线程池提交异步执行target目标任务的常用方法有:

  1. 执行一个 Runnable类型的target执行目标实例,无返回 。

​ void execute(Runnable command);

  1. 提交一个 Callable类型的target执行目标实例, 返回一个 Future异步任务实例

​ Future submit(Callable task);

  1. 提交一个 Runnable类型的target执行目标实例, 返回一个 Future异步任务实例

​ Future submit(Runnable task);

代码实例:

package suan.JUC;

import java.util.concurrent.*;

public class ExecutorsTest {
    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        pool.execute(new demoThread());
        pool.execute(()->{
            for (int i = 0; i < MAX_TURN; i++) {
                System.out.println(Thread.currentThread().getName()+", 轮次: "+i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Future<Long> submit = pool.submit(new ReturnTaskTest());
        Long result = (Long)submit.get();
        System.out.println("异步执行的结果:"+result);
        Thread.sleep(5000);
        System.out.println(Thread.currentThread().getName()+"结束");
    }
}
class demoThread implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < ExecutorsTest.MAX_TURN; i++) {
            System.out.println(Thread.currentThread().getName()+", 轮次: "+ i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
class ReturnTaskTest implements Callable<Long> {

    @Override
    public Long call() throws Exception {
        long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+"线程开始运行");
        Thread.sleep(1000);
        for (int i = 0; i < ExecutorsTest.MAX_TURN; i++) {
            System.out.println(Thread.currentThread().getName()+", 轮次: "+i);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        long useTime = System.currentTimeMillis() - startTime;
        System.out.println(Thread.currentThread().getName()+"线程运行结束");
        return useTime;
    }
}

执行结果:

pool-1-thread-1, 轮次: 0
pool-1-thread-1, 轮次: 1
pool-1-thread-1, 轮次: 2
pool-1-thread-2, 轮次: 0
pool-1-thread-3线程开始运行
pool-1-thread-1, 轮次: 3
pool-1-thread-2, 轮次: 1
pool-1-thread-1, 轮次: 4
pool-1-thread-2, 轮次: 2
pool-1-thread-2, 轮次: 3
pool-1-thread-2, 轮次: 4
pool-1-thread-3, 轮次: 0
pool-1-thread-3, 轮次: 1
pool-1-thread-3, 轮次: 2
pool-1-thread-3, 轮次: 3
pool-1-thread-3, 轮次: 4
pool-1-thread-3线程运行结束
异步执行的结果:1086
main结束

此时虽然main方法的线程结束了,但是线程池中的线程还没有结束,程序还没有停止。

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值