【多线程编程】创建线程的几种方式 面试题

news2024/9/20 22:34:24

创建线程的几种方法

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

 

1.继承Thread类,重写run方法。

//创建一个线程  并发编程   (默认)并发 = 并行 + 并发
class MyThread extends Thread {
    @Override
    public void run() {      //方法重写  线程的入口方法
        while(true) {
            System.out.println("========");
            try {
                //使用静态方法sleep 受查异常 需要抛异常 抛异常有两种 throws 和 try..catch
                //由于这里是重写方法 重写的方法方法名等都要相同 被重写的方法没有throws 故不能用throws  只能用 try..catch
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class Demo1 {
    //main 也是一个线程 由JVM自动创建
    public static void main1(String[] args) {
        Thread thread = new MyThread();
        //start 和 run 都是 Thread 的成员
        //run 只是描述了线程的入口 线程要做什么
        //start 是调用了系统API,在系统中创建线程,让线程再调用 run
        thread.start();
        //thread.run();  //这样调用会先执行调用run方法的线程,此线程执行完后,再执行其他线程
    }


    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();
        thread.start();
        while(true) {
            System.out.println("********");
            Thread.sleep(1000);
        }

    }

}

 

  2. 实现Runnable接口,重写run方法。

class MtThread implements Runnable {  //实现Runnable接口

    @Override
    public void run() {
        while (true) {
            System.out.println("&&&&&&&&&&&&&&");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

public class Demo02 {
    public static void main(String[] args) throws InterruptedException {
        //使用 Runnable 的写法 和 直接继承 Thread 之间的区别主要就是【解耦合】
        Runnable runnable = new MtThread();     
        Thread thread = new Thread(runnable);
        thread.start();
        while (true) {
            System.out.println("=========");
            thread.sleep(1000);
        }

    }

}

使用实现 Runnable 的写法 和 直接继承 Thread 之间的区别?

      主要区别就是解耦合,解耦合,即就是 实现Runnable 会把任务存在runnable的run方法中,如果要执行此任务,再创建线程进行调用,这样,任务就不会单独属于某个进程,而是创建的进程都可以去调用。实现了任务与线程的分离,即实现Runnable的写法会解耦合。

3.Thread+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo03 {
    public static void main(String[] args) {
        //匿名内部类
        Thread thread = new Thread() {
            @Override
            public void run() {

                while (true) {
                    System.out.println("=======");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }

        };
        

        //启动线程
        thread.start();
        while (true) {
            System.out.println("++++++++++++");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

4.Runnable+匿名内部类,重写run方法。

//利用匿名内部类
public class Demo04 {
    public static void main1(String[] args) {
        //第一种
        Runnable runnable = new Runnable() {

            @Override
            public void run() {
                while (true) {
                    System.out.println("======");
                }
            }

        };
        Thread thread = new Thread();
        thread.start();

        while (true) {
            System.out.println("*********");
        }
    }

    public static void main2(String[] args) {
        //第二种
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("???????");
                }
            }
         });
        thread.start();
    }
}

5.Thread+lambda表达式,不用重写。

 public static void main(String[] args) {
        Thread thread = new Thread( () -> {

            while (true) {
                System.out.println("=========");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

        },"这是新线程");    //给线程起名字

        //设置 thread 为后台进程 改为后台进程后 主线程飞快的执行完了,thread 还没来得及执行整个 
          进程就完了
        //前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 
          束与否,整个进程都得结束 
        //thread.setDaemon(true); //设置 thread 为后台进程
 
        thread.start();   //启动线程
        System.out.println(thread.isAlive());  //线程是否存活
    }

前台进程 后台进程概念

前台进程 会影响进程结束  而后台进程则不会即当前台进程若是早于后台进程结束,无论后台结 
束与否,整个进程都得结束。

在java中可通过调用方法 setDaemon(true) 来将线程设置为后台进程。

关于后台进程的要点:JVM会在一个进程的所有非后台进程都执行结束完后,才会结束运行。即后台进程完不完并不关心。只关心所有非后台进程完没完。

Thread的几个常见属性

属性获取方法说明
ID

getId()

线程的唯一标识 是Java分配给线程的,不是系统 API 提供的。

名称

getName()线程名
状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()简单理解为run方法是否运行结束
是否被中断isInterrupted()标志位

面试题:在JAVA中创建线程都有哪些方式?

  1. 继承Thread类,重写run方法。
  2. 实现Runnable接口,重写run方法 。
  3. Thread+匿名内部类,重写run方法。
  4. Runnable+匿名内部类,重写run方法。
  5. Thread+lambda表达式,不用重写。

lamda表达式不用重写的原因及什么是变量捕获?

lambda本质上是一个匿名函数,并且使用lambda表达式的前提是接口为函数式接口,何为函数式接口,即接口中只包含唯一一个抽象方法

而Runnable接口就是函数式接口,只包含一个抽象方法run(),圆括号对应run()方法的参数列表,为空则就为空。箭头后面的一对花括号对应run()方法的方法体。

这就是与匿名内部类的区别:匿名内部类可用于接口中有多个抽象方法的情况

面试题:start方法和run方法的区别?

  •  start方法是启动线程的,在方法内部,会调用系统API,并在系统内核中创建出线程。
  • run方法是线程的入口方法,即告诉线程你要干啥,要执行啥内容。
  • 本质区别是是否在系统内部创建出新的线程。

 

标志位

  • 额外用一个成员变量设置
private  static boolean isQuit = false;   //标志位
    public static void main1(String[] args) throws InterruptedException {
        Thread thread = new Thread( () -> {   //lambda 表达式是不需要写 run 方法的 , 自身本就是 run
            while(!isQuit) {
                System.out.println("线程正在工作");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程运行结束");

        });
        thread.start();
        Thread.sleep(5000);
        isQuit = true;

    }

若将成员变量设置为局部变量时的问题 

 

 其实将红色方框的代码去掉就好了,但是我们设置标志位的目的就是让 isQuit 在线程执行一段时间后从 false 改为 true 。从而使线程结束,但如果去掉变量就没用了,达不到目的了。返回到问题,即为什么用局部变量就不行呢?原因是与lambda表达式的变量捕获有关

所谓变量捕获,匿名内部类有,lambda也有,即就是访问所在方法或代码块中的局部变量,这个过程被称为"变量捕获"。并且这个局部变量得是有效的最终变量。有效的最终变量不一定是非得用 final 修饰,而是指一个在生命周期中没有被修改过的值。而在上面代码中局部变量 isQuit 在红框框中被修改的话就不符合有效的最终变量。

匿名内部类的变量捕获与lambda表达式的变量捕获的区别?

lambda表达式可以捕获外面的this,而匿名内部类无法直接捕获外面的this。

上述标志位不够好。
因为需要手动创建变量。
并且当线程内部在sleep的时候,主线程修改变量,新线程内部不能及时响应。

下面用另一种方法:Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束。

  • 用Thread的属性方法interrupt()进行标志
public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread( () -> {

            //Thread类内部,有一个现成的标志位,可以用来判定当前的循环是否要结束
            //isInterrupted() 判断标志位
            while (!Thread.currentThread().isInterrupted()) {   
                System.out.println("==========");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                    // 1. 若不加break 程序抛出异常后 线程仍然继续正常执行
                    // 2. 加上break ,程序抛出异常后线程立即结束
                    // 3. 做一些其他工作收尾工作 完成之后再break
                    // 其他工作的代码放到这里
                    break;
                }
            }

        });

        //启动线程
        thread.start();

        //5s后线程往下执行
        Thread.sleep(5000);

        //设置标志位 即让线程中断停止
        thread.interrupt(); 
 }

代码中thread.interrupt();这个操作,就是把Thread对象内部的标志位设置为true了。即使线程内部还在sleep,也是可以通过这个方法立即唤醒的。这就是使用这个方法比额外使用一个成员变量充当标志位的优点,及时。并且会抛出InterruptedException异常。

有个注意点是; 一定要在 try... catch 的 catch 里加 break;

如果不加break,程序抛出异常后线程仍然继续正常执行。原因是interrupt()唤醒线程之后,sleep()方法抛出异常,同时会自动清除刚才设置的标志位。while() 循环又再次可以执行。

加了break之后,程序抛出异常后线程立即结束。

 

线程等待 join

无参join()  谁调用,等待谁,直至结束为止 

带参join(1000)  单位是毫秒,等待一秒,但这里会有一个调度开销,等待完成后只是立即进入就绪状态,并不一定立即就执行。

进程,线程的状态

对进程来说,最核心的状态就是就绪状态和阻塞状态,对线程也是。

而在Java中,还赋予了线程一些其他的状态。

状态说明
NEWThread对象已经创建但start方法还没调用时
TERMINATEDThread对象还在,内核中的线程不存在了
RUNNABLE就绪状态(线程已经上CPU执行 / 线程正等待上CPU)
TIMED_WAITING阻塞:由于sleep这种固定时间的方式产生的阻塞
WAITING阻塞:由于wait这种不固定时间的方式产生的阻塞
BLOCKED阻塞:由于锁竞争导致的阻塞

通过这些方法,若在编程中遇到线程卡死等问题时就可以通过这些状态来确定原因。

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

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

相关文章

windows server 2012 服务器打开系统远程功能

服务器上开启远程功能 进入服务器,选择“添加角色和功能” 需要选择安装的服务器类型,如图所示 然后在服务器池中选择你需要使用的服务器。 选择完成后,在图示列表下勾选“远程桌面服务” 再选择需要安装的功能和角色服务。 选择完成确认内容…

学网络安全真的没有出路吗?

在当前的数字化时代,无论是个人,企业,还是国家,都会面临严重的网络安全威胁。网络安全不仅涉及我们的日常生活,也涉及到社会的稳定和国家的安全。这就需要我们高度重视网络安全,强化个人信息保护&#xff0…

web安全详解(渗透测试基础)

** 文章目录 一、Web基础知识 1.http协议2.网络三种架构及特点3. Web应用的特点4.URL组成6.Http协议的性质7.请求响应报文的格式8.请求方法9.http缓存10.缓存新鲜度如何判断11.Http重定向原理以及状态码12.HTTPS协议 数字证书13.HTTPS协议与HTTP协议的区别?14. We…

LVGL_基础控件Button

LVGL_基础控件Button 1、创建按键 /* 创建一个btn部件(对象) */lv_obj_t * btn lv_btn_create(lv_scr_act()); // 创建一个btn部件(对象),他的父对象是活动屏幕对象2、修改样式 // 修改按钮部件(对象)矩形背景部分的样式(按下的时候背…

GICI-LIB源码阅读(一)程序简介、编译调试、配置文件、车载数据集

原始 Markdown文档、Visio流程图、XMind思维导图见:https://github.com/LiZhengXiao99/Navigation-Learning 文章目录 一、GICI-LIB 简介1、程序概述2、资源获取3、功能简介4、代码分析5、第三方库6、manual7、程序执行流程图8、定位模式1. GNSS2. GNSS INS3. GNSS …

计算机网络 快速了解网络层次、常用协议、常见物理设备。 掌握程序员必备网络基础知识!!!

文章目录 0 引言1 基础知识的定义1.1 计算机网络层次1.2 网络供应商1.3 猫、路由器、交换机1.4 IP协议1.5 TCP、UDP协议1.6 HTTP、HTTPS、FTP协议1.7 Web、Web浏览器、Web服务器 2 总结 0 引言 在学习的过程中总是会对IP、TCP、UDP、HTTP、HTTPS、FTP这些常见的协议不熟悉&…

目前制造企业生产计划现状是什么?有没有自动化排产系统?

大家都知道,人的指挥中心是大脑,大脑对我们的发出各种各样的指令,告诉我们:“手”做什么事情,“眼睛”看什么地方,“耳朵”听什么声音,然后再将摸到的、看到的、听到的信息传递给大脑&#xff0…

DataFrame入门

文章目录 1. 数据集合加载2. 使用常用的属性/方法查看数据情况type()shapecolumnsdtypesinfo() 3. 查看部分数据获取一列数据获取多列数据按行加载数据同时取出行列数据切片语法 4. 简单数据分析5. 数据可视化总结 1. 数据集合加载 pd.read_csv()方法不仅可以加载CSV文件&…

【DFIR】蘇小沐的微信公众号

【DFIR】蘇小沐的微信公众号 最近整理排版微信公众号【DFIR】,欢迎关注哟!名称【DFIR】表示【数字取证和事件响应】之意!—【蘇小沐】

哈希表(Hash Table)介绍

哈希表(Hash Table)介绍 哈希表(Hash Table):也叫做散列表。是根据键值(Key Value、关键码值)直接进行访问的数据结构。 哈希表通过“键(key)”和“映射函数&#xff0…

Hive【Hive(六)窗口函数】

窗口函数(window functions) 概述 定义 窗口函数能够为每行数据划分 一个窗口,然后对窗口范围内的数据进行计算,最后将计算结果返回给该行数据。 语法 窗口函数的语法主要包括 窗口 和 函数 两个部分。其中窗口用于定义计算范围…

A*算法和Dijkstra

A*算法 A*算法 个人理解FGH,F是总距离,G是已经走过的距离,F是暂未走过的距离,通过不断探索领进路径直至所有路径都到达终点,然后反向去确定最短路! A*算法是静态路网中寻找最短路的最有效算法&#xff…

网络安全工程师考证指南,不看就亏了!!

目前网络安全行业,国内都有哪些证书可以考? 一、CISP-PTE (国家注册渗透测试工程师) CISP-PTE即注册信息安全渗透测试工程师,该证书由中国信息安全测评中心颁发,证书是国内唯一认可的渗透测试认证&#x…

GO 中优雅编码和降低圈复杂度

本次主要是聊聊关于使用接口抽象和降低圈复杂度的方式 工作中,难免会遇到老项目老代码,不仅仅需要我们维护,可能还需要我们在原来的垃圾代码上进行新增功能或者是进行优化调整 例如 现有的老代码中关于用户系统这一块就已经经是摇摇欲坠&a…

新版精仿今日头条新闻网站源码/搭建教程+自动采集接口+采集更新文章【新闻站搭建源码】

精仿今日头条新闻网站源码,这个是新闻站搭建源码,含有搭建教程,也可以自动采集接口采集更新文章。源码亲测可用,mysql5.7、PHP7.3支持页面自适应,里面带有详细安装搭建教程。 功能特点: 1、这个网站可以自…

【C++】多线程的学习笔记(2)——白话文版(bushi

目录 前一篇 本章内容提要 使用mutex锁的原因 mutex锁的概念 mutex的使用教程 锁的声明以及命名 mutex的加锁以及解锁 例子 结果 注意 mutex的其他方式的锁介绍 lock_guard 介绍 例子 运行结果 adopt_lock参数 unique_lock 介绍 try_to_lock defer_lock re…

fic2023(完结,AIRDROP放弃)

AIRDROP 1.请分析苹果手机导出日志,airdrop所使用的扫描模式(Scanning mode)为?? 直接搜索可得Scanning mode Contacts Only 2. AirDrop服务计划监听端口号是多少? 8770 3.AirDrop中接收到图片的识别码(identifier)是多少?(标准格式:12345678-1234-5678-1234-5678…

工厂生产线管理所需的系统介绍

工厂生产线管理对于提高生产效率、优化资源利用和确保产品质量至关重要。在现代化工厂中,采用各种系统来支持生产线的管理和运营。本文将全方位介绍工厂生产线管理所需的系统,包括生产计划系统、设备监控系统、质量管理系统和数据分析系统。 一、生产计划…

Adobe_InDesign_2023_18.4.0.056图文安装教程及下载

Adobe InDesign是Adobe公司的一个桌面出版 (DTP)应用程序,简称“Id”,主要用于各种印刷品的排版编辑。InDesign是一款功能强大的出版物创作、排版和打印软件,可以帮助出版物和广告创作者提高效率,节省时间,改善印刷质量。InDesign…

大模型部署手记(6)通义千问+Jetson AGX Orin

1.简介 组织机构:阿里 代码仓:https://github.com/QwenLM/Qwen 模型:Qwen/Qwen-7B-Chat-Int4 下载:http://huggingface.co/Qwen/Qwen-7B-Chat-Int4 modelscope下载:https://modelscope.cn/models/qwen/Qwen-7B-Ch…