代码改变世界

第一章:多线程二之创建线程的4种方法

2022-09-28 22:36  阿方技术圈  阅读(21)  评论(0)    收藏  举报

在一个CPU内核上,同一时刻只能有一个线程是正在执行的,该线程也被叫作当前线程。

一、创建线程的几种方式

1、继承Thread类

package day01;
public class MyThread{
    public static final int MAX_RUN = 10;
    public static String getCurThreadName() {
        return Thread.currentThread().getName();
    }
    //线程的编号
    static int threadNo = 1;
    static class testThread extends Thread {  //
        public testThread() {
            super("testThread-" + threadNo++); //
        }

        public void run() {   //
            for (int i = 1; i < MAX_RUN; i++) {
                System.out.println(getName() + ", 轮次:" + i);
            }
            System.out.println(getName() + " 运行结束.");
        }
    }

    public static void main(String args[]) throws InterruptedException {
        Thread thread = null;
        //方法一:使用Thread子类创建和启动线程
        for (int i = 0; i < 5; i++) {
            thread = new testThread();
            thread.start();
        }

        System.out.println(getCurThreadName() + " 运行结束.");
    }
}

运行main方法后的结果

testThread-1, 轮次:1
testThread-2, 轮次:1
testThread-3, 轮次:1
testThread-3, 轮次:2
main 运行结束.
testThread-3, 轮次:3
testThread-3, 轮次:4
testThread-3, 轮次:5
testThread-3, 轮次:6
testThread-5, 轮次:1
testThread-5, 轮次:2
testThread-4, 轮次:1
testThread-2, 轮次:2
testThread-1, 轮次:2
testThread-2, 轮次:3
testThread-4, 轮次:2
testThread-5, 轮次:3
testThread-3, 轮次:7
testThread-5, 轮次:4
testThread-4, 轮次:3
testThread-2, 轮次:4
testThread-1, 轮次:3
testThread-2, 轮次:5
testThread-4, 轮次:4
testThread-5, 轮次:5
testThread-3, 轮次:8
testThread-3, 轮次:9
testThread-5, 轮次:6
testThread-4, 轮次:5
testThread-2, 轮次:6
testThread-1, 轮次:4
testThread-2, 轮次:7
testThread-4, 轮次:6
testThread-5, 轮次:7
testThread-3 运行结束.
testThread-5, 轮次:8
testThread-5, 轮次:9
testThread-5 运行结束.
testThread-4, 轮次:7
testThread-4, 轮次:8
testThread-2, 轮次:8
testThread-1, 轮次:5
testThread-2, 轮次:9
testThread-4, 轮次:9
testThread-2 运行结束.
testThread-1, 轮次:6
testThread-4 运行结束.
testThread-1, 轮次:7
testThread-1, 轮次:8
testThread-1, 轮次:9
testThread-1 运行结束.

 2、实现Runnable接口创建线程

package day01;

//1、定义一个类实现Runnable接口
//2、实现Runnable接口中的run()的抽象方法
//3、通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器实例赋值给自己的target执行目标属性
//4、调用Thread实例的start()的方法将被JVM执行,该run()方法将调用targe的属性的run()方法,从而完成Runnable实现类业务代码逻辑的并发执行
public class ThreadDemo implements  Runnable{


    @Override
    public void run() {

        for (int i=0;i<10;i++){
            System.out.println("我正在工作中"+ i);
        }
    }

    public static void main(String[] args){//主线程
        ThreadDemo threadDemo = new ThreadDemo();//创建Runnable接口实现类对象
        Thread t = new Thread(threadDemo);//创建线程对象
        t.start();//启动线程
        for (int i = 0; i < 300; i++) {
            System.out.println("多线程"+i);
        }

    }
}

通过匿名类创建Runnable线程目标类

package day01;

import sun.misc.ThreadGroupUtils;

public class ThreadDemo1 {

    public static void main(String[] args){
        Thread thread = null;
        for (int i = 0;i<10;i++){//使用Runnable的匿名类创建和启动线程
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j=1;j<5;j++){
                        System.out.println("我正在工作" + j);
                    }

                }
            });
            System.out.println("工作结束" +thread.getName() );
           thread.start();
        }
    }
}

通过实现Runnable接口的方式创建线程目标类的优缺点

通过实现Runnable接口的方式创建线程目标类有以下缺点:

(1)所创建的类并不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程。

(2)如果访问当前线程的属性(甚至控制当前线程),不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前线程实例,才能访问和控制当前线程。

通过实现Runnable接口的方式创建线程目标类有以下优点:

(1)可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。比如,当一个Dog类继承了Pet类,再要继承Thread类就不行了。所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式。

(2)逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想。

 

3、使用Callable和FutureTask创建线程

使用背景:

继承Thread类或者实现Runnable接口这两种方式来创建线程类,但是这两种方式有一个共同的缺陷:不能获取异步执行的结果。这是一个比较大的问题,很多场景都需要获取异步执行的结果,通过Runnable无法实现,是因为它的run()方法不支持返回值。

Callable接口位于java.util.concurrent包中

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

其唯一的抽象方法call()有返回值,返回值的类型为Callable接口的泛型形参类型。call()抽象方法还有一个Exception的异常声明,容许方法的实现版本的内部异常直接抛出,并且可以不予捕获。

 

RunnableFuture接口:

RunnableFuture接口实现了两个目标:一是可以作为Thread线程实例的target实例,二是可以获取异步执行的结果

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

Future接口:

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

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

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

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

     package java.util.concurrent;
     public interface Future<V> {
         boolean cancel(boolean mayInterruptRunning); //取消异步执行
         boolean isCancelled();
         boolean isDone();//判断异步任务是否执行完成
         //获取异步任务完成后的执行结果
         V get() throws InterruptedException, ExecutionException;
         //设置时限,获取异步任务完成后的执行结果
         V get(long timeout, TimeUnit unit) throws InterruptedException, 
                                               ExecutionException, TimeoutException;
        ...
     }

FutureTask类:

FutureTask类是Future接口的实现类,提供了对异步任务的操作的具体实现。但是,FutureTask类不仅实现了Future接口,还实现了Runnable接口,或者更加准确地说,FutureTask类实现了RunnableFuture接口。前面讲到RunnableFuture接口很关键,既可以作为Thread线程实例的target目标,又可以获取并发任务执行的结果,是Thread与Callable之间一个非常重要的搭桥角色。但是,RunnableFuture只是一个接口,无法直接创建对象,如果需要创建对象,就需用到它的实现类——FutureTask。所以说,FutureTask类才是真正的在Thread与Callable之间搭桥的类。

 

 

 FutureTask内部有一个Callable类型的成员——callable实例属性

     private Callable<V> callable;

callable实例属性用来保存并发执行的Callable<V>类型的任务,并且callable实例属性需要在FutureTask实例构造时进行初始化。FutureTask类实现了Runnable接口,在其run()方法的实现版本中会执行callable成员的call()方法

FutureTask内部还有另一个非常重要的Object类型的成员——outcome实例属性:

     private Object outcome;

FutureTask的outcome实例属性用于保存callable成员call()方法的异步执行结果。在FutureTask类的run()方法完成callable成员的call()方法的执行之后,其结果将被保存在outcome实例属性中,供FutureTask类的get()方法获取

package com.lhf.高并发多线程;

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

public class Demo1 {
    //创建一个Callable接口实现类
    static class RunnableTask implements Callable<Long> {

        //重写call方法,并执行具体逻辑
        @Override
        public Long call() throws Exception {
            long startTime = System.currentTimeMillis();
            System.out.println(Thread.currentThread() + "线程开始运行");
            Thread.sleep(2000);
            for (int i = 0;i<10000;i++){
                int j = i*10000;
            }
            long runTime = System.currentTimeMillis() - startTime;
            System.out.println(Thread.currentThread() + "运行结束");
            return runTime;
        }
    }
    public static void main(String[] arg){
        try {
        RunnableTask task = new RunnableTask();
        FutureTask<Long> futureTask = new FutureTask<Long>(task);
        Thread thread = new Thread(futureTask,"RunnableThread");
        thread.start();
        Thread.sleep(500);
        System.out.println("让程序跑一会");
        for (int i = 0;i<10000;i++){
            int j = i*10000;

        }
        System.out.println(Thread.currentThread()+"并发任务执行完成");

            System.out.println(thread.getName() + "执行线程所用时间" + futureTask.get());
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread() + "运行结束");
    }
}

运行结果

Thread[RunnableThread,5,main]线程开始运行
让程序跑一会
Thread[main,5,main]并发任务执行完成
Thread[RunnableThread,5,main]运行结束
RunnableThread执行线程所用时间2002
Thread[main,5,main]运行结束

4、通过线程池创建线程

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

线程池的创建与执行目标提交,通过Executors工厂类创建一个线程池

     //创建一个包含三个线程的线程池
     private static ExecutorService pool = Executors.newFixedThreadPool(3);

ExecutorService是Java提供的一个线程池接口,每次我们在异步执行target目标任务的时候,可以通过ExecutorService线程池实例去提交或者执行。ExecutorService实例负责对池中的线程进行管理和调度,并且可以有效控制最大并发线程数,提高系统资源的使用率,同时提供定时执行、定频执行、单线程、并发数控制等功能。

 

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

     //方法一:执行一个 Runnable类型的target执行目标实例,无返回
     void execute(Runnable command);
     
     //方法二:提交一个 Callable类型的target执行目标实例, 返回一个Future异步任务实例
     <T> Future<T> submit(Callable<T> task);  
                         
     //方法三:提交一个 Runnable类型的target执行目标实例, 返回一个Future异步任务实例
     Future<?> submit(Runnable task);
package com.lhf.高并发多线程;

import com.sun.corba.se.impl.orbutil.closure.Future;

import java.util.concurrent.*;

public class Demo2 {
    //创建一个含有5个线程的线程池
    private static ExecutorService pool = Executors.newFixedThreadPool(3);

    static class  DemoThread implements Runnable{

        @Override
        public void run() {
            for (int i = 0;i<10;i++){
                System.out.println(Thread.currentThread() + ",线程执行" +i);
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    static class RunnableTask implements Callable<Long>{

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

    public static void main(String[] arg){
        pool.execute(new DemoThread());
        pool.execute(new Runnable() {
            @Override
            public void run() {
                for (int i = 0;i<10;i++){
                    System.out.println(Thread.currentThread() + ",线程" +i);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        java.util.concurrent.Future future = pool.submit(new RunnableTask());
        try {
            System.out.println("异步任务执行结果" + future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }
}

执行结果

Thread[pool-1-thread-1,5,main],线程执行0
Thread[pool-1-thread-2,5,main],线程0
Thread[pool-1-thread-3,5,main]线程开始运行
Thread[pool-1-thread-2,5,main],线程1
Thread[pool-1-thread-1,5,main],线程执行1
Thread[pool-1-thread-2,5,main],线程2
Thread[pool-1-thread-1,5,main],线程执行2
Thread[pool-1-thread-1,5,main],线程执行3
Thread[pool-1-thread-2,5,main],线程3
Thread[pool-1-thread-1,5,main],线程执行4
Thread[pool-1-thread-2,5,main],线程4
Thread[pool-1-thread-2,5,main],线程5
Thread[pool-1-thread-1,5,main],线程执行5
Thread[pool-1-thread-2,5,main],线程6
Thread[pool-1-thread-1,5,main],线程执行6
Thread[pool-1-thread-1,5,main],线程执行7
Thread[pool-1-thread-2,5,main],线程7
Thread[pool-1-thread-2,5,main],线程8
Thread[pool-1-thread-1,5,main],线程执行8
Thread[pool-1-thread-2,5,main],线程9
Thread[pool-1-thread-1,5,main],线程执行9
Thread[pool-1-thread-3,5,main]运行结束
异步任务执行结果2013