JavaEE-多线程上

文章目录

线程概述

进程/线程

  • 进程是指操作系统中的一段程序 , 它是一个正在执行的程序实例, 具有独立的内存空间和系统资源, 所以进程之间资源不共享 , 如文件, 网络端口等, 在计算机运行时, 一般是先创建进程, 后创建线程, 一个进程通常可以包含多个线程
  • 线程是指的是进程中的一个执行单元 , 是进程的一部分, 负责在进程中执行代码, 每一个线程都有自己的栈和程序计数器 , 并且可以共享进程的资源, 多个线程可以在同一个时刻执行不同的操作, 从而提高程序的执行效率, 线程与线程之间的资源并不是完全共享的, 下面会详细介绍...

大白话总结:

  • 一个应用程序就是一个进程
  • 一个进程里面有多个线程执行任务

多线程的作用

最重要的就是提高处理问题的效率, 能够使CPU在处理一个任务时同时处理多个线程, 这样可以充分利用CPU的资源, 提高CPU的资源利用率

JVM关于线程资源的规范

我们用下面的一张图来说明

在同一个进程的多个线程之间, Heap(堆), Method Area(方法区)资源是共享的, Java Virtual Machine Stack(Java虚拟机栈), Native Method Stack(本地方法栈), The pc Register(程序计数器)这些都是不能够共享的

所以就存在线程安全的问题

比如对于变量来说, 局部变量存在Java虚拟机栈, 所以不存在线程安全问题, 但是实例变量, 静态变量都存在于堆中, 就会存在线程安全的问题

关于Java程序的运行原理

  • 当JVM启动的时候, JVM会自动开启一个主线程(main-thread) , 然后去调用main方法
    所以main方法都是在主线程中执行的
  • 除了主线程之外, 还会启动一个垃圾回收线程(GC), 因此启动JVM, 至少启动了两个线程
  • 除上述线程之外, 程序员可以手动创建其他的线程并启动

并发与并行

早期的人们使用的单核的CPU, 那是不是不能同时运行多个程序呢? 答案是否定的, 下面的关于并发与并行就可以解释这个问题

并发(concurrency)

  • 在使用单核心CPU时候, 微观层面一个时间点只能执行一个指令 , 但多个指令被快速的轮换执行 ,使得在宏观上具有多个指令同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干端,使多个指令快速交替的执行

下面的这张图就可以很好的说明这个问题

横轴对应的是时间, 纵轴对应的是ABC三个程序, 围观层面, 某一时间点只能执行一个程序, 但是通过CPU高速的在ABC三个程序中进行调度切换, 让我们宏观上看起来是三个程序同时执行的 , 这种情况就是并发

并行(parallellism)

  • 这种情况针对的就是多核心CPU 的情况, 此时在一个时间点, 微观层面上也可以通过多个核心同时多个程序来达到真正意义上的"同时执行"

并发编程与并行编程

  • 多核心CPU资源紧缺(或者是单核心CPU)的前提下 , 如果开启了多个线程, 但是只有一个CPU核心可以提供资源 , 那么这些线程就会抢夺CPU的时间片, 竞争执行机会 , 这就是通过 并发 的方式实现多线程
  • 多核心CPU资源比较充足的情况下 , 此时有多个CPU核心可以来提供资源, 此时一个进程中的线程就会被分配到多个CPU核心上同时执行 , 这就是通过 并行 的方式实现多线程
  • 不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源,而我们使用多线程的目的就是为了提高CPU资源的利用率

那么Java实现多线程的方式是并发还是并行呢?

  • 至于Java多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问我我所写的多线程是并发还是并行的?我会说,都有可能

线程的调度策略

存在线程调度的原因是因为, 当多个线程被分配到同一个CPU核心执行的时候, 此时这些程序就会抢夺CPU的时间片从而或者执行权, 所以就存在执行的先后问题

分时调度模型

  • 所有线程轮流使用CPU的执行权, 并且平均每个线程的占用时间, 也就是绝对平均

抢占式调度模型

  • 让优先级高的线程以较大的概率优先获得CPU的执行权 , 如果线程的优先级相同, 那么就会随机选择一个线程获得CPU的执行权 , 而Java采用的就是抢占式调度模型

创建线程

线程类分析入门

这个是JDK17帮助文档 的链接
JDK17帮助文档

通过API文档, 我们可以查询到我们需要的一些信息内容, 从而完成编程(这里不推荐查看中文的帮助文档)


构造方法

构造方法可以通过传入一个字符串然后指定该线程的名称


static Thread currentThread()

这是一个静态方法, 通过这个方法可以获取到当前线程的Thread信息, 其实有点类似于之前学的this


void setName(String name)

这是一个实例方法, 通过一个线程的引用调用之后可以设置当前线程的名称(其实每一个线程都有一个默认的名称)


String getName()

这是一个实例方法, 作用就是调用之后可以获得当前线程的名称


实现线程的第一种方式

执行逻辑如下

  • 创建一个类继承Thread(java.lang)包下的, 默认导入)类
  • 重写Thread中的run()方法(这个run()相当于每个线程的main函数)
  • 创建这个类的对象, 然后调用start()方法, 开启线程

我们给一个代码的案例测试一下

java 复制代码
package thread_demo.thread_demo01;

public class Thread01 {
    public static void main(String[] args) {
        // 当执行main函数的时候, 系统自动的就开启了两个线程
        Thread t = new MyThread();
        t.start();

        // 主线程的内容
        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}

/**
 * 创建线程的第一种方式是创建一个类继承Thread(所以这个类其实也就是一个线程)
 * 1. 创建一个类继承Thread这个线程类(所以此时这就是一个线程)
 * 2. 重写run方法(这就相当于每一个线程运行的入口)
 * 3. new一个线程对象然后调用start()方法启动一个线程
 */
class MyThread extends Thread {
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "---->" + i);
        }
    }
}

执行结果如下

两个线程会无规则的交替执行

初学者好多不理解这个代码的执行逻辑, 首先我们在main中创建了一个线程对象, start()的作用就是开启一个线程, 然后就弹栈了, 此时JVM中就存在了两个线程同时执行(不含GC), 所有就会出现交替执行的情况

实现线程的第二种方式

执行逻辑如下

  • 创建一个类实现Runnable接口
  • 重写其中的run()方法
  • 创建这个类的对象(匿名的也可以), start()开启一个线程

给一个代码案例测试一下

java 复制代码
package thread_demo.thread_demo02;

public class Thread02 {
    public static void main(String[] args) {
        // 直接new一个对象调用创建一个t线程
        Runnable r = new MyThread();
        Thread t = new Thread(r, "test1");
        // 开启t线程
        t.start();

        // 利用匿名内部类的方式创建一个线程对象
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "--->" + i);
                }
            }
        }, "test2");
        // 开启t1线程
        t1.start();

        // 直接就不接收变量直接开启一个线程
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "--->" + i);
                }
            }
        }, "test3").start();

        // 最后在操作一下主线程
        Thread mainThread = Thread.currentThread();
        mainThread.setName("main");
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

/**
 * 创建线程的第二种方式是定义一个类去实现Runnable接口, 我们推荐使用这种方式开启多线程
 * 1. 定义一个类实现Runnable接口
 * 2. 重写run方法
 * 3. Thread t = new Thread(传入这个类的对象)
 * 4. t.start() 去开启一个线程
 */
class MyThread implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 100; i++){
            System.out.println(Thread.currentThread().getName() + "--->" + i);
        }
    }
}

执行结果也是多个线程交替执行, 原理和第一种是一致的

线程的生命周期

线程的生命周期概述

线程的生命周期主要就是指的一个线程在不同时期的状态情况, 大致可以分

  • 6种(JDK层面)
  • 7种(我们平时常说的)

首先我们从JDK层面分析一下为什么是6种

我们查看一下帮助文档找到Thread.State这个枚举类型

  • NEW : 新建状态, 也就是执行start()之前的状态
  • RUNNABLE : 可运行状态, 这个状态分为两个子状态
    就绪状态 和 运行状态
  • WAITING : 等待状态, 不限时间(比如等待用户输入)
  • TIMED_WAITING : 超时等待状态, 限制时间(比如Thread.sleep(毫秒)
  • BLOCKED : 阻塞状态, 比如遇到了一些关于锁的操作
  • TERMINATED : 时亡状态, run()方法结束

线程生命周期间的关系(含UML图)

下面我们尝试绘制一个UML来描述一下各个状态之间的关系

图上说明的十分的详细了..., 我们再阐述一下

  • 首先线程处于一个NEW的新建状态, 然后通过start()创建一个线程 , 此时线程处于RUNNABLE(就绪状态)
  • 位于RUNNABLE(就绪状态)的线程拥有抢夺CPU时间片的能力 , 当抢夺到了CPU时间片之后, 线程的run()方法就开始执行, 此时线程处于RUNNABLE(运行状态), 当抢夺到的CPU时间片用完了之后, 线程会再次进入到RUNNABLE(就绪状态), 下次执行时会接着上一次的执行, 这个过程中靠的是CPU的调度机制
  • 当程序在RUNNABLE(运行状态)遇到Thread.sleep(毫秒)类似的间隔的时候, 会进入到TIMED_WAITING超时等待状态, 此时会返还先前抢到的CPU时间片 , 所有的等待休眠期度过之后, 会进入到RUNNABLE(就绪状态)等待下一次抢夺CPU时间片
  • 当程序在RUNNABLE(运行状态)遇到例如接收到需要等待用户输入的指令 的时候, 就会进入到WAITING等待状态, 这个等待状态是没有时间的限制的(比如接收到输入为止)
  • run()方法彻底执行结束之后, 就会触发到TERMINATED状态...
相关推荐
儿时可乖了10 分钟前
使用 Java 操作 SQLite 数据库
java·数据库·sqlite
ruleslol11 分钟前
java基础概念37:正则表达式2-爬虫
java
xmh-sxh-131428 分钟前
jdk各个版本介绍
java
天天扭码1 小时前
五天SpringCloud计划——DAY2之单体架构和微服务架构的选择和转换原则
java·spring cloud·微服务·架构
程序猿进阶1 小时前
堆外内存泄露排查经历
java·jvm·后端·面试·性能优化·oom·内存泄露
FIN技术铺1 小时前
Spring Boot框架Starter组件整理
java·spring boot·后端
小曲程序1 小时前
vue3 封装request请求
java·前端·typescript·vue
陈王卜1 小时前
django+boostrap实现发布博客权限控制
java·前端·django
小码的头发丝、1 小时前
Spring Boot 注解
java·spring boot
java亮小白19971 小时前
Spring循环依赖如何解决的?
java·后端·spring