创建线程的四种方式

111 阅读2分钟

线程创建方法一:继承Thread类创建线程

  • 需要继承Thread类,创建一个新的线程类。
  • 同时重写run()方法,将需要并发执行的业务代码编写在run()方法中。
package createThread;



import lombok.extern.slf4j.Slf4j;

/**
 * @Author: Long
 * @Date: 2022/9/23 23:52
 * 高内聚低耦合,线程操作资源类
 * 继承Thread类创建线程
 */

@Slf4j
public class CreateDemo {
    public static final int MAX_TURN = 5;
    public static String getCurThreadName() {
        return Thread.currentThread().getName();
    }
    //线程的编号 相当于资源类
    static int threadNo = 1;

    static class DemoThread extends Thread {  //①继承Thread类
        public DemoThread() {
            super("DemoThread-" + threadNo++); //②设置线程名
        }

        public void run() {   //③
            for (int i = 1; i < MAX_TURN; i++) {
                log.info(getName() + ", 轮次:" + i);
            }
            log.info("{}运行结束",getName() );
        }
    }

    public static void main(String args[]) throws InterruptedException {
        //方法一:使用Thread子类创建和启动线程
        for (int i = 0; i < 3; i++) {
            new DemoThread().start();
        }
        System.out.println();
        log.info(getCurThreadName() + " 运行结束.");
    }
}
  • 运行结果

image.png

线程创建方法二:实现Runnable接口创建线程目标类

  • Thread类的run()方法的代码
package java.lang;
public class Thread implements Runnable {
    private Runnable target; //执行目标
    public void run() {
        if(this.target != null) {
            this.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)
  • Runnable接口(函数式接口)
    package java.lang;
    @Functional
    Interface public interface Runnable { 
        void run(); 
     }
  • Runnable有且仅有一个抽象方法——void run(),代表被执行的用户业务逻辑的抽象,在使用的时候,将用户业务逻辑编写在Runnable实现类的run()的实现版本中。当Runnable实例传入Thread实例的target属性后,Runnable接口的run()的实现版本将被异步调用。具体步骤如下:
  1. 定义一个新类实现Runnable接口。
  2. 实现Runnable接口中的run()抽象方法,将线程代码逻辑存放在该run()实现版本中。
  3. 通过Thread类创建线程对象,将Runnable实例作为实际参数传递给Thread类的构造器,由Thread构造器将该Runnable实例赋值给自己的target执行目标属性。
  4. 调用Thread实例的start()方法启动线程。
  5. 线程启动之后,线程的run()方法将被JVM执行,该run()方法将调用target属性的run()方法,从而完成Runnable实现类中业务代码逻辑的并发执行。代码如下:

外部实现类写法

package createThread;

import lombok.extern.slf4j.Slf4j;
import utils.ThreadUtil;

/**
 * @Author: long
 * @Date: 2022/9/25 0:37
 *外部实现类写法
 */
@Slf4j
public class CreateDemo2
{
    public static final int MAX_TURN = 5;
    static int threadNo = 1;
    static class RunTarget implements Runnable  //①实现Runnable接口
    {
        public void run()  //②在这里编写业务逻辑
        {
            for (int j = 1; j < MAX_TURN; j++)
            {
                log.info(ThreadUtil.getCurThreadName() + ", 轮次:" + j);
            }
            log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
        }
    }

    public static void main(String args[]) throws InterruptedException
    {
        Thread thread = null;
        for (int i = 0; i < 2; i++)
        {
            Runnable target = new RunTarget();
            //通过Thread 类创建线程对象,将Runnable实例作为实际参数传入
            thread = new Thread(target, "RunnableThread" + threadNo++);
            thread.start();
        }
    }
}

内部类写法

package createThread;

import lombok.extern.slf4j.Slf4j;
import utils.ThreadUtil;

/**
 * @Author: longong
 * @Date: 2022/9/25 0:42
 * 内部类写法
 */
@Slf4j
public class CreateDemo2Lambda {
    public static final int MAX_TURN = 5;
    static int threadNo = 1;
    public static void main(String args[]) throws InterruptedException
    {
        Thread thread = null;
        for (int i = 0; i < 2; i++)
        {
            Runnable target = new CreateDemo2.RunTarget();
            //通过Thread 类创建线程对象,将Runnable实例作为实际参数传入
            thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int j = 1; j < MAX_TURN; j++)
                    {
                        log.info(ThreadUtil.getCurThreadName() + ", 轮次:" + j);
                    }
                    log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
                }
            }, "RunnableThread" + threadNo++);
            thread.start();
        }
    }
}

lambbda表达式写法

package createThread;

import lombok.extern.slf4j.Slf4j;

/**
 * @Author: long
 * @Date: 2022/9/24 1:00
 * lambbda表达式的写法
 */
@Slf4j
public class CreateDemolambbda {

    public static final int MAX_TURN = 5;
    public static String getCurThreadName() {
        return Thread.currentThread().getName();
    }
    //线程的编号 相当于资源类
    static int threadNo = 1;

    public static void main(String args[]) throws InterruptedException {
        //方法一:使用Thread子类创建和启动线程
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                for (int j = 1; j < MAX_TURN; j++) {
                    log.info(getCurThreadName() + ", 轮次:" + j);
                }
                log.info("{}运行结束",getCurThreadName() );
            },i+"").start();
        }
        System.out.println();
        log.info(getCurThreadName() + " 运行结束.");
    }
}

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

  • 所创建的类并不是线程类,而是线程的target执行目标类,需要将其实例作为参数传入线程类的构造器,才能创建真正的线程。
  • 如果访问当前线程的属性(甚至控制当前线程),不能直接访问Thread的实例方法,必须通过Thread.currentThread()获取当前线程实例,才能访问和控制当前线程。

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

  • 可以避免由于Java单继承带来的局限性。如果异步逻辑所在类已经继承了一个基类,就没有办法再继承Thread类。比如,当一个Dog类继承了Pet类,再要继承Thread类就不行了。所以在已经存在继承关系的情况下,只能使用实现Runnable接口的方式。
  • 逻辑和数据更好分离。通过实现Runnable接口的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景。在同一个资源被多个线程逻辑异步、并行处理的场景中,通过实现Runnable接口的方式设计多个target执行目标类可以更加方便、清晰地将执行逻辑和数据存储分离,更好地体现了面向对象的设计思想

“逻辑与数据分离”的代码演示

package createThread;

import lombok.extern.slf4j.Slf4j;
import utils.ThreadUtil;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * @Author: long
 * @Date: 2022/10/4 23:53
 */
// 省略import
@Slf4j
public class SalesDemo
{
    public static final int MAX_AMOUNT = 5; //商品数量

    //商店商品类(销售线程类),一个商品一个销售线程,每个线程异步销售4次
    static class StoreGoods extends Thread
    {
        StoreGoods(String name)
        {
            super(name);
        }

        private   AtomicInteger goodsAmount = new AtomicInteger(MAX_AMOUNT);;

        public void run()
        {
                for (int i = 0; i <= MAX_AMOUNT; i++)
                {
                    if (this.goodsAmount.get() > 0)
                    {
                        log.info(ThreadUtil.getCurThreadName() + " 卖出一件,还剩:"
                                + (goodsAmount.decrementAndGet()));
                        try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}
                    }
                }
                log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
        }
    }

    //商场商品类(target销售线程的目标类),一个商品最多销售4次,可以多人销售
    static class MallGoods implements Runnable
    {
        //多人销售可能导致数据出错,使用原子数据类型保障数据安全
        private AtomicInteger goodsAmount = new AtomicInteger(MAX_AMOUNT);

        public void run()
        {
            synchronized (this){
                for (int i = 0; i <= MAX_AMOUNT; i++)
                {
                    if (this.goodsAmount.get() > 0)
                    {
                        log.info(ThreadUtil.getCurThreadName() + " 卖出一件,还剩:"
                                + (goodsAmount.decrementAndGet()));
                        try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}
                    }
                }
                log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
            }
        }
    }

    public static void main(String args[]) throws InterruptedException
    {
        log.info("商店版本的销售");
        //多个线程操纵多个资源类
        for (int i = 1; i <= 2; i++)
        {
            Thread thread = null;
            thread = new StoreGoods("店员-" + i);
            thread.start();
        }

        Thread.sleep(1000);
        log.info("商场版本的销售");
        //创建了一个公有的对象 多个线程操纵一个资源类
        MallGoods mallGoods = new MallGoods();
        for (int i = 1; i <= 2; i++)
        {
            Thread thread = null;
            thread = new Thread(mallGoods, "商场销售员-" + i);
            thread.start();
        }
        log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
    }
}
  • 输出结果:
[main] INFO createThread.SalesDemo - 商店版本的销售
[店员-1] INFO createThread.SalesDemo - 店员-1 卖出一件,还剩:4
[店员-2] INFO createThread.SalesDemo - 店员-2 卖出一件,还剩:4
[店员-1] INFO createThread.SalesDemo - 店员-1 卖出一件,还剩:3
[店员-2] INFO createThread.SalesDemo - 店员-2 卖出一件,还剩:3
[店员-1] INFO createThread.SalesDemo - 店员-1 卖出一件,还剩:2
[店员-2] INFO createThread.SalesDemo - 店员-2 卖出一件,还剩:2
[店员-2] INFO createThread.SalesDemo - 店员-2 卖出一件,还剩:1
[店员-1] INFO createThread.SalesDemo - 店员-1 卖出一件,还剩:1
[店员-2] INFO createThread.SalesDemo - 店员-2 卖出一件,还剩:0
[店员-1] INFO createThread.SalesDemo - 店员-1 卖出一件,还剩:0
[店员-2] INFO createThread.SalesDemo - 店员-2 运行结束.
[店员-1] INFO createThread.SalesDemo - 店员-1 运行结束.
[main] INFO createThread.SalesDemo - 商场版本的销售
[main] INFO createThread.SalesDemo - main 运行结束.
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 卖出一件,还剩:4
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 卖出一件,还剩:3
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 卖出一件,还剩:2
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 卖出一件,还剩:1
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 卖出一件,还剩:0
[商场销售员-1] INFO createThread.SalesDemo - 商场销售员-1 运行结束.
[商场销售员-2] INFO createThread.SalesDemo - 商场销售员-2 运行结束.

通过对比可以看出:

  1. 通过继承Thread类实现多线程能更好地做到多个线程并发地完成各自的任务,访问各自的数据资源。
  2. 通过实现Runnable接口实现多线程能更好地做到多个线程并发地完成同一个任务,访问同一份数据资源。多个线程的代码逻辑可以方便地访问和处理同一个共享数据资源(如例子中的MallGoods.goodsAmount),这样可以将线程逻辑和业务数据进行有效的分离,更好地体现了面向对象的设计思想。
  3. 通过实现Runnable接口实现多线程时,如果数据资源存在多线程共享的情况,那么数据共享资源需要使用原子类型(而不是普通数据类型),或者需要进行线程的同步控制,以保证对共享数据操作时不会出现线程安全问题。总之,在大多数情况下,偏向于通过实现Runnable接口来实现线程执行目标类,这样能使代码更加简洁明了。后面介绍线程池的时候会讲到,异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交,所以更多的做法是,通过实现Runnable接口创建异步执行任务,而不是继承Thread去创建异步执行任务。

线程创建的第三种方式:使用Callable和FutureTask创建线程

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

通过实现callable接口使用FutureTask类创建线程

  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 createThread;

import lombok.extern.slf4j.Slf4j;
import utils.ThreadUtil;

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

/**
 * @Author: long
 * @Date: 2022/10/28 23:23
 */
@Slf4j
public class CreateDemo3 {
    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;

    //①创建一个 Callable 接口的实现类
    static class ReturnableTask implements Callable<Long> {
        //②编写好异步执行的具体逻辑,可以有返回值
        public Long call() throws Exception{
            long startTime = System.currentTimeMillis();
            log.info(ThreadUtil.getCurThreadName() + " 线程运行开始.");
            Thread.sleep(1000);

            for (int i = 0; i < COMPUTE_TIMES; i++) {
                int j = i * 10000;
            }
            long used = System.currentTimeMillis() - startTime;
            log.info(ThreadUtil.getCurThreadName() + " 线程运行结束.");
            return used;
        }
    }

    public static void main(String args[]) throws InterruptedException {
        ReturnableTask task=new ReturnableTask();//③
        FutureTask<Long> futureTask = new FutureTask<Long>(task);//④
        Thread thread = new Thread(futureTask, "returnableThread");//⑤
        thread.start();//⑥
        Thread.sleep(500)
        log.info(ThreadUtil.getCurThreadName() + " 做一点自己的事情.");
        for (int i = 0; i < COMPUTE_TIMES / 2; i++) {
            int j = i * 10000;
        }

        log.info(ThreadUtil.getCurThreadName() + " 获取并发任务的执行结果.");
        try {
            log.info(thread.getName()+"线程占用时间:"+ futureTask.get());//⑦
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        log.info(ThreadUtil.getCurThreadName() + " 运行结束.");
    }
}

注意:futureTask的结果outcome为空,callable.call()还没有执行完。在这种情况下,main线程作为结果获取线程会被阻塞住,一直阻塞到callable.call()执行完成。当执行完后,最终结果会保存到outcome中,futureTask会唤醒main线程,去提取注意:callable.call()执行结果

线程创建方法四:通过线程池创建线程

线程池创建线程的好处在于使线程实例在执行完成后不会立即销毁,而是使用池化技术保存起来进行复用,节省了频繁创建线程实例的时间成本。java中提供了一个静态工厂来创建不同的线程池,该静态工厂为Executors工厂类。

package createThread;

import lombok.extern.slf4j.Slf4j;
import utils.ThreadUtil;

import java.util.concurrent.*;

/**
 * @Author: long
 * @Date: 2022/10/28 23:54
 */
// 省略import
@Slf4j
public class CreateDemo4
{

    public static final int MAX_TURN = 5;
    public static final int COMPUTE_TIMES = 100000000;

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

    static class DemoThread implements Runnable {
        @Override
        public void run() {
            for (int j = 1; j < MAX_TURN; j++)
            {
                log.info(ThreadUtil.getCurThreadName() + ", 轮次:" + j);
                try{TimeUnit.MILLISECONDS.sleep(10);}catch(InterruptedException e){e.printStackTrace();};
            }
        }
    }


    static class ReturnableTask implements Callable<Long>
    {
        //返回并发执行的时间
        public Long call() throws Exception
        {
            long startTime = System.currentTimeMillis();
            log.info(ThreadUtil.getCurThreadName() + " 线程运行开始.");
            for (int j = 1; j < MAX_TURN; j++)
            {
                log.info(ThreadUtil.getCurThreadName() + ", 轮次:" + j);
                try{TimeUnit.SECONDS.sleep(10);}catch(InterruptedException e){e.printStackTrace();};
            }
            long used = System.currentTimeMillis() - startTime;
            log.info(ThreadUtil.getCurThreadName() + " 线程运行结束.");
            return used;
        }
    }

    public static void main(String[] args) {

        pool.execute(new DemoThread()); //执行线程实例,无返回
        pool.execute(new Runnable()
        {
            @Override
            public void run()
            {
                for (int j = 1; j < MAX_TURN; j++)
                {
                    log.info(ThreadUtil.getCurThreadName() + ", 轮次:" + j);
                    try{TimeUnit.MILLISECONDS.sleep(10);}catch(InterruptedException e){e.printStackTrace();};
                }
            }
        });
        //提交Callable 执行目标实例,有返回
        Future future = pool.submit(new ReturnableTask());
        Long result = null;
        try {
            result = (Long) future.get();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
        log.info("异步任务的执行结果为:" + result);
        try{TimeUnit.SECONDS.sleep(Integer.MAX_VALUE);}catch(InterruptedException e){e.printStackTrace();};
    }
}

注意:实际生产环境用newThreadPool来创建线程池,禁止使用Executors.newFixedThreadPool()创建