Java并发编程的艺术

探讨并发编程中的上下文切换与死锁问题,介绍无锁编程、CAS算法、协程等优化方法,深入分析Java并发机制,包括volatile与synchronized的工作原理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在进行并发编程时,如果希望通过多线程执行任务让程序运行的更快,会面临非常多的挑战,比如上下文切换,死锁的问题。
①上下文切换,时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒。
当前任务执行一个时间片后会切换到下一个任务,但是,切换前会保存上一个任务的状态,以便切回来时可以在此加载。所以任务从保存到再加载的过程就是一次上下文切换。

②如何减少上下文切换。
减少上下文切换的方法有无锁并发编程,CAS算法,使用最少线程和使用协程。
无锁并发编程:多线程竞争锁时,会引起上下文切换,所以多线程处理数据时,可以用一些方法来避免使用锁,比如讲数据的ID按照哈希算法取模分段,不同的线程处理不同段的数据。

CAS算法:Java的Atomic包使用CAS算法来更新数据,并不需要加锁。

使用最少线程:避免创建不需要的线程,比如任务很少,但是创建了很多线程来处理,这样会造成大量线程处于等待状态。

协程:在单线程里实现多任务的调度,并在单线程里维持多个 任务间的切换。

③死锁(避免死锁的几个常用方法)
避免一个线程同时获取多个锁。
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁,使用lock.tryLock(timeout)来替代内部锁机制。
对于数据库锁,加锁和解锁必须在同一个数据库链接里,否则会出现解锁失败。

Java并发机制的底层实现原理。
volatile的应用:在多线程编程中,Synchronized和Volatile都扮演着重要的角色,volatile是轻量级的Synchronized,它在多处理器开发中保证了共享变量的”可见性“。可见性的意思是当一个线程修改一个共享变量时,另一个线程能读取到这个修改的值。

如果volatile变量修饰符使用恰当的话,他比Synchronized的使用和执行成本低,因为他不会引起上下文切换和调度。 如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
volatile是如何保证可见性的那?我们通过X86处理器下通过工具获取JIT编译器的汇编指令来查看volatile进行写操作时,CPU会做什么?
Java代码如下: instance = new Singleton(); //instance是volatile变量
转变成汇编代码: 0x01a3deld: movb $0×0,0×1104800(%esi);0x01a3de24: lock add1 $0×0
根据汇编语言,Lock前缀的指令在多核处理器下会引发两件事情:
1)将当前处理器缓存行的数据写回到系统内存。
2)这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。

Synchronized的实现原理与应用
Java中的每一个对象都可以作为锁,具体表现为以下三种形式:
1)对于普通同步方法,锁是当前对象。
2)对于静态同步方法,锁是当前类的Class对象。
3)对于同步方法块,锁是Synchronized括号里配置的对象。
那么Synchronized的锁到底存在哪里那?
Synchronized用的锁是存在Java对象头里的。Java对象头里的Mark Word默认存储对象的Hashcode,分代年龄和锁标记位。为了减少获得锁和释放锁带来的性能消耗,引入了”偏向锁“和”轻量级锁“。在1.6中,锁一共有四种状态,级别从低到高依次是:”无锁状态“,”偏向锁状态“,”轻量级锁状态“,”重量级锁状态“,这几个状态会随着竞争情况逐渐升级,而且不能降级。

1)偏向锁
HotSpot虚拟机的作者经过研究发现,大多数情况下,锁不仅不存在多线程竞争,而且总是由同一个线程多次获得。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程的ID,以后该线程进入和退出同步块时不需要进行CAS操作。
2)偏向锁的释放
偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
3)轻量级锁加锁
线程在执行同步块之前,JVM会先在当前线程的栈帧中创建用于存储锁记录的空间。线程尝试使用CAS将对象头中的Mark Word替换为指向锁记录的指针,如果成功,当前线程获得锁。如果失败,表示其他线程竞争,继续自旋。
4)轻量级解锁
轻量级解锁时,会使用原子的CAS操作将原来的Mark Word记录替换回对象头,如果成功,则表示没有竞争发生。如果失败,表示当前锁存在竞争,锁就会膨胀成重量级锁。
因为自旋会消耗CPU,为了避免无用的自旋,一旦锁升级成重量级锁就不会再回复成轻量级的状态。此时所有视图获得这个锁的线程都会被阻塞。
在这里插入图片描述

原子操作意为不可中断的一个或一系列操作。Java中可以通过锁或者循环CAS方式来实现原子操作。

在Java并发编程中,需要处理两个关键问题:线程之间如何通信以及线程之间如何同步。通信是指线程之间以何种机制来交换信息,主要有两种,共享内存和消息传递。同步是指程序中用于控制不同线程间操作发生相对顺序的机制。

Java线程之间的通信由Java内存模型(JMM)控制,JMM决定了一个线程对共享变量的的写入何时对另一个线程可见。线程之间的共享变量存储在主内存,每个线程都有一个本地副本,线程对于变量的操作都在本地副本中进行。JMM通过控制主内存和每个线程的本地内存之间的交互,来为Java程序员提供内存可见性保证。

JMM通过禁止特定类型的编译器重排序和处理器重排序,为程序员提供一致性的内存可见性保证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值