【笔记】Java并发编程之美-并发编程的其他基础知识

volatile关键字

确保对一个变量更新对其他线程马上可见(写入变量不把值缓存在寄存器/其他地方,直接刷新回主内存),其他线程读取该值时,直接从主内存读取。
一般在以下情况使用:
1.写入变量值不依赖变量的当前值时。(因为若依赖,将是获取-计算-写入三步,这三步不是原子性的,volatile不保证原子性)
2.读写变量值时没有加锁。(加锁本身已保证内存可见性)

CAS(Compare And Swap)操作

非阻塞原子性操作。对于内存中的某一个值V,提供一个旧值A和一个新值B。如果提供的旧值V和A相等就把B写入V。这个过程是原子性的。
ABA问题:如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。
通过时间戳避免ABA问题。

Unsafe类

硬件级别的原子级操作,都是native方法。

Java指令重排序

Java内存模型允许编译器和处理器对指令重排序以提高运行性能,且只会对没有数据依赖性的指令重排序。重排序在多线程下会导致非预期程序执行结果,使用volatile修饰变量就可以保证:写时,该变量之前的操作不会被重排序到该变量之后;读时,该变量之后的操作不会额比重排序到该变量之前。

伪共享

由于存放到cache行的是内存块而不是单个变量,所以可能会多个变量在同一行,当多个线程同时修改一行的多个变量时,由于只能有一个线程操作缓存行,所以相比将每个变量放在一个缓存行,性能有所下降,这就是伪共享。

避免伪共享:
JDK8之前,字节填充(创建变量时使用填充字段填充该变量所在缓冲行)。
JDK8提供sun.misc.Contended注解。

锁的概述

乐观锁与悲观锁

悲观锁指对数据被外界修改持保守态度,认为数据很容易就会被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据处理过程中,使数据处于锁定状态。
悲观锁的实现往往依靠数据库提供的锁机制,即在数据库中,在对数据记录操作前给记录加排它锁。如果获取锁失败,则说明数据正在被其他线程修改,当前线程则等待或者抛出异常。如果获取锁成功,则对记录进行操作,然后提交事务后释放排它锁。
乐观锁并不使用数据库提供的锁机制,一般在表中添加version字段或用业务状态实现。乐观锁直到提交时才锁定,所以不会产生死锁。

悲观锁适合多写场景,乐观锁适合多读场景。

公平锁与非公平锁

公平锁:线程获取锁的顺序是请求锁的早晚决定。
非公平锁:运行时闯入,先来不一定先得。

独占锁与共享锁

独占锁:任何时候只能一个线程得到锁。
共享锁:可由多个线程持有。

可重入锁

某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
synchronized内部锁就是可重入锁。

自旋锁

当前线程获取锁时,若锁已被其他线程占有,它不马上阻塞自己(若线程被阻塞,需要从用户态到内核态,开销大,影响并发性能),而是在不放弃CPU使用权情况下,多次尝试获取(默认10次)。
自旋锁是使用 CPU 时间换取线程阻塞、调度开销。

猜你喜欢

转载自blog.csdn.net/Ethan_997/article/details/109060765