Java创建线程的四种方式和线程的生命周期(面试题彻底搞懂)

news2025/2/12 3:44:32

方式一:继承Thread类的方式:

    1. 创建一个继承于Thread类的子类
    1. 重写Thread类的run() --> 将此线程执行的操作声明在run()中
    1. 创建Thread类的子类的对象
    1. 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()

说明两个问题:
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。否则就成了普通调用方法
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().

 //创建Thread类的匿名子类的方式  线程对象只使用一次
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    if(i % 2 == 0){
                        System.out.println(Thread.currentThread().getName() + ":" + i);

                    }
                }
            }
        }.start();

//1. 创建一个继承于Thread类的子类
 * 例子:遍历100以内的所有的偶数
class MyThread extends Thread {
    //2. 重写Thread类的run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}


public class ThreadTest {
    public static void main(String[] args) {
        //3. 创建Thread类的子类的对象
        MyThread t1 = new MyThread();

        //4.通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
        t1.start();
        //问题一:我们不能通过直接调用run()的方式启动线程。
//        t1.run();

        //问题二:再启动一个线程,遍历100以内的偶数。不可以还让已经start()的线程去执行。会报IllegalThreadStateException
//        t1.start();
        //我们需要重新创建一个线程的对象
        MyThread t2 = new MyThread();
        t2.start();


        //如下操作仍然是在main线程中执行的。
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i + "***********main()************");
            }
        }
    }

}

测试Thread中的常用方法

    1. start():启动当前线程;调用当前线程的run()
    1. run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    1. currentThread():静态方法,返回执行当前代码的线程
    1. getName():获取当前线程的名字
    1. setName():设置当前线程的名字
    • yield():释放当前cpu的执行权
    • Thread.yield()方法作用是:暂停当前正在执行的线程对象(及放弃当前拥有的cup资源),
      并执行其他线程。yield()做的是让当前运行线程回到可运行(就绪)状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。
      但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
    1. join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。低优先级的线程也可以获得执行
    1. stop():已过时。当执行此方法时,强制结束当前线程。
    1. sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
    1. isAlive():判断当前线程是否存活

    线程通信:wait() / notify() / notifyAll() :此三个方法定义在Object类中的。

  • 线程的优先级:

  • MAX_PRIORITY:10

  • MIN _PRIORITY:1

  • NORM_PRIORITY:5 -->默认优先级

  • 2.如何获取和设置当前线程的优先级:

  • getPriority():获取线程的优先级

  • setPriority(int p):设置线程的优先级

  • 说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下

  • 被执行。并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行。

  • 说明
    线程创建时继承父线程的优先级
    低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

class HelloThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){

//                try {
//                    sleep(10);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }

                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

//            if(i % 20 == 0){
//                yield();释放当前cpu的执行权 
//            }

        }

    }

    public HelloThread(String name){
        super(name);
    }
}


public class ThreadMethodTest {
    public static void main(String[] args) {

        HelloThread h1 = new HelloThread("Thread:1");//通过构造器给线程命名

//        h1.setName("线程一");
        //设置分线程的优先级
        h1.setPriority(Thread.MAX_PRIORITY);

        h1.start();

        //给主线程命名
        Thread.currentThread().setName("主线程");
        Thread.currentThread().setPriority(Thread.MIN_PRIORITY);

        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() + ":" + i);
            }

//            if(i == 20){
//                try {
//                    h1.join();
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
//            }

        }

//        System.out.println(h1.isAlive());

    }
}

wait()和join()区别:
1、存在不同的java包中(最明显的区别)

wait()方法需要在java.lang.Object类中声明;而,join()方法是在java.lang.Thread类中声明。

2、使用目的不同

wait()方法用于线程间通信;而join()方法用于在多个线程之间添加排序,第二个线程需要在第一个线程执行完成后才能开始执行。

3、唤醒线程方面的区别

我们可以通过使用notify()和notifyAll()方法启动一个通过wait()方法进入等待状态的线程。但是我们不能打破join()方法所施加的等待,除非或者中断调用了连接的线程已执行完了。

4、同步上下文(最重要的区别)

wait()方法必须从同步(synchronized)的上下文调用,即同步块或方法,否则会抛出IllegalMonitorStateException异常。

但,在Java中有或没有同步的上下文,我们都可以调用join()方法。

方式二:实现Runnable接口的方式:

    1. 创建一个实现了Runnable接口的类
    1. 实现类去实现Runnable中的抽象方法:run()
    1. 创建实现类的对象
    1. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    1. 通过Thread类的对象调用start()
package atguigu.java;

/**
 *
 * 比较创建线程的两种方式。
 * 开发中:优先选择:实现Runnable接口的方式
 * 原因:1. 实现的方式没有类的单继承性的局限性
 *      2. 实现的方式更适合来处理多个线程有共享数据的情况。
 *
 * 联系:public class Thread implements Runnable
 * 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
 *
 * @author shkstart
 * @create 2019-02-13 下午 4:34
 */
//1. 创建一个实现了Runnable接口的类
class MThread implements Runnable{

    //2. 实现类去实现Runnable中的抽象方法:run()
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }

        }
    }
}


public class ThreadTest1 {
    public static void main(String[] args) {
        //3. 创建实现类的对象
        MThread mThread = new MThread();
        //4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
        Thread t1 = new Thread(mThread);
        t1.setName("线程1");
        //5. 通过Thread类的对象调用start():① 启动线程 ②调用当前线程的run()-->调用了Runnable类型的target的run()
        t1.start();

        //再启动一个线程,遍历100以内的偶数 不需要在new实现类 MThread 的对象 要再new Thread对象
        Thread t2 = new Thread(mThread);
        t2.setName("线程2");
        t2.start();
    }

}

线程的生命周期

●JDK中用Thread.State类定义了线程的几种状态

  • 要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类
    及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五.
    种状态:

➢新建:当一个Thread类 或其子类的对象被声明并创建时,新生的线程对象处于新建
状态

➢就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已
具备了运行的条件,只是没分配到CPU资源

➢运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线
程的操作和功能

➢阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中
止自己的执行,进入阻塞状态

➢死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
在这里插入图片描述
说明:
1.生命周期关注两个概念:状态、相应的方法。
2.关注:状态a–>状态b:哪些方法执行了(回调方法)
某个方法主动调用:状态a–>状态b
3.阻塞:临时状态,不可以作为最终状态
死亡:最终状态。

方式三:实现Callable接口和线程池

新增方式一:实现Callable接口。 --- JDK 5.0新增
//1.创建一个实现Callable的实现类
class NumThread implements Callable{
    //2.实现call方法,将此线程需要执行的操作声明在call()中
    @Override
    public Object call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
                sum += i;
            }
        }
        return sum;
    }
}


public class ThreadNew {
    public static void main(String[] args) {
        //3.创建Callable接口实现类的对象
        NumThread numThread = new NumThread();
        //4.将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
        FutureTask futureTask = new FutureTask(numThread);
        //5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()
        new Thread(futureTask).start();

        try {
            //6.获取Callable中call方法的返回值
            //get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值。
            Object sum = futureTask.get();
            System.out.println("总和为:" + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

}

说明:
* 如何理解实现Callable接口的方式创建多线程比实现Runnable接口创建多线程方式强大?
* 1. call()可以返回值的。
* 2. call()可以抛出异常,被外面的操作捕获,获取异常的信息
* 3. Callable是支持泛型的


Future接口

➢可以对具体Runnable、Callable任 务的执行结果进行取消、查询是
否完成、获取结果等。

FutrueTask是Futrue接口的唯一的实现类

➢FutureTask 同时实现了Runnable, Future接口。它既可以作为
Runnable被线程执行,又可以作为Future得到Callable的返回值

方式四:使用线程池


背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。

思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完
放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
●好处:
➢提高响应速度(减少了创建新线程的时间)
➢降低资源消耗(重复利用线程池中线程,不需要每次都创建)
➢便于线程管理
V corePoolSize: 核心池的大小
V maximumPoolSize: 最大线程数
VkeepAliveTime:线程没有任务时最多保持多长时间后会终止

新增方式二:使用线程池
class NumberThread implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

class NumberThread1 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i <= 100;i++){
            if(i % 2 != 0){
                System.out.println(Thread.currentThread().getName() + ": " + i);
            }
        }
    }
}

public class ThreadPool {

    public static void main(String[] args) {
        //1. 提供指定线程数量的线程池
        ExecutorService service = Executors.newFixedThreadPool(10);
        ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
        //设置线程池的属性
//        System.out.println(service.getClass());
//        service1.setCorePoolSize(15);
//        service1.setKeepAliveTime();


        //2.执行指定的线程的操作。需要提供实现Runnable接口或Callable接口实现类的对象
        service.execute(new NumberThread());//适合适用于Runnable
        service.execute(new NumberThread1());//适合适用于Runnable

//        service.submit(Callable callable);//适合使用于Callable
        //3.关闭连接池
        service.shutdown();
    }

}
说明:
* 好处:
* 1.提高响应速度(减少了创建新线程的时间)
* 2.降低资源消耗(重复利用线程池中线程,不需要每次都创建)
* 3.便于线程管理
*      corePoolSize:核心池的大小
*      maximumPoolSize:最大线程数
*      keepAliveTime:线程没任务时最多保持多长时间后会终止

面试题:Java中多线程的创建有几种方式?四种。
线程池相关API
●JDK 5.0起提供了线程池相关API: ExecutorServiceExecutorsExecutorService: 真正的线程池接口。常见子类ThreadPoolExecutorvoid execute(Runnable command):执行任务/命令,没有返回值,一般用来执行Runnable<T> Future<T> submit(Callable<T> task): 执行任务,有返回值,一般来执行Callablevoid shutdown() :关闭连接池

●Executors: 工具类、线程池的工厂类,用于创建并返回不同类型的线程池
➢Executors.newCachedThreadPool(): 创建一个可根据需要创建新线程的线程池
➢Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
➢Executors.newSingle ThreadExecutor() :创建一个只有一个线程的线程池
➢Executors.newScheduledThreadPool(n): 创建1个线程池,它可安排在给定延迟后运行命令或者定期地执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/645863.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

百度CDN配置TLS

概述 为了保障您互联网通信的安全性和数据完整性&#xff0c;百度智能云CDN提供TLS版本控制功能。您可以根据不同域名的需求&#xff0c;灵活地配置TLS协议版本。 TLS&#xff08;Transport Layer Security&#xff09;即安全传输层协议&#xff0c;在两个通信应用程序之间提…

关于Dockerfile的优化

如今各个公有镜像仓库中已经包含了成千上万的镜像文件&#xff0c;但并不是所有的镜像都是精简高效的。很多初学者刚开始都习惯使用FROM centos然后RUN 一堆yum install&#xff0c;这样还停留在虚拟机层面的使用&#xff0c;这样创建出来的镜像往往体积比较大。其实我们可以参…

Vmware 设置固定ip地址--桥接模式

前言&#xff1a; 若虚拟机没有设置固定ip地址&#xff0c;每次关机重启后都会更新ip地址。导致连接工具得跟着一起修改&#xff0c;每次修改很烦。 之前使用NAT模式&#xff0c;因为使用此模式后&#xff0c;每次打开网页都会转几秒钟后才会显示网页。所以才使用桥接模式&…

DP学习第一篇之爬楼梯

DP学习之爬楼梯 剑指 Offer II 088. 爬楼梯的最少成本 - 力扣&#xff08;LeetCode&#xff09; 1. 题目分析 可以从第0或者第1作为起始台阶、每次可以选择跳1或2步、到楼顶结束 2. 解题 a.解法一 状态表示 tips: 经验题目要求。以i位置为结尾&#xff0c;。。。 dp[i] :…

Frida技术:App逆向开发屠龙刀

Frida是一种基于JavaScript的动态分析工具,可以用于逆向开发、应用程序的安全测试、反欺诈技术等领域。Frida主要用于在已安装的应用程序上运行自己的JavaScript代码,从而进行动态分析、调试、修改等操作,能够绕过应用程序的安全措施,可以助力于对应用程序进行逆向分析。 …

OpenShift Virtualization - 从集群外部访问集群中的 VM(附视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.12 的环境中验证 文章目录 方法1&#xff1a;通过 Service 的 NodePort 访问 VM方法2&#xff1a;通过外部 IP 访问 VM确认 OpenShift 集群环境为 Worker 节点添加 Linux Bridge创建使用 Li…

大文件上传功能在标签服务的简单应用和代码实现

各位看官大家好&#xff0c;今天给大家分享的又是一篇实战文章&#xff0c;希望大家能够喜欢。 目前「袋鼠云客户数据洞察平台」标签服务的群组按种类划分&#xff0c;可以分为三大类&#xff0c;分别是实时群组、动态群组以及静态群组。如果按创建方式划分则有两种&#xff0…

6.11 有名管道和无名管道

目录 进程间通讯介绍 System V IPC 无名管道 无名管道特点 无名管道创建-pipe 无名管道通信 无名管道-示例 有名管道特点 有名管道创建-mkfifo 有名管道读写-示例 进程间通讯介绍 无名管道&#xff08;pipe&#xff09; 有名管道 &#xff08;fifo&#xff09; 信号…

制造业供应商合作该如何协调?SRM供应商管理系统的出现改变一切

制造业是使用SRM系统频率最高的行业了&#xff0c;因为该行业需要与大量供应商合作和协调&#xff0c;以便及时获得所需的原材料和零件。同时&#xff0c;该行业生产周期长&#xff0c;需求通常较为稳定&#xff0c;需要稳定的供应链管理来确保生产效率和质量。因此&#xff0c…

写一个vscode支持vue文件跳转到定义的插件,又可以愉快地摸鱼了

1. 背景 vscode自身是支持vue文件组件跳转到定义的&#xff0c;但是支持的力度是非常弱的。我们在vue-cli的配置的下&#xff0c;可以写很多灵活的用法&#xff0c;这样可以提升我们的生产效率。但是正是这些灵活的写法&#xff0c;导致了vscode自身提供的功能无法支持跳转到文…

MySQL数据库语言二:DML、DQL

&#x1f618;作者简介&#xff1a;正在努力的99年打工人。 &#x1f44a;宣言&#xff1a;人生就是B&#xff08;birth&#xff09;和D&#xff08;death&#xff09;之间的C&#xff08;choise&#xff09;&#xff0c;做好每一个选择。 &#x1f64f;创作不易&#xff0c;动…

Linux---网络传输命令(ping、wget、curl)

1. ping命令 执行 ping 指令会使用 ICMP 传输协议&#xff0c;发出要求回应的信息&#xff0c;若远端主机的网络功能没有问题&#xff0c; 就会回应该信息&#xff0c;因而得知该主机运作正常。 语法&#xff1a;ping [参数] IP名或主机名 参数包括&#xff1a; 注意&#…

Jmeter实现Dubbo接口测试

目录 前言&#xff1a; 一、准备 二、编写我们的测试工程 三、Jmeter来测试这个工程 前言&#xff1a; JMeter可以用来测试Dubbo接口的性能和负载。Dubbo是阿里巴巴的高性能RPC框架&#xff0c;常用于分布式服务的调用。为了测试Dubbo接口&#xff0c;需要使用JMeter提供的…

Android Chrome Custom Tabs

参考文档 API文档 链接 Chrome Custom Tabs 参考文档 https://developer.chrome.com/docs/android/custom-tabs/ Chrome Custom Tabs最佳实践_customtabs_Just_Sanpark的博客-CSDN博客 Chrome Custom Tabs最佳实践_chrome custom tabs集成_wxx614817的博客-CSDN博客 Chrome…

红黑树(RBTree)c++实现

目录 红黑树介绍 红黑树的性质&#xff1a; 红黑树的结点类 搜索(红黑)树的旋转 旋转分为4种(左旋&#xff0c;右旋&#xff0c;左右双旋&#xff0c;右左双旋)&#xff1a; 左旋(RotateL) 右旋(RotateR) 左右双旋(RotateLR) 右左双旋(RotateRL) 红黑树的插入 插入结…

计算机中丢失MSVCP140.dll无法启动此程序怎么办,可以使用这个方法修复

计算机中丢失MSVCP140.dll会导致很多软件跟游戏无法启动运行&#xff0c;这个问题相信困扰着不少小伙伴&#xff0c;遇到这个问题其实不用慌&#xff0c;也无需重装系统。需要先了解清楚MSVCP140.dll文件是什么&#xff0c;已经在我们电脑系统中的作用&#xff0c;了解清楚以后…

【技术干货】数字电路电平标准

信号的逻辑电平经历了从单端信号到差分信号、从低速信号到高速信号的发展过程。最基本的单端信号逻辑电平为CMOS、TTL&#xff0c;在此基础上随着电压摆幅的降低&#xff0c;出现LVCMOS、LVTTL等逻辑电平&#xff0c;随着信号速率的提升又出现ECL、PECL、LVPECL、LVDS、CML等差…

2.7V至25V宽输入电压15A 峰值电流

HT7179是一款高功率异步升压转换器&#xff0c;集成 20mΩ功率开关管&#xff0c;为便携式系统提供高效的 小尺寸解决方案。 HT7179具有2.7V至25V宽输入电压范围&#xff0c;可为 采用单节或两节锂电池&#xff0c;或12V铅酸电池的应 用提供支持。该器件具备15A开关电流能力&a…

【Java基础学习打卡05】命令提示符

目录 引言一、命令提示符是什么二、命令提示符常用命令1.打开命令提示符2.命令演示3.常用命令 总结 引言 知道命令提示符是什么&#xff0c;熟练打开命令提示符&#xff0c;熟练使用常用命令&#xff0c;并自行尝试其他命令。本文只是对命令提示符进行简单介绍和使用。 一、命…

绝版功能回归,Win11开始向Win7进化了

如果将 Win11 上架到 Steam 提供评论打分&#xff0c;那么 Win11 会和很多有争议的游戏一样&#xff1a;褒贬不一。 许多功能是吸引人的&#xff0c;但微软总会在什么时候突然给你一击。 前有 VBS 影响性能、谜之卡顿&#xff0c;后有各种广告、自动安装微软电脑管家。 以及我…