当前位置: 首页 > article >正文

javaEE-多线程编程-3

目录

java 常见的包 :

回调函数:

什么是线程:

第一个线程:

验证多线程执行:

内核:

调用sleep()方法:

执行结果分析:

线程创建的几种方式:

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

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

3.继承Thread类,重写run()方法.但使用匿名内部类

4.实现Runnable接口,重写run()方法,但使用匿名内部类:

5.使用lambda表达式(推荐)

Thread的构造方法

Thread的常用的属性:

1.ID:jvm自动分配的身份标识,不同线程不会重复

2.名称是调试的时候用到的

3.状态:

4.优先级:

5.是否后台线程:

6.是否存活:


在写代码的时候,可以用多进程编程,也可以用多线程编程.

多进程编在java中不太推荐,因为与多进程编程相关的api,在java标准库中都没有提供.

系统提供了多线程编程的api,在java标准库中,把这些api都封装了,在代码中可以直接使用.

并且在面对频繁创建和销毁进程的时候,多线程编程具有非常大的优势.效率非常高.

java 常见的包 :

1. java.lang:系统常用基础类(String、Object),此包从JDK1.1后自动导入。

2. java.lang.reflect:java 反射编程包;

3. java.net:进行网络编程开发包。

4. java.sql:进行数据库开发的支持包。

5. java.util:是java提供的工具程序包。(集合类等) 非常重要

6. java.io:I/O编程开发包

Thread类就是java.long包下的类,不需要引入包就能直接使用.

一个.java文件中,只能有一个类被public修饰,若该类没有被public修饰,就只能在该包中,被别的类引用.

一个进程至少会有一个线程,该进程的第一个线程就是main线程,也就是主线程的入口方法.

回调函数:

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

简单来说:回调函数就是把一段代码,向传参一样,传递给其它代码,这段代码会在某个时刻被调用执行.这就叫做回调.

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。如果代码立即被执行就称为 同步回调,如果过后再执行,则称之为 异步回调。

什么是线程:

⼀个线程就是⼀个"执⾏流".每个线程之间都可以按照顺序执⾏⾃⼰的代码.多个线程之间"同时"执⾏ 着多份代码

第一个线程:

创建一个Thread01类,让该类继承Thread类,重写Thread类的run()方法,

在主函数中实例化该类,调用该类的start()方法,这样,不需要我们手动调用run()方法,待线程创建好后,会在合适的时机被jvm自动调用.(这种风格的函数,就被称为"回调函数"callback)

调⽤start()⽅法,才真的在操作系统的底层创建出⼀个线程.

package Thread_;

class MyThread01 extends Thread{
    @Override
    public void run() {//重写run()方法
        //run()方法就是该线程的入口
        while (true){
            System.out.println("Thread run");
        }
    }
}
public class Thread01 {
    public static void main(String[] args) {
        Thread thread01 = new MyThread01();
        thread01.start();//必须要调用start()方法,才能开启线程
        while(true){
        System.out.println("main ");
        }
    }
}

当引入多线程之后,代码中就可以同时具备多个执行流了.

验证多线程执行:

可以通过jdk/bin/jconsloe.exe文件看进程状态.

先运行自己写的代码,让后进行下面的操作

内核:

操作系统的内核是操作系统最核心的功能模块.(管理硬件,给软件提供稳定的运行环境)

简单来说:内核态是非常重要的,不允许用户修改的,划分出用户态和内核态也是目的是为了稳定,以防自己的应用程序把硬件设备或软件资源给搞坏了.

操作系统=内核+应用程序.

一旦开始执行,线程就会飞快的循环起来,使cpu占用率较高,进一步提高电脑的功耗,为了不让循环跑的那么快,可以在循环体中设置Thread类的sleep()方法,让每隔一定时间运行一次,这样就能降低电脑的功耗.

调用sleep()方法:

sleep()方法调用时,会有受查异常,需要手动抛出.

Mythread类中的run()方法内的异常抛出只能以try-catch的形式,不能以throws的形式,因为run()方法是重写父类的方法,父类没有throws这个异常,若加上throws,就修改了方法签名,因此,子类重写run()方法的时候,也就只能以try-catch的形式抛出异常了.

以try-catch的形式抛出异常:

以throws的形式抛出异常:

时间转换单位:

1s=1000ms(毫秒)

1ms=1000(微妙)

1um=1000nm(纳秒)

上面的第一个线程代码,可以看出,main()方法中有一个死循环,在Thread01类的run()方法中也有一个死循环,一般来说,一个代码中出现两个死循环,只会运行一个,但实际上这两个死循环中的代码都被运行了,这就说明是两个线程在同时执行.

执行结果分析:

从执行的结果上可以看出,thread和main是随机交替出现的,也就是说,每隔一秒钟,thread和main被执行一次,但是谁先被执行,是无法确定的.

每次最开始打印的结果,可以看到:都是main第一次被打印,这是因为在创建MyThrow01线程的时候,虽然该线程不是第一个线程,还是会有一定开销的,这个开销比创建进程要低很多(但也不是没有),就是这一点点的开销,使得main线程每次在最开始执行的时候强到了先机.

(注意:main线程是第一线程)

线程创建的几种方式:

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

也就是上面的这种方式.

package Thread_;

class MyThread01 extends Thread{
    @Override
    public void run() {
        //run()方法就是该线程的入口
        while (true){
            System.out.println("Thread run");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Thread01 {
    public static void main(String[] args) {
        Thread thread01 = new MyThread01();
        thread01.start();//必须要调用start()方法,才能开启线程
        while(true){
        System.out.println("main ");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

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

Runnable接口需要搭配Thread类使用,才能真正在系统中创建出线程来
package Thread_;

class MyThread02 implements Runnable{
    @Override
    public void run() {
        while(true){
            System.out.println("Thread01 run()");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Thread02 {
    public static void main(String[] args) {
        MyThread02 runnable = new MyThread02();
        Thread thread1 = new Thread(runnable);//Runnable接口需要搭配Thread类使用,才能真正在系统中创建出线程来
        thread1.start();
        while(true){
            System.out.println("main()");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

    }
}

3.继承Thread类,重写run()方法.但使用匿名内部类

Thread t=new Thread(){//这里写的{}表示要定义一个类,并且这个新类继承Thread,
                        //且没有名字,用一次就不能再用了
                       //{}中可以定义新类的属性和方法
                        //此处的目的就是重写父类Thread的run()方法.
    @Override
    public void run() {
        while(true){
            System.out.println("Thread run03");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
};

这里的thread变量名指向的是新创建的Thread的匿名内部类(子类),而不是Thread类.

4.实现Runnable接口,重写run()方法,但使用匿名内部类:

Thread构造方法的参数,传了Runnable的匿名内部类的实例.

5.使用lambda表达式(推荐)

lambda表达式原理:

Thread thread=new Thread(()->{    //这里的()是形参列表,在这里,不需要传参数
                                 //()的前面应该有个函数名,这里是匿名的,所以没有名字
                                //->后面的{}是方法体,
    while(true){
        System.out.println("Thread run5");
        try{
        Thread.sleep(1000);
        }catch(InterruptedException o){
             throw new RuntimeException(e);
        }
    }
});

这里的规则是方法不能脱离类单独存在.

这几种方法都是等价的,作用都相同.

Thread的构造方法

1.构造方法:

前两个在前面创建线程的时候都提到了,

第三,四个:给线程起个名字:

在jcolsole.exe中就能看到线程名字被修改

自己创建的线程默认是按照Thread-0 1 2 ...创建的,线程之间的名字可以重复,起名字是为了方便调试.

Thread的常用的属性:

1.ID:jvm自动分配的身份标识,不同线程不会重复

2.名称是调试的时候用到的

3.状态:

线程有不同的状态:就绪状态,阻塞状态

4.优先级:

优先级⾼的线程理论上来说更容易被调度到,线程的优先级在java中,效果不是很明显

5.是否后台线程:

和后台线程相对应的是前台线程,当一个进程开始运行的时候,只有前台线程都运行结束,后台线程才会结束.

JVM会在⼀个进程的所有⾮后台线程结束后,才会结束运⾏。

目前代码创建的线程都是前台线程,都会阻止线程的结束,当全部都执行完了,才会结束整个进程.

设置线程为后台线程:在start之前setDaemon(true);此时,该线程就为后台线程

在start之前,给thread线程设置成了后台线程,在main函数之后,休眠2秒,thread线程就执行了2秒,打印了两遍,main线程结束了,thread线程为后台线程,也就结束了.

6.是否存活:

表示内核中的线程是否还存活,在start之前,还没有开始线程,isAlive为false,

只有调用了start,线程才开始执行,isAive才为true.

2s之后,run()已经结束,isAlive为false.


http://www.kler.cn/a/448267.html

相关文章:

  • LabVIEW实现WiFi通信
  • 如何在window 使用 conda 环境下载大模型
  • [JAVA备忘录] Lambda 表达式简单介绍
  • 【AI知识】为什么激活值过大/过小,初始权重过大/过小,可能导致梯度爆炸/消失?具体例子举例
  • 如何优雅的关闭GoWeb服务器
  • SQL进阶技巧:如何计算商品需求与到货队列表进出计划?
  • EdgeX Core Service 核心服务之 Core Command 命令
  • Redis梳理
  • 计算机视觉目标检测——DETR(End-to-End Object Detection with Transformers)
  • 自然语言处理学什么
  • 前端开放性技术面试—面试题
  • 虚拟世界中的社交互动:Facebook在元宇宙中的发展路径
  • CNN分类-卷积神经网络(Convolutional Neural Network)
  • 【C语言】倒序输出
  • uniapp input的触发事件
  • robots协议
  • 使用Python和OpenCV进行双目摄像头测距的详细教程及源代码
  • Hive SQL 查询所有函数
  • setTimeout 最小执行时间是多少
  • java.util.ConcurrentModificationException异常出现的原因及解决方法
  • 【python实现烟花】
  • 解锁 SSM 与 Vue 在新锐台球厅管理系统设计与实现中的融合密码
  • 【大语言模型】ACL2024论文-29 答案即所需:通过回答问题实现指令跟随的文本嵌入
  • 【多维DP】【准NOI难度】力扣3251. 单调数组对的数目 II
  • 爬虫代码中如何处理异常?
  • 【面试 - 遇到的问题】Vue 里 router-view 使用 key + 关闭页面后重新打开页面-获取的数据赋值到旧组件问题(钩子执行顺序)