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

【JavaEE】【多线程】进阶知识

目录

  • 一、常见的锁策略
    • 1.1 悲观锁 vs 乐观锁
    • 1.2 重量级锁 vs 轻量级锁
    • 1.3 挂起等待锁 vs 自旋锁
    • 1.4 普通互斥锁 vs 读写锁
    • 1.5 可重入锁 vs 不可重入锁
    • 1.6 不公平锁 vs 公平锁
  • 二、synchronized特性
    • 2.1 synchronized的锁策略
    • 2.2 synchronized加锁过程
    • 2.3 其它优化措施
  • 三、CAS
    • 3.1 CAS概念
    • 3.2 CAS应用场景
      • 3.2.1 实现原子类。
      • 3.2.2 实现自旋锁
    • 3.3 CAS的ABA问题
      • 3.3.1 ABA问题简介
      • 3.3.2 解决方案
  • 四、JUC组件
    • 4.1 Callable接口
    • 4.2 ReentrantLock类
    • 4.3 Semaphore类
    • 4.4 CountDownLatch类

一、常见的锁策略

1.1 悲观锁 vs 乐观锁

悲观和乐观是指锁竞争的激烈情况。

  1. 悲观锁:加锁的时候预测接下来的锁竞争会非常激烈,就需要针对这样的激烈情况额外做工作;
  2. 乐观锁:加锁的时候预测接下来的锁竞争不激烈,就不需要额外做工作去处理锁竞争情况;

1.2 重量级锁 vs 轻量级锁

这两种锁就对应上诉的悲观乐观情况下的处理机制。

  1. 重量级锁:应对上面的锁竞争激烈的悲观情况,效率更低;
  2. 轻量级锁:应对上面的锁竞争不激烈的情况,效率更高。

1.3 挂起等待锁 vs 自旋锁

这又是对上面的重量级与轻量级锁的典型实现。

  1. 挂起等待锁:重量级锁的典型实现,是操作系统内核级别的,加锁发生竞争,线程进入阻塞后,就需要内核进行唤醒。获取锁的周期会变长,但是这期间不会消耗CPU资源。
  2. 自旋锁:轻量级锁的典型实现,是应用程序级别的,加锁时发生竞争,一般不进行阻塞,而是通过忙等,等待后续程序唤醒。获取锁的周期很短,可以及时获取到锁,但是这期间会一直消耗CPU资源。

1.4 普通互斥锁 vs 读写锁

这两种锁是针对加锁解锁时的线程安全问题。

  • 普通互斥锁只有加锁,解锁操作,并且读操作不会出现线程安全问题;
  • 而读写锁有读加锁,写加锁,和解锁操作,
    读锁与读锁之间不互斥;
    读锁与写锁之间存在互斥;
    写锁与写锁之间也存在互斥。
    读写锁主要是针对读操作多,写操作少的情况服务。

1.5 可重入锁 vs 不可重入锁

这组锁就是针对同一个线程多次嵌套获取同一把锁。

  • 可重入锁:字面意思是“可以重新进入的锁”,即允许同一个线程多次获取同一把锁。
  • 不可重入锁:字面意思是“不可以重新进入的锁”,即同一个线程多次获取同一把锁,会报错。

1.6 不公平锁 vs 公平锁

这组锁是针对线程获取锁的概率设置的。

  • 不公平锁:在Java中不公平锁是概率均等的随机分配锁;
  • 公平锁:在Java中公平锁是按照等待的时间先来后到分配锁。因为操作系统是随机调度,为了实现公平锁就需要数据结构来记录先后调度顺序。

二、synchronized特性

2.1 synchronized的锁策略

锁策略如下:

  • synchronized是一个自适应的锁,开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁。
  • 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁。
  • 是一种普通互斥锁。
  • 是一种不公平锁。
  • 是一种可重入锁。

2.2 synchronized加锁过程

synchronized的自适应过程称为锁升级:无锁 -> 偏向锁 -> 自旋锁 -> 重量级锁。

  • 偏向锁:偏向锁不是真的 “加锁”, 只是给对象头中做一个 “偏向锁的标记”, 记录这个锁属于哪个线程.
    如果后续没有其他线程来竞争该锁, 那么就不用进行其他同步操作了(避免了加锁解锁的开销)
    如果后续有其他线程来竞争该锁(刚才已经在锁对象中记录了当前锁属于哪个线程了, 很容易识别
    当前申请锁的线程是不是之前记录的线程), 那就取消原来的偏向锁状态, 进入一般的轻量级锁状态.
    就像跟你搞暧昧,但是不正式跟你确认关系,如果没人来追求你就一直不确定关系,如果有人追求你就确认关系。

  • 无锁 -> 偏向锁 :这个阶段是进入synchronized代码块;

  • 偏向锁 -> 自旋锁:这个阶段是拿到偏向锁后,遇到其他线程来竞争这个锁;

  • 自旋锁 -> 重量级锁:这个阶段是JVM发现当前的锁竞争非常激烈。

2.3 其它优化措施

除了上面的synchronized锁升级以外,还有以下的优化措施:

  • 锁消除:编译器优化措施,编译器会对加锁的代码进行判断(但这种判断是非常保守的,只有100%确定当前是单线程),如果当前逻辑不需要加锁,编译器就会自动去除synchronized。

  • 锁粗化:一个代码对细粒度的代码反复加锁解锁,就会将这个步骤优化为更粗粒度的加锁解锁。

  • 锁的粒度:加锁和解锁之间,包含的代码执行的逻辑/时间越多,则锁的粒度就越粗,反之越细。

三、CAS

3.1 CAS概念

CAS: 全称Compare and swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:

我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。

伪代码表示如下:

boolean CAS(address, expectValue, swapValue) {if (&address == expectedValue) {&address = swapValue;return true;}return false;
}

3.2 CAS应用场景

3.2.1 实现原子类。

原子类:特指java.util.concurrent.atomic 包下的类,这些类中的操作都是原子的。

像我们前面Thread类详解,这篇文章介绍的非原子的操作带来的线程安全问题,如果使用原子类就不存在了。
例如执行下面这段代码:结果就一直是100000。


import java.util.concurrent.atomic.AtomicInteger;public class Demo {private static AtomicInteger count = new AtomicInteger(0);public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});Thread thread2 = new Thread(() -> {for (int i = 0; i < 50000; i++) {count.getAndAdd(1);}});thread1.start();thread2.start();thread1.join();thread2.join();System.out.println(count.get());}
}

原子类线程安全的原理:

假设两个线程同时调用 getAndIncrement :

  1. 两个线程都读取 value 的值到 oldValue 中。 (oldValue 是一个局部变量, 在栈上. 每个线程有自己的栈)
  2. 线程1 先执行 CAS 操作. 由于 oldValue 和 value 的值相同, 直接进行对 value 赋值。
  3. 线程2 再执行 CAS 操作, 第一次 CAS 的时候发现 oldValue 和 value 不相等, 不能进行赋值。因此需要进入循环。在循环里重新读取 value 的值赋给 oldValue。
  4. 线程2 接下来第二次执行 CAS, 此时 oldValue 和 value 相同, 于是直接执行赋值操作。
  5. 线程1 和 线程2 返回各自的 oldValue 的值即可。

3.2.2 实现自旋锁

基于 CAS 实现更灵活的自旋锁, 获取到更多的控制权。

主要逻辑如下:

  • 通过 CAS 看当前锁是否被某个线程持有。
  • 如果这个锁已经被别的线程持有, 那么就自旋等待。
  • 如果这个锁没有被别的线程持有, 那么就把 owner 设为当前尝试加锁的线程.

伪代码描述如下:

public class SpinLock {private Thread owner = null;public void lock(){while(!CAS(this.owner, null, Thread.currentThread())){}}public void unlock (){this.owner = null;}
}

3.3 CAS的ABA问题

3.3.1 ABA问题简介

假设存在两个线程 t1 和 t2. 有一个共享变量 num, 初始值为 A。
接下来, 线程 t1 想使用 CAS 把 num 值改成 Z, 那么就需要先读取 num 的值, 记录到 oldNum 变量中.。
使用 CAS 判定当前 num 的值是否为 A, 如果为 A, 就修改成 Z。
但是, 在 t1 执行这两个操作之间, t2 线程可能把 num 的值从 A 改成了 B, 又从 B 改成了 A。
线程 t1 的 CAS 是期望 num 不变就修改. 但是 num 的值已经被 t2 给改了. 只不过又改成 A 了. 这
个时候 t1 究竟是否要更新 num 的值为 Z 。就不符合预期出现bug。

过程图:

bug例子:

假如你去取钱有1000,取500,不小心按了两次取款,我们只想扣一次500,第一次按相当于T2,第二次按相当于T1,先执行T2读取操作修改为500,这时又正好有人给你汇款了500,内存中与寄存器中又相等了,T1扣款的操作也会执行成功。

造成ABA问题的原因就是内存值既有加又有减,所以我们的解决ABA的方式如下。

3.3.2 解决方案

给要修改的值, 引入版本号. 在 CAS 比较数据当前值和旧值的同时, 也要比较版本号是否符合预期。
CAS 操作在读取旧值的同时, 也要读取版本号。
真正修改的时候,:

  • 如果当前版本号和读到的版本号相同, 则修改数据, 并把版本号 + 1。
  • 如果当前版本号高于读到的版本号. 就操作失败(认为数据已经被修改过了)。

四、JUC组件

JUC组件就是指 java.util.concurrent 这个包下面的类。

4.1 Callable接口

Callable 接口就和java.lang包下的Runnable接口的定位差不多,只不过给了我们一个返回值,在Runnable接口要重写run方法,而在Callable接口中要重写call方法。

在使用Callable接口传入线程时需要借助JUC下的另一个类FutureTask。

使用例子一般如下:

public class Demo {public static void main(String[] args) {//创建实现了Callable接口的方法Callable<Integer> callable = new Callable<Integer>() {@Overridepublic Integer call() throws Exception {return null;}};//将实现Callable接口的对象作为参数传入FutureTask,泛型参数要一致FutureTask<Integer> futureTask = new FutureTask<>(callable);//将FutureTask对象作为参数传入Thread thread = new Thread(futureTask);}
}

4.2 ReentrantLock类

ReentrantLock类可重入互斥锁,和synchronized的定位是差不多的。

ReentrantLock 的用法:

  • public void lock(): 加锁, 如果获取不到锁就死等。
  • public boolean trylock(超时时间): 加锁, 如果获取不到锁, 等待一定的时间之后就放弃加锁。
  • public void unlock(): 解锁。
    使用这个类加锁需要我们手动解锁,为避免解锁执行不到的情况,我们一般将unlock放在finally代码块中。
		ReentrantLock locker = new ReentrantLock();try {locker.lock();}finally {locker.unlock();}

ReentrantLock与synchronized的区别:

  • ReentrantLock是类,是Java代码实现的,synchronized是关键字由JVM使用c++代码实现的。
  • ReentrantLock是需要lock()方法加锁,unlock()方法解锁。synchronized是通过进出代码块加锁解锁。
  • ReentrantLock还提供了tryLock()方法可以设置超时时间,不会一直阻塞,加锁成功返回true,调用者可以根据返回值自己操作。
  • ReentrantLock还提供了公平锁的实现,默认是非公平锁,但是构造方法传参true实现公平锁。
  • ReentrantLock搭配的通知机制是Condition类,可以更精确控制唤醒某个指定的线程,相比wait / notify功能更强大。

4.3 Semaphore类

信号量,:用来表示 “可用资源的个数”。 本质上就是一个计数器,能够协调多个线程之间的资源调配。
最主要的就是申请资源(P操作,调用acquire方法)释放资源(V操作,调用release方法)

  • 可以把信号量想象成是停车场的展示牌: 当前有车位 100 个。表示有 100 个可用资源。
  • 当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)
  • 当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)
  • 如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

4.4 CountDownLatch类

CountDownLatch类的作用:同时等待 N 个任务执行结束。(就像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩。)

使用多线程的时候,经常会把一个大任务拆分为多个子任务,使用CountDownLatch衡量子任务完成,让整个任务完成。

  • 构造 CountDownLatch 实例, 参数传入任务个数,初始化 10 表示有 10 个任务需要完成。
  • 每个任务执行完毕, 都调用 countDown()方法 . 在 CountDownLatch 内部的计数器同时自减.
  • 主线程中使用 await()方法; 阻塞等待所有任务执行完毕后wait结束。 相当于计数器为 0 了。

相关文章:

【JavaEE】【多线程】进阶知识

目录 一、常见的锁策略1.1 悲观锁 vs 乐观锁1.2 重量级锁 vs 轻量级锁1.3 挂起等待锁 vs 自旋锁1.4 普通互斥锁 vs 读写锁1.5 可重入锁 vs 不可重入锁1.6 不公平锁 vs 公平锁 二、synchronized特性2.1 synchronized的锁策略2.2 synchronized加锁过程2.3 其它优化措施 三、CAS3.…...

LeetCode100之三数之和(15)--Java

1.问题描述 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请你返回所有和为 0 且不重复的三元组。 注意 答案中不可以包含重复的三元组 示例1 输入&…...

并发编程三大特性--可见性和有序性

可见性&#xff1a; 什么是可见性&#xff1a; 可见性是指在数据在收到一个线程的修改时&#xff0c;其他的线程也可以得知并获取修改后的值的属性。这是并发编程的三大特性之一。 为了提高cpu的利用率&#xff0c;cpu在获取数据时&#xff0c;不是直接在主内存读取数据&…...

Android 使用ninja加速编译的方法

ninja的简介 随着Android版本的更迭&#xff0c;makefile体系逐渐增多&#xff0c;导致make单编模块的时间越来越长&#xff0c;每次都需要半个小时甚至更长时间&#xff0c;其原因为每次make都会重新加载所有mk文件&#xff0c;再生成ninja编译&#xff0c;此完整过程十分耗时…...

《Java 实现选择排序:原理剖析与代码详解》

目录 一、引言 二、选择排序原理 三、代码分析 1. 代码整体结构 2. main方法 3. sort方法&#xff08;选择排序核心逻辑&#xff09; 四、测试结果 一、引言 排序算法在计算机科学领域中是非常重要的一部分&#xff0c;它能够帮助我们将无序的数据按照特定的顺序进行排列…...

数据结构之双链表——考研笔记

文章目录 一.单链表VS双链表二.创建双链表&#xff08;带头结点&#xff09;三.双链表的插入四.双链表删除五.销毁双链表六.双链表遍历七. 循环链表八.静态链表1.用代码定义一个静态链表 一.单链表VS双链表 单链表中只包含指向它后继结点的指针&#xff0c;所以给定一个结点p找…...

Django视图写法

1.View&#xff1a;Django默认的视图基类,Django的HttpRequeset对象 2.APIView&#xff1a;REST-framework提供的所有视图的基类,继承自Django的View REST framework的Request对象 Request对象的数据是自动根据前端发送数据的格式进行解析之后的结果。 serializer Book…...

单臂路由实现不同VLAN之间设备通信

转载请注明出处 本实验为单臂路由配置&#xff0c;目的为让不同VLAN之间的设备能够互相通信。 1.首先&#xff0c;按照要求配置两个pc的ip地址&#xff0c;以pc0为例子&#xff1a; 2在交换机创建vlan10和vlan20 3.划分vlan&#xff0c;pc0为vlan10的设备&#xff0c;pc1为vla…...

Linux·进程控制(system V)

1. 共享内存 system V共享内存是最快的IPC形式&#xff0c;之前的管道是基于Linux内核开发的通讯方案&#xff0c;其读写接口都是现成的&#xff0c;因此内核设计者为了完成进程间通讯任务并不需要新增太多代码。而共享内存属于system V标准&#xff0c;是操作系统单独…...

华为云Stack名词解释

1、MRS MapReduce服务&#xff08;MRS&#xff09;是一种基于云计算平台的即开即用、稳定可靠、弹性伸缩、便捷管理的数据处理分析服务。 2、VBS 云硬盘备份服务&#xff08;VBS&#xff0c;Volume Backup Service&#xff09;可为云硬盘&#xff08;EVS&#xff0c;Elastic…...

YoloV9改进策略:上采样改进|CARAFE,轻量级上采样|即插即用|附改进方法+代码

论文介绍 CARAFE模块概述&#xff1a;本文介绍了一种名为CARAFE&#xff08;Content-Aware ReAssembly of FEatures&#xff09;的模块&#xff0c;它是一种用于特征上采样的新方法。应用场景&#xff1a;CARAFE模块旨在改进图像处理和计算机视觉任务中的上采样过程&#xff0…...

【C++】多态的语法与底层原理

1.多态的概念 1.1 概念 多态的概念&#xff1a;通俗来说&#xff0c;就是多种形态&#xff0c;具体点就是去完成某个行为&#xff0c;当不同的对象去完成时会 产生出不同的状态。 举个例子&#xff1a;在现实当中&#xff0c;我们去火车站买票&#xff0c;一般都分三种情况&…...

RTP和RTCP的详细介绍及其C代码示例

RTP和RTCP的详细介绍及其C代码示例 RTP和RTCP简介RTP协议详解RTCP协议详解RTP和RTCP之间的关系C代码示例RTP和RTCP简介 RTP(Real-time Transport Protocol,实时传输协议)和RTCP(Real-time Transport Control Protocol,实时传输控制协议)是流媒体传输中常用的两个协议。R…...

深入浅出了解AI教育发展与落地应用情况

2023年,是生成式AI能力涌现的一年,通用大模型是其中的主旋律。经过一年的发展,通用大模型格局已初步形成,生成式AI也从能力展示走向应用落地。进入2024年,对生成式AI的讨论和实践也都转向如何赋能产业。相比于通用大模型,进入产业内的大模型需要的是对行业的Know-How,以…...

Hive数据库操作语法

数据类型 内部表和外部表 内部表 &#xff08;CREATE TABLE table_name ......&#xff09;未被external关键字修饰的即是内部表&#xff0c; 即普通表。 内部表又称管理表,内部表数据存储的位置由hive.metastore.warehouse.dir参数决定&#xff08;默认&#xff1a;/user/h…...

容器架构-Docker的成长之路

目录 1. 什么是容器 2. 容器 vs 虚拟机 3. Docker极速上手指南 环境准备 3.1 配置docker源 3.2 下载镜像加速的配置 3.3 下载自动补全工具 4. Docker C/S架构 5. Docker的镜像管理 5.1 下载nginx:alpine镜像并查看 5.2 sl大法 5.3 删除镜像 5.4 镜像清理用的命令 5…...

关于我、重生到500年前凭借C语言改变世界科技vlog.14——常见C语言算法

文章目录 1.冒泡排序2.二分查找3.转移表希望读者们多多三连支持小编会继续更新你们的鼓励就是我前进的动力&#xff01; 根据当前所学C语言知识&#xff0c;对前面知识进行及时的总结巩固&#xff0c;出了这么一篇 vlog 介绍当前所学知识能遇到的常见算法&#xff0c;这些算法是…...

简记Vue3(三)—— ref、props、生命周期、hooks

个人简介 &#x1f440;个人主页&#xff1a; 前端杂货铺 &#x1f64b;‍♂️学习方向&#xff1a; 主攻前端方向&#xff0c;正逐渐往全干发展 &#x1f4c3;个人状态&#xff1a; 研发工程师&#xff0c;现效力于中国工业软件事业 &#x1f680;人生格言&#xff1a; 积跬步…...

ARM cpu算力KDMIPS测试

一、引言 KDMIPS(KiloDhrystone Million Instructions Per Second)是一种衡量处理器性能的指标,它表示处理器每秒钟可以执行多少百万条Dhrystone指令。 二、测试说明 1、将cpu模式调整为perfermance 2、将cpu的频率和gpu的频率调大最大 3、将ddr和各core的电压和频率调大最…...

自杀一句话木马(访问后自动删除)

在做安全测试时&#xff0c;例如文件上传时就要上传可以解析的脚本文件解析证明存在漏洞&#xff0c;这个时候就需要(访问后自动删除文件的一句话木马) PHP <?php echo md5(1);unlink(__FILE__); ?> 访问后自动删除...

变量 varablie 声明- Rust 变量 let mut 声明与 C/C++ 变量声明对比分析

一、变量声明设计&#xff1a;let 与 mut 的哲学解析 Rust 采用 let 声明变量并通过 mut 显式标记可变性&#xff0c;这种设计体现了语言的核心哲学。以下是深度解析&#xff1a; 1.1 设计理念剖析 安全优先原则&#xff1a;默认不可变强制开发者明确声明意图 let x 5; …...

iOS 26 携众系统重磅更新,但“苹果智能”仍与国行无缘

美国西海岸的夏天&#xff0c;再次被苹果点燃。一年一度的全球开发者大会 WWDC25 如期而至&#xff0c;这不仅是开发者的盛宴&#xff0c;更是全球数亿苹果用户翘首以盼的科技春晚。今年&#xff0c;苹果依旧为我们带来了全家桶式的系统更新&#xff0c;包括 iOS 26、iPadOS 26…...

python打卡day49

知识点回顾&#xff1a; 通道注意力模块复习空间注意力模块CBAM的定义 作业&#xff1a;尝试对今天的模型检查参数数目&#xff0c;并用tensorboard查看训练过程 import torch import torch.nn as nn# 定义通道注意力 class ChannelAttention(nn.Module):def __init__(self,…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)

可以使用Sqliteviz这个网站免费编写sql语句&#xff0c;它能够让用户直接在浏览器内练习SQL的语法&#xff0c;不需要安装任何软件。 链接如下&#xff1a; sqliteviz 注意&#xff1a; 在转写SQL语法时&#xff0c;关键字之间有一个特定的顺序&#xff0c;这个顺序会影响到…...

镜像里切换为普通用户

如果你登录远程虚拟机默认就是 root 用户&#xff0c;但你不希望用 root 权限运行 ns-3&#xff08;这是对的&#xff0c;ns3 工具会拒绝 root&#xff09;&#xff0c;你可以按以下方法创建一个 非 root 用户账号 并切换到它运行 ns-3。 一次性解决方案&#xff1a;创建非 roo…...

Java 加密常用的各种算法及其选择

在数字化时代&#xff0c;数据安全至关重要&#xff0c;Java 作为广泛应用的编程语言&#xff0c;提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景&#xff0c;有助于开发者在不同的业务需求中做出正确的选择。​ 一、对称加密算法…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

有限自动机到正规文法转换器v1.0

1 项目简介 这是一个功能强大的有限自动机&#xff08;Finite Automaton, FA&#xff09;到正规文法&#xff08;Regular Grammar&#xff09;转换器&#xff0c;它配备了一个直观且完整的图形用户界面&#xff0c;使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...