Java并发编程(二)

news2024/11/18 1:41:44

线程方法

API

Thread 类 API:

方法说明
public void start()启动一个新线程,Java虚拟机调用此线程的 run 方法
public void run()线程启动后调用该方法
public void setName(String name)给当前线程取名字
public void getName()获取当前线程的名字
线程存在默认名称:子线程是 Thread-索引,主线程是 main
public static Thread currentThread()获取当前线程对象,代码在哪个线程中执行
public static void sleep(long time)让当前线程休眠多少毫秒再继续执行
Thread.sleep(0) : 让操作系统立刻重新进行一次 CPU 竞争
public static native void yield()提示线程调度器让出当前线程对 CPU 的使用
public final int getPriority()返回此线程的优先级
public final void setPriority(int priority)更改此线程的优先级,常用 1 5 10
public void interrupt()中断这个线程,异常处理机制
public static boolean interrupted()判断当前线程是否被打断,清除打断标记
public boolean isInterrupted()判断当前线程是否被打断,不清除打断标记
public final void join()等待这个线程结束
public final void join(long millis)等待这个线程死亡 millis 毫秒,0 意味着永远等待
public final native boolean isAlive()线程是否存活(还没有运行完毕)
public final void setDaemon(boolean on)将此线程标记为守护线程或用户线程

run start

run:称为线程体,包含了要执行的这个线程的内容,方法运行结束,此线程随即终止。直接调用 run 是在主线程中执行了 run,没有启动新的线程,需要顺序执行

start:使用 start 是启动新的线程,此线程处于就绪(可运行)状态,通过新的线程间接执行 run 中的代码

说明:线程控制资源类

run() 方法中的异常不能抛出,只能 try/catch

  • 因为父类中没有抛出任何异常,子类不能比父类抛出更多的异常
  • 异常不能跨线程传播回 main() 中,因此必须在本地进行处理

sleep yield

sleep:

  • 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  • sleep() 方法的过程中,线程不会释放对象锁
  • 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  • 睡眠结束后的线程未必会立刻得到执行,需要抢占 CPU
  • 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield:

  • 调用 yield 会让提示线程调度器让出当前线程对 CPU 的使用
  • 具体的实现依赖于操作系统的任务调度器
  • 会放弃 CPU 资源,锁资源不会释放

join

public final void join():等待这个线程结束

原理:调用者轮询检查线程 alive 状态,t1.join() 等价于:

public final synchronized void join(long millis) throws InterruptedException {
    // 调用者线程进入 thread 的 waitSet 等待, 直到当前线程运行结束
    while (isAlive()) {
        wait(0);
    }
}
  • join 方法是被 synchronized 修饰的,本质上是一个对象锁,其内部的 wait 方法调用也是释放锁的,但是释放的是当前的线程对象锁,而不是外面的锁

  • 当调用某个线程(t1)的 join 方法后,该线程(t1)抢占到 CPU 资源,就不再释放,直到线程执行完毕

线程同步:

  • join 实现线程同步,因为会阻塞等待另一个线程的结束,才能继续向下运行
    • 需要外部共享变量,不符合面向对象封装的思想
    • 必须等待线程结束,不能配合线程池使用
  • Future 实现(同步):get() 方法阻塞等待执行结果
    • main 线程接收结果
    • get 方法是让调用线程同步等待
public class Test {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            r = 10;
        });
        t1.start();
        t1.join();//不等待线程执行结束,输出的10
        System.out.println(r);
    }
}

interrupt

打断线程

public void interrupt():打断这个线程,异常处理机制

public static boolean interrupted():判断当前线程是否被打断,打断返回 true,清除打断标记,连续调用两次一定返回 false

public boolean isInterrupted():判断当前线程是否被打断,不清除打断标记

打断的线程会发生上下文切换,操作系统会保存线程信息,抢占到 CPU 后会从中断的地方接着运行(打断不是停止)

  • sleep、wait、join 方法都会让线程进入阻塞状态,打断线程会清空打断状态(false)

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(()->{
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1");
        t1.start();
        Thread.sleep(500);
        t1.interrupt();
        System.out.println(" 打断状态: {}" + t1.isInterrupted());// 打断状态: {}false
    }
  • 打断正常运行的线程:不会清空打断状态(true)

    public static void main(String[] args) throws Exception {
        Thread t2 = new Thread(()->{
            while(true) {
                Thread current = Thread.currentThread();
                boolean interrupted = current.isInterrupted();
                if(interrupted) {
                    System.out.println(" 打断状态: {}" + interrupted);//打断状态: {}true
                    break;
                }
            }
        }, "t2");
        t2.start();
        Thread.sleep(500);
        t2.interrupt();
    }

打断 park

park 作用类似 sleep,打断 park 线程,不会清空打断状态(true)

public static void main(String[] args) throws Exception {
    Thread t1 = new Thread(() -> {
        System.out.println("park...");
        LockSupport.park();
        System.out.println("unpark...");
        System.out.println("打断状态:" + Thread.currentThread().isInterrupted());//打断状态:true
    }, "t1");
    t1.start();
    Thread.sleep(2000);
    t1.interrupt();
}

如果打断标记已经是 true, 则 park 会失效

LockSupport.park();
System.out.println("unpark...");
LockSupport.park();//失效,不会阻塞
System.out.println("unpark...");//和上一个unpark同时执行

可以修改获取打断状态方法,使用 Thread.interrupted(),清除打断标记

LockSupport 类在 同步 → park-un 详解


终止模式

终止模式之两阶段终止模式:Two Phase Termination

目标:在一个线程 T1 中如何优雅终止线程 T2?优雅指的是给 T2 一个后置处理器

错误思想:

  • 使用线程对象的 stop() 方法停止线程:stop 方法会真正杀死线程,如果这时线程锁住了共享资源,当它被杀死后就再也没有机会释放锁,其它线程将永远无法获取锁
  • 使用 System.exit(int) 方法停止线程:目的仅是停止一个线程,但这种做法会让整个程序都停止

两阶段终止模式图示:

打断线程可能在任何时间,所以需要考虑在任何时刻被打断的处理方法:

public class Test {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        Thread.sleep(3500);
        tpt.stop();
    }
}
class TwoPhaseTermination {
    private Thread monitor;
    // 启动监控线程
    public void start() {
        monitor = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    Thread thread = Thread.currentThread();
                    if (thread.isInterrupted()) {
                        System.out.println("后置处理");
                        break;
                    }
                    try {
                        Thread.sleep(1000);					// 睡眠
                        System.out.println("执行监控记录");	// 在此被打断不会异常
                    } catch (InterruptedException e) {		// 在睡眠期间被打断,进入异常处理的逻辑
                        e.printStackTrace();
                        // 重新设置打断标记,打断 sleep 会清除打断状态
                        thread.interrupt();
                    }
                }
            }
        });
        monitor.start();
    }
    // 停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

daemon

public final void setDaemon(boolean on):如果是 true ,将此线程标记为守护线程

线程启动前调用此方法:

Thread t = new Thread() {
    @Override
    public void run() {
        System.out.println("running");
    }
};
// 设置该线程为守护线程
t.setDaemon(true);
t.start();

用户线程:平常创建的普通线程

守护线程:服务于用户线程,只要其它非守护线程运行结束了,即使守护线程代码没有执行完,也会强制结束。守护进程是脱离于终端并且在后台运行的进程,脱离终端是为了避免在执行的过程中的信息在终端上显示

说明:当运行的线程都是守护线程,Java 虚拟机将退出,因为普通线程执行完后,JVM 是守护线程,不会继续运行下去

常见的守护线程:

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等待它们处理完当前请求

不推荐

不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁:

  • public final void stop():停止线程运行

    废弃原因:方法粗暴,除非可能执行 finally 代码块以及释放 synchronized 外,线程将直接被终止,如果线程持有 JUC 的互斥锁可能导致锁来不及释放,造成其他线程永远等待的局面

  • public final void suspend()挂起(暂停)线程运行

    废弃原因:如果目标线程在暂停时对系统资源持有锁,则在目标线程恢复之前没有线程可以访问该资源,如果恢复目标线程的线程在调用 resume 之前会尝试访问此共享资源,则会导致死锁

  • public final void resume():恢复线程运行


线程原理

运行机制

Java Virtual Machine Stacks(Java 虚拟机栈):每个线程启动后,虚拟机就会为其分配一块栈内存

  • 每个栈由多个栈帧(Frame)组成,对应着每次方法调用时所占用的内存
  • 每个线程只能有一个活动栈帧,对应着当前正在执行的那个方法

线程上下文切换(Thread Context Switch):一些原因导致 CPU 不再执行当前线程,转而执行另一个线程

  • 线程的 CPU 时间片用完
  • 垃圾回收
  • 有更高优先级的线程需要运行
  • 线程自己调用了 sleep、yield、wait、join、park 等方法

程序计数器(Program Counter Register):记住下一条 JVM 指令的执行地址,是线程私有的

当 Context Switch 发生时,需要由操作系统保存当前线程的状态(PCB 中),并恢复另一个线程的状态,包括程序计数器、虚拟机栈中每个栈帧的信息,如局部变量、操作数栈、返回地址等

JVM 规范并没有限定线程模型,以 HotSopot 为例:

  • Java 的线程是内核级线程(1:1 线程模型),每个 Java 线程都映射到一个操作系统原生线程,需要消耗一定的内核资源(堆栈)
  • 线程的调度是在内核态运行的,而线程中的代码是在用户态运行,所以线程切换(状态改变)会导致用户与内核态转换进行系统调用,这是非常消耗性能

Java 中 main 方法启动的是一个进程也是一个主线程,main 方法里面的其他线程均为子线程,main 线程是这些线程的父线程


线程调度

线程调度指系统为线程分配处理器使用权的过程,方式有两种:协同式线程调度、抢占式线程调度(Java 选择)

协同式线程调度:线程的执行时间由线程本身控制

  • 优点:线程做完任务才通知系统切换到其他线程,相当于所有线程串行执行,不会出现线程同步问题
  • 缺点:线程执行时间不可控,如果代码编写出现问题,可能导致程序一直阻塞,引起系统的奔溃

抢占式线程调度:线程的执行时间由系统分配

  • 优点:线程执行时间可控,不会因为一个线程的问题而导致整体系统不可用
  • 缺点:无法主动为某个线程多分配时间

Java 提供了线程优先级的机制,优先级会提示(hint)调度器优先调度该线程,但这仅仅是一个提示,调度器可以忽略它。在线程的就绪状态时,如果 CPU 比较忙,那么优先级高的线程会获得更多的时间片,但 CPU 闲时,优先级几乎没作用

说明:并不能通过优先级来判断线程执行的先后顺序


未来优化

内核级线程调度的成本较大,所以引入了更轻量级的协程。用户线程的调度由用户自己实现(多对一的线程模型,多个用户线程映射到一个内核级线程),被设计为协同式调度,所以叫协程

  • 有栈协程:协程会完整的做调用栈的保护、恢复工作,所以叫有栈协程
  • 无栈协程:本质上是一种有限状态机,状态保存在闭包里,比有栈协程更轻量,但是功能有限

有栈协程中有一种特例叫纤程,在新并发模型中,一段纤程的代码被分为两部分,执行过程和调度器:

  • 执行过程:用于维护执行现场,保护、恢复上下文状态
  • 调度器:负责编排所有要执行的代码顺序

线程状态

进程的状态参考操作系统:创建态、就绪态、运行态、阻塞态、终止态

线程由生到死的完整过程(生命周期):当线程被创建并启动以后,既不是一启动就进入了执行状态,也不是一直处于执行状态,在 API 中 java.lang.Thread.State 这个枚举中给出了六种线程状态:

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动,还没调用 start 方法,只有线程对象,没有线程特征
Runnable(可运行)线程可以在 Java 虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器,调用了 t.start() 方法:就绪(经典叫法)
Blocked(阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入 Blocked 状态;当该线程持有锁时,该线程将变成 Runnable 状态
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入 Waiting 状态,进入这个状态后不能自动唤醒,必须等待另一个线程调用 notify 或者 notifyAll 方法才能唤醒
Timed Waiting (限期等待)有几个方法有超时参数,调用将进入 Timed Waiting 状态,这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有 Thread.sleep 、Object.wait
Teminated(结束)run 方法正常退出而死亡,或者因为没有捕获的异常终止了 run 方法而死亡

  • NEW → RUNNABLE:当调用 t.start() 方法时,由 NEW → RUNNABLE

  • RUNNABLE <--> WAITING:

    • 调用 obj.wait() 方法时

      调用 obj.notify()、obj.notifyAll()、t.interrupt():

      • 竞争锁成功,t 线程从 WAITING → RUNNABLE
      • 竞争锁失败,t 线程从 WAITING → BLOCKED
    • 当前线程调用 t.join() 方法,注意是当前线程在 t 线程对象的监视器上等待

    • 当前线程调用 LockSupport.park() 方法

  • RUNNABLE <--> TIMED_WAITING:调用 obj.wait(long n) 方法、当前线程调用 t.join(long n) 方法、当前线程调用 Thread.sleep(long n)

  • RUNNABLE <--> BLOCKED:t 线程用 synchronized(obj) 获取了对象锁时竞争失败


查看线程

Windows:

  • 任务管理器可以查看进程和线程数,也可以用来杀死进程
  • tasklist 查看进程
  • taskkill 杀死进程

Linux:

  • ps -ef 查看所有进程
  • ps -fT -p 查看某个进程(PID)的所有线程
  • kill 杀死进程
  • top 按大写 H 切换是否显示线程
  • top -H -p 查看某个进程(PID)的所有线程

Java:

  • jps 命令查看所有 Java 进程
  • jstack 查看某个 Java 进程(PID)的所有线程状态
  • jconsole 来查看某个 Java 进程中线程的运行情况(图形界面)

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

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

相关文章

实战演练 | 使用 Navicat Premium 自动运行数据库复制

与同步&#xff08;使两个数据库的模式和数据同步的一次性过程&#xff09;不同&#xff0c;复制是一个连续&#xff08;自动&#xff09;在两个数据库之间重现数据的过程&#xff08;尽管模式更新也是可能的&#xff09;。复制可以异步完成&#xff0c;因此不需要永久连接两个…

【Lniux】目录的权限,默认权限,粘滞位详细讲解

大家好&#xff0c;今天详细讲解一些关于目录权限的细节 很多细节都是通过问答方式&#xff0c;希望大家可以先自己思考一下答案然后再听我的分析 欢迎指正错误&#xff0c;我们共同成长 目录 1.目录的权限 2.默认权限 3.粘滞位 1.目录的权限 如果我们要进图一个目录只需要…

ArcGIS基础实验操作100例--实验25统一多分辨率栅格数据

本实验专栏来自于汤国安教授《地理信息系统基础实验操作100例》一书 实验平台&#xff1a;ArcGIS 10.6 实验数据&#xff1a;请访问实验1&#xff08;传送门&#xff09; 基础编辑篇--实验25 统一多分辨率栅格数据 目录 一、实验背景 二、实验数据 三、实验步骤 &#xff0…

springboot admin-server的使用

指标监控可视化文档&#xff1a; 用于管理 Spring Boot 应用程序的管理 UI Spring Boot Admin Reference Guide 一、创建项目 就勾选Spring Web项目即可 二、基础设置 (1) 依赖引入 <dependency><groupId>de.codecentric</groupId><artifactId>sp…

Android: Binder: 彻底顿悟Android Binder

Binder机制可谓是Android 知识体系的重中之中&#xff0c;作为偏底层的基础组件&#xff0c;平时我们很少关注它&#xff0c;但是它却无处不在&#xff0c;这也是android面试考察点之一&#xff0c;本篇将从流程上将Binder通信过一遍。 文章目录 1&#xff1a;Binder作用 2&…

STM32F7-Discovery使用ITM作为调试工具

关于代码的调试手段&#xff0c;我在自己的一篇文章(http://bbs.ickey.cn/index.php?appgroup&actopic&id54944链接中的《STM32F030 Nucleo-开发调试的经验USART的重要性.pdf》)中已经详细谈到&#xff0c;为什么在调试中我们通常使用J-Link或ULINK或ST-Link(ST)或Ope…

机器学习——细节补充

1.matplotlib与seaborn的区别 来源&#xff1a;https://geek-docs.com/matplotlib/matplotlib-ask-answer/difference-between-matplotlib-and-seaborn.html 2.%matplotlib inline使图片嵌入notebook&#xff0c;而不需要使用show()方法 3.IPython与python&#xff1a;IPyth…

中小企业如何选择进销存软件?

企业信息化转型趋势的推动&#xff0c;让很多中小企业也开启了转型的探索。对于企业&#xff0c;一款合适的进销存管理软件&#xff0c;绝对是转型之路上的必备工具&#xff0c;可以帮助企业对经营中的采购、库存、销售等环节进行有效管理监督。 目前&#xff0c;市面上的各种…

three.js 的渲染结构

理解three.js 的渲染结构 1 three.js 的渲染 Three.js 封装了场景、灯光、阴影、材质、纹理和三维算法&#xff0c;让你不必再直接用WebGL 开发项目。three.js 在渲染三维场景时&#xff0c;需要创建很多对象&#xff0c;并将它们关联在一起。 下图便是一个基本的three.js 渲…

Python通知Epic白嫖游戏信息

每周都有免费游戏 - Epic Games 近期看到Epic在送游戏&#xff0c;目前每周都会有活动白嫖。 身为白嫖党&#xff0c;肯定要操作一下。 游戏列表&#xff1a;Epic Games Store 每周免费游戏&#xff08;331&#xff09; | indienova GameDB 游戏库 大致思路&#xff1a; 1、…

把teamtalk中的网络库(netlib)拆出来单独测试实现双工通信效果

这篇文章的基础是上一篇对于将teamtalk中的线程池&#xff0c;连接池单独拆出来的讲解 不是说这个网络库会依赖线程池&#xff0c;连接池&#xff0c;而是上一篇文章中讲了一些base目录中的文件&#xff0c;并且这个网络库会依赖一些base目录里的文件&#xff0c; 文末会将所有…

基于fpga的自动售货机(三段式状态机)

目录 1、VL38 自动贩售机1 题目介绍 思路分析 代码实现 仿真文件 2、VL39 自动贩售机2 题目介绍&#xff1a; 题目分析 代码实现 仿真文件 3、状态机基本知识 1、VL38 自动贩售机1 题目介绍 设计一个自动贩售机&#xff0c;输入货币有三种&#xff0c;为0.5/1/2元&…

JS概览 (JS基础 DOM BOM)

目录 JavaScript JS基础 JS数据类型 函数 变量的作用域 作用域链 预解析 DOM DOM树 获取元素的方法 事件高级 注册和解绑事件 DOM事件流 BOM 和DOM的区别 window 对象的常见事件 window.onload JS执行机制 具体的执行流程 例子 JavaScript JS基础 JS数据类…

鉴源论坛 · 观模丨基于AUTOSAR的TTCAN通信协议的形式化建模与分析

作者 | 郭建 上海控安可信软件创新研究院特聘专家 版块 | 鉴源论坛 观模 汽车工业发展至今&#xff0c;硬件方面如车身材料、发动机等已无太大升值空间&#xff0c;而汽车电子则有着广阔的前景。为此各大汽车厂商对汽车电子的研究都投入了大量的人力财力。2003 年&#xff0c…

链式二叉树的代码总结

今天我带来链式二叉树的代码总结。 目录前言链式二叉树代码实现的五个文档二叉树的例子前序遍历中序遍历后序遍历层序遍历求结点个数的函数求叶子的个数的函数求k层结点个数的函数查找某一个值的函数求二叉树高度的函数判断二叉树是否是完全二叉树的函数开辟二叉树结点的函数销…

【设计模式】工厂方法模式

简单工厂模式的弊端 在简单工厂模式中只提供一个工厂类&#xff0c;该工厂类处于对产品类进行实例化的中心位置&#xff0c;它需要知道每一个产品对象的创建细节&#xff0c;并决定何时实例化哪一个产品类。简单工厂模式最大的缺点是当有新产品要加入到系统中时&#xff0c;必…

实现isReactive和isReadonly

08_实现isReactive和isReadonly 一、实现isReactive isReactive: 检查一个对象是否是由 reactive 创建的响应式代理。 1. 单元测试 // src/reactivity/tests/reactive.spec.tsimport { reactive, isReactive } from ../reactive;describe(reactive, function () {it(happy pa…

Callable接口

前言 获取多线程的方法&#xff0c;我们都知道有三种&#xff0c;还有一种是实现Callable接口 实现Runnable接口实现Callable接口实例化Thread类使用线程池获取Callable接口 Callable接口&#xff0c;是一种让线程执行完成后&#xff0c;能够返回结果的 在说到Callable接口…

【Unity天空盒】卡通渲染中如何实现云的消散效果

写在前面 完成大气渲染之后&#xff0c;接下来就是考虑云渲染了。因为我想做的天空盒本身是想跟着这位大佬Unity 卡通渲染 程序化天空盒 - 知乎里叙述的进程来的&#xff0c;里面云实现的是原神里的云&#xff0c;原神又是在崩3的基础上加上了消散效果。但现在能找到的一些教程…

线程中的sleep, yield, join

1. 前言 今天以具体实例的方法来详细记录下实战中的sleep, yield, join。 到底是什么意思&#xff0c;应该怎么用呢&#xff1f;&#xff1f;&#xff1f; 2. 适合人群 对该类方法的概念比较模糊的人 3. 开始 3.1 sleep 此方法是一个静态方法&#xff0c;可以通过类名直接调…