原子类
原子类的实现原理
原子类的原理主要是使用了volatile+cas锁来实现原子性更新
以下就是原子类这一类java类中共有的核心代码:
private static final Unsafe U = Unsafe.getUnsafe();
private volatile int value;
public final boolean compareAndSet(int expectedValue, int newValue) {
return U.compareAndSetInt(this, VALUE, expectedValue, newValue);
}
可以看到原子类中有一个Unsafe类型的对象,Unsafe类是java不对外开放的可以直接操作内存的类,其实现的compareAndSet()就是cas操作,也就是说cas操作是底层硬件支持的,其工作原理如下:
当线程执行CAS操作时,它会检查主内存中的值是否仍然与预期原值相匹配。如果匹配,则更新内存位置的值;如果不匹配,则操作失败。
示例说明:
假设有一个共享变量
x
,其初始值为0
,存储在主内存的某个位置。
- 线程1 读取
x
的值为0
,并想要将其更新为1
。- 线程2 也读取
x
的值为0
,并想要将其更新为2
。在执行CAS操作之前,两个线程都会持有
x
的值的副本,即0
。当线程1尝试执行CAS操作时,它会检查主内存中x
的值是否仍然是0
。如果是,则线程1将x
的值更新为1
。如果线程2随后尝试执行CAS操作,它会发现主内存中x
的值不再是0
(已经被线程1更新为1
),因此CAS操作失败。
同时在原子类中使用了volatile关键词修饰使得在执行对共享数据操作的时候,线程的副本会更新,同时共享内存下的数据也会更新,Cas在比对时就是共享内存下新的数据,从而让其它线程修改失败。
ABA问题
假设我们有两个线程:线程A、线程B。还有一个共享变量
value
,其初始值为A
。
- 线程A 读取
value
的值为A
,并打算将其更新为B
。- 线程B 也读取
value
的值为A
,并将其更新为C
。- 线程B 再次将
value
的值从C
更新回A
。- 线程A 执行CAS操作,尝试将
value
的值从A
更新为B
。由于线程A读取
value
的值时为A
,并且在线程A尝试执行CAS操作时,value
的值仍然是A
,因此CAS操作成功。但实际上,value
的值已经从A
变为C
再变回A
。这就产生了误导性的结果,线程A以为没有任何其他线程修改过value
的值。通常可以采取以下几种策略:
- 使用版本号:为共享变量添加一个版本号或时间戳,每次修改变量时都更新版本号。这样即使值相同,版本号也会不同,从而避免了ABA问题。
- 使用带有版本号的原子类:Java中提供了
AtomicStampedReference
类,它允许跟踪一个引用和一个版本号(也称为标记)。这样即使引用本身没有变化,版本号也会随着每次更新而改变,从而解决了ABA问题。
为什么引入LongAdder
使用CAS这类乐观锁不会阻塞线程,减少了上下文的开销,故而使用原子类在高并发场景下有很大的性能提升,但由于使用了volatile,每个线程增加了一个刷新到共享内存的开销,而且在并发程度高的时候线程容易CAS失败,于是就引入了LongAdder类,当并发度不高时默认使用base来自增,当并发度高的时候会使用一个cell数组,同时根据每个线程的hashcode来分配给线程各自的cell位置,每个线程只用负责自增自身内存的数据,然后LongAdder会使用一个sum来将base和cell值累加起来。
并发工具
Exchanger
概念
Exchanger
是 Java 中的一个并发工具类,它允许两个线程在一个特定的点上交换数据。当两个线程都到达这个交换点时,它们会交换各自携带的数据并继续执行。
代码演示
import java.util.concurrent.Exchanger;
public class ExchangerExample {
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Thread threadA = new Thread(() -> {
String dataA = "Data from A";
try {
String dataB = exchanger.exchange(dataA);
System.out.println("Thread A received: " + dataB);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread threadB = new Thread(() -> {
String dataB = "Data from B";
try {
String dataA = exchanger.exchange(dataB);
System.out.println("Thread B received: " + dataA);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
threadA.start();
threadB.start();
try {
threadA.join();
threadB.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
场景描述
假设你需要在两个线程之间同步交换数据,比如一个线程负责收集数据,另一个线程负责处理这些数据。你可以使用 Exchanger
来确保数据的同步交换。
CountDownLatch
概念
CountDownLatch
是一个同步辅助类,允许一个或多个线程等待其他线程完成操作。它通过一个计数器来控制线程的等待行为,当计数器减到0时,等待的线程会被释放。
代码演示
import java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " started");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " finished");
latch.countDown(); // 减少计数器
}).start();
}
System.out.println("Main thread waiting...");
latch.await(); // 等待计数器减到0
System.out.println("All tasks completed.");
}
}
场景描述
假设你有一个主程序,需要等待所有子线程完成一些任务后再继续执行。你可以使用 CountDownLatch
来确保主程序不会提前继续执行,直到所有的子线程都完成了它们的任务。
Semaphore
概念
Semaphore
是一个用于控制对有限数量的资源的访问的并发工具类。它可以用于模拟信号量的行为,限制同时访问共享资源的线程数量。
代码演示
import java.util.concurrent.Semaphore;
public class SemaphoreExample {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3); // 最多允许3个线程同时访问
for (int i = 0; i < 5; i++) {
new Thread(() -> {
try {
semaphore.acquire(); // 请求许可
System.out.println(Thread.currentThread().getName() + " is accessing a resource");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " finished accessing");
semaphore.release(); // 释放许可
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
场景描述
假设你有一个服务,需要限制同时处理请求的客户端数量,以防止资源过度消耗。你可以使用 Semaphore
来限制同时处理请求的线程数量。
CyclicBarrier
概念
CyclicBarrier
是一个让一组线程相互等待,直到到达某个公共屏障点的工具。当所有参与线程到达屏障点时,它们会被释放并继续执行。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(3);
for (int i = 0; i < 3; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " started");
try {
Thread.sleep(1000);
barrier.await(); // 等待所有线程到达
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " passed the barrier");
}).start();
}
}
}
场景描述
假设你需要在多个线程之间同步执行某项操作,比如在多线程环境中启动一个任务,要求所有线程都准备好了才能一起开始。你可以使用 CyclicBarrier
来确保所有线程都到达了某个公共的屏障点后再继续执行。
ps:CountDownLatch和CyclicBarrier前者专注于count的数量是否达标,也就是无论是一个线程执行的countDown还是一个任务执行的countDown只要将count减为零被await()的线程就能被唤醒,而后者专注于线程数,每个线程中需要使用await来等待线程数到规定的数量,同时前者是不可复用的,计数器的数字不会重置,而后者是可复用的。