JavaEE语法第二章之多线程(初级一)

news2024/9/25 23:24:19

一、认识线程

1.1线程的概念

一个线程就是一个 "执行流"每个线程之间都可以按照顺序执行自己的代码. 多个线程之间 "同时"执行着多份代码。

一家公司要去银行办理业务,既要进行财务转账,又要进行福利发放,还得进行缴社保。
如果只有张三一个会计就会忙不过来,耗费的时间特别长。为了让业务更快的办理好,张三又找来两位同事李四、王五一起来帮助他,三个人分别负责一个事情,分别申请一个号码进行排队,自此就有了三个执行流共同完成任务,但本质上他们都是为了办理一家公司的业务。此时,我们就把这种情况称为多线程,将一个大任务分解成不同小任务,交给不同执行流就分别排队执行。其中李四、王五都是张三叫来的,所以张三一般被称为主线程(Main Thread)。

1.2为啥要有线程

1.2.1首先, "并发编程" 成为 "刚需".

  • 单核 CPU 的发展遇到了瓶颈. 要想提高算力, 就需要多核 CPU. 而并发编程能更充分利用多核 CPU资源.
  • 有些任务场景需要 "等待 IO", 为了让等待 IO 的时间能够去做一些其他的工作, 也需要用到并发编程.

1.2.2其次, 虽然多进程也能实现 并发编程, 但是线程比进程更轻量.

  • 创建线程比创建进程更快.
  • 销毁线程比销毁进程更快.
  • 调度线程比调度进程更快.

1.2.3最后, 线程虽然比进程轻量, 但是人们还不满足, 于是又有了 "线程池"(ThreadPool) 和 "协程"(Coroutine)

1.3进程和线程的区别

  • 进程是包含线程的. 每个进程至少有一个线程存在,即主线程。
  • 进程和进程之间不共享内存空间. 同一个进程的线程之间共享同一个内存空间.

比如之前的多进程例子中,每个客户来银行办理各自的业务,但他们之间的票据肯定是不想让别人知道的,否则钱不就被其他人取走了么。而上面我们的公司业务中,张三、李四、王五虽然是不同的执行流,但因为办理的都是一家公司的业务,所以票据是共享着的。这个就是多线程和多进程的最大区别。

  • 进程是系统分配资源的最小单位,线程是系统调度的最小单位。

 1.4 Java 的线程 和 操作系统线程 的关系

线程是操作系统中的概念. 操作系统内核实现了线程这样的机制, 并且对用户层提供了一些 API 供用户使用(例如 Linux 的 pthread 库).
Java 标准库中 Thread 类可以视为是对操作系统提供的 API 进行了进一步的抽象和封装.

1.5面试题


二、创建线程

InterruptedExceptipn表示终断异常,意思是sleep睡眠过程中还没到点就被唤醒了。

2.1方法1 继承 Thread 类

class MyThread extends Thread {
//继承Thread创建一个线程
    @Override
    public void run() {
        while (true) {
            System.out.println("hello t");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo1 {
    public static void main(String[] args) {
        //创建MyThread的实例
        Thread t = new MyThread();
        // start 会创建新的线程
        t.start();
        // run 不会创建新的线程. run 是在 main 线程中执行的~~
        // t.run();

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

上述代码涉及到两个线程:

1.main方法所对应的线程(一个进程里至少得有一个线程),也可以称之为主线程

2.通过t.start创建的新的线程

(每个线程是一个独立的“执行流”)

 注意:此处的交替不是严格意义的交替,具体谁先打印是不确定的。(多个线程在CPU上执行调度的顺序是不确定的,随机的!即使存在优先级,也只是建议)

1.run叫做入口方法,这种特殊的方法可以被系统自动的调用

2.可以把run堪称别的线程的main就和正常写程序一样,想用的放在run里面让run自动调用这些逻辑。

3.run是重写了父类的方法(多态)

4.sleep是Thread的静态方法,参数单位为ms

 2.2实现 Runnable 接口

class MyRunnable implements Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello t");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

public class ThreadDemo2 {
    public static void main(String[] args) {
        MyRunnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();

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

对比上面两种方法:
继承 Thread 类, 直接使用 this 就表示当前线程对象的引用.
实现 Runnable 接口, this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

2.3第三方软件查看多线程

1.在本地jdk目录的bin目录中找到jconsole(jdk提供的工具)

 2.选择本地进程(前提是程序在运行中)

(若进程列表全空,一般是权限问题。通过管理员身份运行就可以解决)

 

堆栈线程描述了当前的代码执行到了哪里,便于后期我们找错 

2.4匿名内部类创建 Thread 子类对象

public class ThreadDemo3 {
    public static void main(String[] args) {
        Thread thread =new Thread(){
            @Override
            public void run(){
                    while(true){
                        System.out.println("Hello Thread");
                    try{
                        Thread.sleep(1000);
                    }
                    catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        };
        thread.start();
        while(true){
            System.out.println("Hello Main");
            try{
                Thread.sleep(1000);
            }
            catch(InterruptedException e){
                e.printStackTrace();
            }
        }

    }
}

2.5匿名内部类创建 Runnable 子类对象

public class ThreadDemo4 {
    public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    System.out.println("Hello Thread");
                    try{
                        Thread.sleep(1000);
                    }
                    catch(InterruptedException e){
                        e.printStackTrace();
                    }
                }

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

2.6lambda 表达式创建 Runnable 子类对象

Lambda表达式本质是一个匿名函数

public class ThreadDemo5 {
    public static void main(String[] args) {
        Thread thread =new Thread(()->{
            while(true){
                System.out.println("Hello Thread");
                try{
                    Thread.sleep(1000);
                }
                catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        while(true){
            System.out.println("Hello Main");
            try{
                Thread.sleep(1000);
            }
            catch(InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

三、多线程的优势-增加运行速度

可以观察多线程在一些场合下是可以提高程序的整体运行效率的。

  • 使用 System.nanoTime() 可以记录当前系统的 纳秒 级时间戳.
  • serial 串行的完成一系列运算. concurrency 使用两个线程并行的完成同样的运算.
public class ThreadAdvantage {
    // 多线程并不一定就能提高速度,可以观察,count 不同,实际的运行效果也是不同的
    private static final long count = 10_0000_0000;
    public static void main(String[] args) throws InterruptedException {
// 使用并发方式
        concurrency();
// 使用串行方式
        serial();
    }
    private static void concurrency() throws InterruptedException {
        long begin = System.nanoTime();
// 利用一个线程计算 a 的值
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                int a = 0;
                for (long i = 0; i < count; i++) {
                    a--;
                }
            }
        });
        thread.start();
// 主线程内计算 b 的值
        int b = 0;
        for (long i = 0; i < count; i++) {
            b--;
        }
// 等待 thread 线程运行结束
        thread.join();
// 统计耗时
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("并发: %f 毫秒%n", ms);
    }
    private static void serial() {
// 全部在主线程内计算 a、b 的值
        long begin = System.nanoTime();
        int a = 0;
        for (long i = 0; i < count; i++) {
            a--;
        }
        int b = 0;
        for (long i = 0; i < count; i++) {b--;
        }
        long end = System.nanoTime();
        double ms = (end - begin) * 1.0 / 1000 / 1000;
        System.out.printf("串行: %f 毫秒%n", ms);
    }
}

并发: 399.651856 毫秒
串行: 720.616911 毫秒


四、Thread 类及常见方法

Thread 类是 JVM 用来管理线程的一个类,换句话说,每个线程都有一个唯一的 Thread 对象与之关联。用我们上面的例子来看,每个执行流,也需要有一个对象来描述,类似下图所示,Thread 类的对象就是用来描述一个线程执行流的,JVM 会将这些 Thread 对象组织起来,用于线程调度,线程管理。

 4.1Thread 的常见构造方法

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

 name参数,是给线程起了一个名字。这里的名字,不影响程序执行,只是方便咱们在调试的时候,快速找到咱们关心的线程。

4.2Thread 的几个常见属性

  • ID 是线程的唯一标识,不同线程不会重复
  • 名称是各种调试工具用到
  • 状态表示线程当前所处的一个情况,下面我们会进一步说明
  • 优先级高的线程理论上来说更容易被调度到
  • 关于后台线程,需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。
  • 是否存活,即简单的理解,为 run 方法是否运行结束了
  • 线程的中断问题,下面我们进一步说明 
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    System.out.println(Thread.currentThread().getName() + ": 我还
                            活着");
                            Thread.sleep(1 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + ": 我即将死去");
        });
        System.out.println(Thread.currentThread().getName()
                + ": ID: " + thread.getId());
        System.out.println(Thread.currentThread().getName()
                + ": 名称: " + thread.getName());
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
        System.out.println(Thread.currentThread().getName()
                + ": 优先级: " + thread.getPriority());System.out.println(Thread.currentThread().getName()
                + ": 后台线程: " + thread.isDaemon());
        System.out.println(Thread.currentThread().getName()
                + ": 活着: " + thread.isAlive());
        System.out.println(Thread.currentThread().getName()
                + ": 被中断: " + thread.isInterrupted());
        thread.start();
        while (thread.isAlive()) {}
        System.out.println(Thread.currentThread().getName()
                + ": 状态: " + thread.getState());
    }
}

 

public class ThreadDemo7 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("hello t");
        });

        t.start();

        try {
            // 上述 t 线程没有进行任何循环和 sleep, 意味着里面的代码会迅速执行完毕.
            // main 线程如果 sleep 结束, 此时 t 基本上就是已经执行完了的状态. 此时 t 对象还在
            // 但是在 系统中 对应的线程已经结束了.
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(t.isAlive());
    }
}
public class ThreadDemo8 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (true) {

            }
        });

        // 默认是前台线程, 也就是设为 false
        // 此时这个线程会阻止进程结束

        // 改成 true 变成后台线程. 不影响进程的结束.
        t.setDaemon(true);
        t.start();

    }
}

 

 

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

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

相关文章

Docker常见问题集合

一、Docker安装 1、yum 安装 1&#xff09;更新yum包到最新 yum update2&#xff09;安装软件需要的软件&#xff0c;yum-util&#xff08;提供 yum-config-manager 功能&#xff09;&#xff0c;device-mapper-persistent-data、lvm2&#xff08;devicemapper 驱动依赖&…

mmdetection踩坑记录

1.mmcv-full和mmdetection的版本匹配问题 Readme里应该会给可复现的版本&#xff0c;一定要按照readme里的&#xff0c;这里是一些版本对应关系&#xff0c;像我的mmdet是2.3.0&#xff0c;我就只能装1.0.5的mmcv-full 表格来源&#xff1a;https://blog.csdn.net/qq_55957975/…

高频-测试岗面试题,软件测试面试常问面试题(付答案)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 测试流程&#xf…

【Zynq】Xilinx SDK设置编码方式

举例&#xff1a;将Xilinx SDK设置为UTF-8编码 工具栏->Window->Preferences

基于Tensorflow和Keras实现卷积神经网络CNN并进行猫狗识别

文章目录 一、环境配置1、安装Anaconda2、配置TensorFlow、Keras 二、猫狗数据集分类建模3.1 猫狗图像预处理3.2 猫狗分类的实例——基准模型3.1 构建神经网络3.2 配置优化器3.3 图片格式转化3.4 训练模型3.5 保存模型3.6 可视化 三、数据增强四、dropout 层五、参考资料 一、环…

Openresty原理概念篇(十五)Lua 规则和 NGINX 配置文件产生冲突怎么办?

一 Lua 规则和 NGINX 配置文件产生冲突怎么办? ① OpenResty 的名字和语言 说明&#xff1a; 了解openresty的发展史 ② 配置文件的规则优先级 1) 如何各司其职2) 都能满足功能,该如何取舍 理解&#xff1a; 1) rewrite ... break 到POST_WRITE阶段2) 而rewrite_by_lua*…

JAVA的DIFF算法

首先看一下我的文件结构 1.EnumType 类 public enum EnumType {ADD("ADD"),MODIFIED("MODIFIED"), DELETED("DELETED");//创建私有变量private String type;EnumType(String type) {this.type type;} }2.OperationType类 public class Operati…

vue封装svg组件来修改svg图片颜色

文章目录 1、引入依赖2、根目录的vue.config.js配置3、在组件文件夹(compontents)中创建svgIcon.vue4、在src目录下创建icons文件5、处理svg格式的图片6、在main.js文件中引入icons文件中的index.js文件7、使用8、效果图1、项目成功运行后的样子2、直接在html上添加样式&#x…

DEBUG系列三:使用 F9 和 watch point

首先是我随便找了个报错。 报销消息号信息&#xff1a; No pricing procedure could be determined Message No. V1212 1&#xff09;首先可以直接SE91 来追溯这个消息号哪儿报出来的 可以看到下面两个地方可能会报这个消息&#xff0c;可以直接在这两个地方打断点&#xff0c;…

开发一个RISC-V上的操作系统(一)—— 环境搭建

在前面我们使用Verilog实现了一个简易的RISC-V处理器&#xff0c;并且能烧录到板子上跑一些简单C程序&#xff0c;传送门&#xff1a; RISC-V处理器的设计与实现&#xff08;一&#xff09;—— 基本指令集_risc_v处理器_Patarw_Li的博客-CSDN博客 RISC-V处理器的设计与实现&…

电子器件系列41:扁平高压电阻

这种电阻和其他的高压电阻不同&#xff0c;不是绕线电阻而是陶瓷电阻 找到一个大神&#xff0c;他的专栏也得很详细了&#xff0c;贴在这里 https://blog.csdn.net/wkezheng/category_12059870.html 阻容感基础03&#xff1a;电阻器分类&#xff08;1&#xff09;-片式电阻器…

如何快速判断是否在容器环境

在渗透测试过程中&#xff0c;我们的起始攻击点可能在一台虚拟机里或是一个Docker环境里&#xff0c;甚至可能是在K8s集群环境的一个pod里&#xff0c;我们应该如何快速判断当前是否在容器环境中运行呢&#xff1f; 当拿到shell权限&#xff0c;看到数字和字母随机生成的主机名…

软考A计划-系统集成项目管理工程师-项目范围管理(二)

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…

HTML、Markdown、Word、Excel等格式的文档转换为PDF

工具&#xff1a;gotenberg&#xff0c;docker部署 github&#xff1a;https://github.com/gotenberg/gotenberg 文档&#xff1a;https://gotenberg.dev/docs/about https://gotenberg.dev/docs/modules/libreoffice docker运行&#xff1a; docker run -d --rm -p 3000:30…

kubernete部署prometheus监控sring-boot程序

目录 1、kubernete集群环境以及prometheus基础环境 2、kubernetes监控集群内部的spring-boot程序 2.1、application.yml系统配置&#xff0c;endpoints相关设置 2.2、引入监控的相关依赖文件 pom.xml &#xff0c;主要是spring-boot-starter-actuator和micrometer-registr…

ModaHub魔搭社区:向量数据库Milvus产品问题(二)

目录 为什么向量距离计算方式是内积时&#xff0c;搜索出来的 top1 不是目标向量本身&#xff1f; 对集合分区的查询是否会受到集合大小的影响&#xff0c;尤其在集合数据量高达一亿数据量时&#xff1f; 如果只是搜索集合中的部分分区&#xff0c;整个集合的数据会全部加载…

表单(form) post 方式提交时的编码与乱码(上)

在上一篇章中谈论了表单以 get 提交时的编码与乱码问题, 这一章中将讨论以 post 方式提交时的编码与乱码问题. 在前面也同时提到, 表单有一个叫 enctype 的属性, 它有两个值, application/x-www-form-urlencoded 和 multipart/form-data. 这一属性实际只对 post 方式起作用, …

@Configuration 和 @Component 的区别 ,别再瞎用了!

一句话概括就是 Configuration 中所有带 Bean 注解的方法都会被动态代理&#xff0c;因此调用该方法返回的都是同一个实例。 理解&#xff1a;调用Configuration类中的Bean注解的方法&#xff0c;返回的是同一个示例&#xff1b;而调用Component类中的Bean注解的方法&#xff…

List, Set, Ordered-SetHash

前言 本文小结Redis中List&#xff0c;Set&#xff0c;ZSet和Hash四种数据类型的&#xff0c;基本特点&#xff0c;使用场景和实现方式。 一、List 1. 基本特点 a. 作为数组&#xff0c;基于下标索引操作, 但支持正向索引和反向索引; b. 作为链表, 支持高效插入&#xff1b…