PXM的JAVA并发编程学习总结

#『Java分布式系统开发:从理论到实践』征文活动#

进程与线程

进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

线程与进程类似,但是是一个更小的执行单位,一个进程运行时可以产生多个线程,但是同一个进程的线程之间会共享堆和方法区,而进程与进程之间时相互隔离的。

所以系统创建和运行线程的开销比进程小很多。

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

  • NEW: 初始状态,线程被创建出来但没有被调用 start() 。
  • RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
  • BLOCKED:阻塞状态,需要等待锁释放。
  • WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。
  • TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。
  • TERMINATED:终止状态,表示该线程已经运行完毕

volatile

volatile关键字能够保证变量的可见性,如果声明了volatile关键字,每次都会从主内存中获取变量。本质原因时它禁用了cpu缓存,因为cpu缓存中的L1,L2级缓存时不共享的,所以才会造成同一个变量在不同线程读到的值可能不一致,因此禁用,让它们全部实时对共享的主内存进行修改即可。还有一个重要的特性就是volatile通过内存屏障,能够防止指令重排。这个的主要应用参考单例模式双重检查锁,如果有一个线程发现单例已经不为null了,并开始调用,如果没加volatile,那么可能由于指令重排,这个单例的创建变成了先建立引用,再初始化,此时由于初始化尚未完成,容易引发未知问题。当然,它不能保证操作的原子性,进行并发写的时候仍然会出问题。

synchronized

synchronized可以保证用它修饰的方法或者代码块同时只有一个线程在运行,对于语句块,内部基于monitor相关指令和对象头实现,monitor是JVM的内置锁,通过争抢monitor来确保只有一个线程能进入。对于方法,用的则是ACC_SYNCHRONIZED,用于标记这个方法是一个同步方法。

synchronized会随着锁竞争的加剧自动进行锁升级,因为在低竞争环境下,中止线程并唤醒的成本显然较高,轻量级锁更优,在高竞争环境下,显然让大量线程自旋等待容易导致资源被耗尽。因此,锁会经历如下过程:无锁->偏向锁->轻量锁->自旋锁,在后续版本(jdk15),偏向锁逐渐被淘汰了,由于现在的锁往往是多线程竞争而不是同一线程反复调用,因此偏向锁往往是负优化。

  • volatile 关键字是线程同步的轻量级实现,所以 volatile性能肯定比synchronized关键字要好 。但是 volatile 关键字只能用于变量而 synchronized 关键字可以修饰方法以及代码块 。
  • volatile 关键字能保证数据的可见性,但不能保证数据的原子性。synchronized 关键字两者都能保证。
  • volatile关键字主要用于解决变量在多个线程之间的可见性,而 synchronized 关键字解决的是多个线程之间访问资源的同步性。

ReentrantLock

ReentrantLock是一个可重入的独占锁,是对AQS框架的一个实现。相比syncornized增加了轮询、超时、中断、公平锁和非公平锁等高级功能,通过对AQS的state变量进行维护实现可重入。和syncornized不同的地方在于它有更多高级功能,并且是JDK层面的锁,而syncornized是由JVM实现的。唯一需要注意的是它要显式调用lock() 和 unlock(),使用不当可能产生死锁,一般通过try-catch调用,并放在finally里面解锁。

Threadlocal

Threadlocal类可以让每个线程读取同一个对象的同一个属性,都有相互独立的不同副本,可以避免数据竞争问题,它的核心实现逻辑在于通过把Threadlocal本身的引用和要被传入的属性组成一对Entry,其中Threadlocal本身的虚引用是Key,被传入的属性是value。因为Key是弱引用,所以当发生gc的时候会把它回收,所以如果没有了其它Threadlocal强引用,进行gc的时候会将Key也就是Threadlocal给回收掉。这样就不会导致污染后续使用者了。

Threadlocal有一个内部类叫做threadlocalMap,其中Threadlocal产生的entry就存在这里,每个线程的这个是唯一的,通过Threadlocal对象就能查找到当前线程的Threadlocal修饰的数据。

Threadlocal虽然很好,但是也有一些缺点。诸如内存泄露,因此你最好在执行结束的时候释放所有和当前线程有关的Threadlocal,必须通过try-catch调用并在finally代码块中手动调用remove。如果是公有的Threadlocal,最好加上final static关键字防止threadlocal被多次实例化。

Threadlocal和公共的ConcurrentHashMap最大的不同就是Threadlocal只负责储存私有信息,ConcurrentHashMap会共享给所有对象。

线程池

推荐使用ThreadPoolExecutor进行实现,构造参数如下:

int corePoolSize,//线程池的核心线程数量 int maximumPoolSize,//线程池的最大线程数 long keepAliveTime,//当线程数大于核心线程数时,多余的空闲线程存活的最长时间 TimeUnit unit,//时间单位 BlockingQueue workQueue,//任务队列,用来储存等待执行任务的队列 ThreadFactory threadFactory,//线程工厂,用来创建线程,一般默认即可 RejectedExecutionHandler handler//拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务

核心线程数量就是线程池中一直保持着的线程数,即使没有请求它们也仍然会继续等待,防止突然来请求了大量增开线程导致阻塞。

最大线程数就是在请求变多,并且任务队列也放不下了以后,线程池扩容的最大线程数。

存活时间和时间单位会在线程扩容后,若线程空闲超过一定时长就回收它们,直到等于核心线程数。

任务队列会存储即将进行的任务,线程在上一个任务完成后会向这个队列的头部进行拉取,如果要根据权重排优先级可以改成PriorityBlockingQueue。队列也可分为有界和无界的,如果是有界的就会在装不下了之后触发拒绝策略。

线程工厂用于创建新线程,可以自己定义属性,如定制线程名称,守护线程,设置优先级等,基本上都要自定义,默认的调试较为困难。

拒绝策略用于处理任务队列丢弃的数据,常见的有直接丢弃,让原线程运行,重试等。

CompletableFuture

这是一个能够满足异步任务需求的执行器,通过默认或者自定义的线程池来异步执行耗时任务,并且可以实现任务的串行运行,相比Future.get()方式它不会阻塞主线程,还拥有优雅的链式调用,错误处理等机制。

CompletableFuture.supplyAsync(() -> { // 数据库查询或外部API调用 return fetchFromDB(); }, taskExecutor);

对于IO密集型任务,如果同步阻塞调用会导致吞吐量非常低,把这个任务交给任务线程池就能释放tomcat线程,从而大大提升吞吐量,常用于sql慢查询,AI对话接口调用。不少开源框架的任务调度系统都是基于这个来实现的。

### 如何在包含多个 XML 文件的目录中定位缺失的 `pxm.xml` 文件 为了找到特定的 `pxm.xml` 文件并确认其是否存在,可以采用以下方法: #### 方法一:使用命令行工具 可以通过操作系统自带的命令行工具快速扫描目标目录及其子目录中的文件名。 对于 Linux 或 macOS 用户,可运行如下命令: ```bash find /path/to/directory -name "pxm.xml" ``` 此命令会递归搜索指定路径下的所有文件夹,并返回匹配名称为 `pxm.xml` 的文件位置[^1]。 对于 Windows 用户,则可以在 PowerShell 中执行类似的指令: ```powershell Get-ChildItem -Path C:\your\directory -Recurse | Where-Object { $_.Name -eq "pxm.xml" } ``` #### 方法二:编写脚本辅助查找 当需要更复杂的逻辑或者批量操作时,建议利用编程语言实现自动化的解决方案。以下是 Python 脚本的一个例子用于检测某个目录下是否有名为 `pxm.xml` 的文件存在。 ```python import os def find_pxm(directory): for root, dirs, files in os.walk(directory): if 'pxm.xml' in files: return os.path.join(root, 'pxm.xml') return None result = find_pxm('/path/to/search') # 替换实际路径 if result is not None: print(f"PXM file found at: {result}") else: print("No PXM file was located.") ``` 上述代码片段遍历给定目录树寻找符合条件的目标文件;如果没有发现任何实例则输出提示信息表明未查到该文件[^2]。 #### 方法三:检查父级POM配置 假如是在 Maven 工程环境下工作,并且怀疑可能是由于模块划分原因造成无法直接看到单独列出的 `pxm.xml` ,那么应该查看当前工程对应的顶级 Parent POM 定义部分是否包含了相关 module 设置项。例如下面展示了一个标准形式: ```xml <parent> <groupId>com.example</groupId> <artifactId>project-parent</artifactId> <version>1.0-SNAPSHOT</version> </parent> <modules> <module>core</module> <module>utils</module> <!-- 如果 pxm.xml 属于其中一个子模组 --> <module>specific-module-containing-pxm</module> </modules> ``` 注意这里假设了可能存在的放置 `pxm.xml` 的具体子模块标签 `<module>` 。如果确实如此安排的话,在对应的实际物理存储位置上也应该能够按照前述方式定位得到这份资源文件[^3]。 最后提醒一点,有时即使表面上看去缺少某些预期中的 xml 类型文档,实际上它们也许被嵌套打包进了 jar 形式的产物里头去了——这种情形下单纯依靠外部视角难以察觉内部细节内容,必要时候还得借助解压手段进一步探究实际情况才行[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值