首页 > 其他分享 >【线程基础】【一】线程的创建方式

【线程基础】【一】线程的创建方式

时间:2023-04-18 20:46:06浏览次数:38  
标签:run 方式 Thread 创建 start 线程 new

1  前言

本节开始我们来回顾下线程基础相关的东西,最近在复习所以来做一些笔记哈,这节我们来讲讲创建线程的方式。

2  创建分类

Java提供了两种线程的创建方法,第一种是继承Thread类;第二种是实现Runable接口,并将Runnable实例传递给Thread类。详细的可以参考官方文档哈:https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html

那么接下来我们就来看看这两种方式的优缺点哈,只有了解好坏我们才能在使用中悠然自得。

2.1  继承Thread类

优点 : 方便传参,可以在子类添加成员变量,通过方法设置参数或构造函数传参,也就是类就是一个线程类,对象创建出来start就完事了。

缺点:

  • 单继承的弊端,大家都懂的
  • 这种创建方式不便于线程池管理,像野孩子需要自己管理
  • 代码写法上可能相对于接口方式要麻烦
  • 无法获取线程运行结果
class PrimeThread extends Thread {
    long minPrime;
    PrimeThread(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
         . . .
    }
}
PrimeThread p = new PrimeThread(143);
p.start();

2.2  Runnable 接口

优点 : 此方式可以继承其他类。也可以使用线程池管理,节约资源。创建线程代码的耦合性较低。推荐使用此种方式创建线程。

缺点: 不方便传参,只能使用主线程中用final修饰的变量。其次是无法获取线程任务的返回结果。

// 第一种:常规写法
class PrimeRun implements Runnable {
    long minPrime;
    PrimeRun(long minPrime) {
        this.minPrime = minPrime;
    }

    public void run() {
        // compute primes larger than minPrime
         . . .
    }
}
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
// 第二种:lambda写法
new Thread(()->{}).start()

2.3  Callable 接口

此种方式创建线程底层源码也是使用实现Runnable接口的方式实现的,所以不是一种新的创建线程的方式,只是在实现Runnable接口方式创建线程的基础上,同时实现了Future接口,实现有返回值的创建线程

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 封装一个Callable任务
        Callable<String> callTask = () -> {
            System.out.println("开始执行【Callable】任务");
            TimeUnit.SECONDS.sleep(2);
            // 执行结束后返回结果值
            return "我是结果";
        };
        // 创建异步任务
        FutureTask<String> task = new FutureTask<>(callTask);
        // 启动线程
        new Thread(task).start();
        // 等待结果
        String res = task.get();
        System.out.println(res);
    }
}

3  引申问题

3.1  如果同时使用Thread和Runnable两种方式创建线程并启动,会怎样?

public class CreateThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            public void run() {
                System.out.println("通过Runnable接口创建线程");
            }
        }) {
            @Override
            public void run() {
                // 此方法会覆盖掉父类run方法,即父类run方法不再执行(java面向对象中的继承特性)
                System.out.println("通过Thread类创建线程");
            }
        }.start();
    }
}

分析:上述代码先通过Runnable方式创建一个线程,并实现接口中的run方法。然后在new Thread的方法体中再次重写Thread的run方法时,父类的run方法将不会再执行,即System.out.println("通过Runnable接口创建线程")此行代码不会被执行了,直接执行重写后的run方法System.out.println("通过Thread类创建线程")。原因是Java面向对象中继承后重写父类的方法时,该父类的方法将不再被执行,直接执行子类重写后的方法,所以结果只打印了“通过Thread类创建线程”此行输出。

3.2  线程的启动为什么不能直接调用run方法,而是调用start方法?

通过调用run()方法,是在主线程中执行任务,所以本质上并没有创建出新的线程,其实就是方法调用。
通过调用start()方法,是在主线程中创建一个子线程去执行任务,这才是创建新线程去执行。通过start()方法启动线程后,并不一定立即执行,而是由线程调度器决定何时运行,可能立刻就会运行,也可能稍后才会运行,也可能一直不运行(饥饿状态)。
两种方式都能成功运行并执行,只是直接调用run()方法,并没有使用新线程去运行任务,程序还是串行执行的,所以这种方式是不符合预期的。

3.3  如果线程连续调用两次start()方法,会怎样?

public class ThreadClass extends Thread {

    @Override
    public void run() {
        System.out.println("运行Thread线程");
    }

    public static void main(String[] args) {
        ThreadClass threadClass = new ThreadClass();
        threadClass.start();
        threadClass.start();

    }
}

原因分析:
查看start()方法源码:

public synchronized void start() {
    /**
     * This method is not invoked for the main method thread or "system"
     * group threads created/set up by the VM. Any new functionality added
     * to this method in the future may have to also be added to the VM.
     *
     * A zero status value corresponds to state "NEW".
     */
    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    /* Notify the group that this thread is about to be started
     * so that it can be added to the group's list of threads
     * and the group's unstarted count can be decremented. */
    group.add(this);
    boolean started = false;
    try {
        start0();
        started = true;
    } finally {
        try {
            if (!started) {
                group.threadStartFailed(this);
            }
        } catch (Throwable ignore) {
            /* do nothing. If start0 threw a Throwable then
              it will be passed up the call stack */
        }
    }
}

通过源码中发现,之所以抛出java.lang.IllegalThreadStateException,是因为threadStatus不为0;threadStatus为0,表示线程刚初始化完成,还没有启动。若threadStatus不为0,说明线程已经被启动过了。所以第二次调用start()方法时,线程的状态threadStatus已改变,此时会抛出异常。threadStatus是voliate修饰的保证可见性顺序性,遵循happens-before原则,所以第二次启动发现状态不对直接抛异常。

4  小结

本节我们看了下创建线程的两种方式,即继承Thread类和实现Runnable接口。其他所有创建线程的方式,底层都是使用这两种方式中的一种实现的,比如通过线程池、通过匿名类、通过lambda表达式、通过Callable<V>接口等等,全是通过这两种方式中的一种实现的。有理解不对的地方欢迎指正哈。

标签:run,方式,Thread,创建,start,线程,new
From: https://www.cnblogs.com/kukuxjx/p/17330947.html

相关文章

  • Redis 一、(简介,redis-linux下载,启动方式,常用配置,应用场景,数据结构和内部编码,字符类型)
    目录Redis一、Redis1、简介2、RedisLinux下载安装3、redis启动方式3、1.简单启动3、2.动态参数启动3、3.配置文件启动5、常用配置6、redis应用场景7、redis通用命令8、数据结构和内部编码9、redis字符串类型Redis一、Redis1、简介#Redis特性1)速度快10wops(每秒10万......
  • selenium三种等待方式 (强制等待、隐式等待、显示等待)
    ​ 方式一:强制等待time.sleep(n)#单位:秒复制代码程序表现:强制暂停程序运行,等待n秒后继续执行后续代码演示代码:time.sleep(3)driver.find_element(By.ID,"kw").send_keys("华测教育")复制代码方式二:隐式等待driver.implicitly_wait(n)#单位:秒复制代码......
  • vue03 01.创建项目
    目录01.创建项目打包工具vite介绍安装命令启动项目浏览效果代码目录打包预览运行插件使用01.创建项目打包工具vite官网vite学习视频vite介绍Vite也是前端构建工具相较于webpack,vite采用了不同的运行方式:开发时,并不对代码打包,而是直接采用ESM的方式来,而是直接采用ESM的方式来运行......
  • C语言实现回调函数标准方式
    #include<iostream>#defineOFFSET1000usingnamespace::std;intbuttonId;//定义回调函数的类型【注意,这里只是用type定义,回调函数一般是作为另一个函数的形式参数的,只注重类型,而调用的时候需要具体实现】typedefvoid(*someCallback)(int);//回调函数的具体实现v......
  • 进程上下文切换、线程上下文切换、中断上下文切换的区别
    概念​CPU上下文切换指的是CPU从一个进程或线程切换到另一个进程或线程的次数。当CPU执行一个进程或线程时,会为其建立一个执行上下文(Context),当CPU切换到另一个进程或线程时,需要保存当前的上下文并建立新的上下文,这个过程就是上下文切换。​上下文切换会......
  • window location跳转的几种方式与a链接跳转的总结
    1.window.location.href在当前页面跳转window.location.href='http://www.baidu.com'等于<ahref="http://www.baidu.com"></a>2.window.open在新页面跳转window.open('www.baidu.com')等于<ahref="http://www.baidu.com&qu......
  • Action 接受参数的3中方式(4)
    Action接受参数的3中方式1.继承ActionSupport的Action在Action中添加字段,这些字段的名字与参数的名字相同,并添加相应的setters与getters方法。packagecom.bjsxt.struts2.user.action;importcom.opensymphony.xwork2.ActionSupport;publicclassUserActionextendsActionSu......
  • 基于LINUX系统下多线程贪吃蛇小游戏
    基于Ncurse图形库的C语言小游戏。涉及到,C变量,流程控制,函数,指针,结构体等知识内容,动态链表的创建和插入,以及释放。数组的遍历,#include<stdlib.h>#include<curses.h>#defineROW20#defineCOL20#defineROW_Snake2#defineCOL_Snake2#defineUP1#defineDOWN......
  • 一个Java线程的线生(线生 vs 人生)
    java线程的使用1.Java多线程概述下面我们看下Java的多线程作者:博学谷狂野架构师GitHub:GitHub地址(有我精心准备的130本电子书PDF)只分享干货、不吹水,让我们一起加油!......
  • Qt多线程之QMutex
    QMutex同一个QMutexlock()时,其他的lock()操作要等待locked的地方unlock()(可以保证顺序执行)测试代码:#include<iostream>#include<QThread>#include<QMutex>usingnamespacestd;classmyThread1:publicQThread{public:myThread1(int&num,QMutex&......