高并发与多线程网络学习笔记(七)并发包Atomic&CountDownLatch&CyclicBar
Atomic类
比volatile要好,它满足了线程安全的三个要求
- 原子性
- 可见性
- 有序性
AtomicInteger
AtomicXXX的其他类型都是大同小异
private volatile int value;
public final int get() //获取当前的值
public final int set(int value) //设置当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
- 基本数据类型的get/set方法不保证原子性,只能保证volatile,因为是不可变对象
- AtomicXXArray的get/set()方法保证原子性
XXAndXX方法包证原子性(示例如下)
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
CAS(CompareAndSet)原理
核心思想
这是一条硬件级别的原子性操作,对比当前内存值若与current一致,则用新值next更新。整个过程CPU保证了原子性。(从代码级别来看就像无锁因为没有lock,但其实硬件级别上加锁)
使用建议
老师说用atomic就挺好的,不需要volatile,性能上不会差很多。
ABA问题
如果另一个线程修改V值假设原来是A,先修改成B,再修改回成A。当前线程的CAS操作无法分辨当前V值是否发生过变化。(这样看起来好像没有问题,毕竟它又改回A和原来一样了)
- 链表head->A->B->C,变成head->A->C,线程并不知道B删除,仍赋值为head->B->C
- 链表head->A->B->C,变成head->A->C,线程并不知道B删除,仍赋值为head->C
- 账户100元,小明取50元,别人汇给他50元,诈骗分子偷了50元,小明无法知道别人汇钱和偷钱。
总结:A->B->A,会漏掉一段时间窗口对A的监控,也就是说当你关心A的变化过程就要注意,如果你只是关心A是否变化就不会出现ABA。
解决方案
AtomicStampedRefence
每次操作都有一个记录戳,每次比较不仅比较值还比较记录戳
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp) {
// 获取当前的(元素值,版本号)对
Pair<V> current = pair;
return
// 引用没变
expectedReference == current.reference &&
// 版本号没变
expectedStamp == current.stamp &&
// 新引用等于旧引用
((newReference == current.reference &&
// 新版本号等于旧版本号
newStamp == current.stamp) ||
// 构造新的Pair对象并CAS更新
casPair(current, Pair.of(newReference, newStamp)));
}
private boolean casPair(Pair<V> cmp, Pair<V> val) {
// 调用Unsafe的compareAndSwapObject()方法CAS更新pair的引用为新引用
return UNSAFE.compareAndSwapObject(this, pairOffset, cmp, val);
}
AtomicXXXFieldUpdater
核心思想
对一个对象里没有atomic修饰的属性进行原子性的封装和操作。
这些在JAVA类库中用的较多,自己用的较少,比起使用atomic节约一点点空间,在大量元素需要定义为atomic时可以用。
public class Test{
private static AtomicIntegerFieldUpdater<Test> update = AtomicIntegerFieldUpdater.newUpdater(Test.class, "a");
private static Test test = new Test();
public volatile int a = 100;
public static void main(String[] args){
for(int i=0; i<100;i++){
Thread t = new Thread(new Runnable() {
@Override
public void run() {
if(update.compareAndSet(test, 100, 120)){
System.out.print("已修改");
}
}
});
t.start();
}
}
}
使用前提
- 属性必须是public&&volatile
- targetObject不能是null
- 属性类型和更新类型要一致
Unsafe
非常强大,它可以直接操作内存,内部实现是C++代码,没有办法修改,只能使用。当然不推荐使用。
- 可以绕过构造函数实例化
- 可以直接为变量开辟内存空间
- 可以直接给类的变量赋值
- 可以读取字节码文件构建实例
- 可以拿到父类
- 可以计算类的内存偏移量
我觉得基本上不会主动使用它,所以就不详细看了。
并行串行
使用场景

- 在串行化的过程中把部分改为并行化
- 在并行化的过程中把部分改为串行化
CountDownLatch
CountDownLatch(5) //初始化计数值,必须大于等于0
latch.countDown() //计数值自减,如果当前值为0无操作
latch.await() //只要计数值不等于0就会blocked住
latch.await(time.TimeUnit.MILLISECONS) //超时就不会等了
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(5);
Service service = new Service(latch);
Runnable task = () -> service.exec();
for (int i = 0; i < 5; i++) {
Thread thread = new Thread(task);
thread.start();
}
System.out.println("main thread await. ");
latch.await();
System.out.println("main thread finishes await. ");
}
}
public class Service {
private CountDownLatch latch;
public Service(CountDownLatch latch) {
this.latch = latch;
}
public void exec() {
try {
System.out.println(Thread.currentThread().getName() + " execute task. ");
sleep(2);
System.out.println(Thread.currentThread().getName() + " finished task. ");
} finally {
latch.countDown();
}
}
private void sleep(int seconds) {
try {
TimeUnit.SECONDS.sleep(seconds);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
CyclicBarrier
之所以会称为cyclic,就是当所有线程到达同步点唤醒之后,它会自己重新初始化,可以继续使用cb.await()进行阻塞。
CyclicBarrier(3) //初始化计数值(多少个线程同时执行到同步点)
CyclicBarrier(3,runnable)//当计数值等于等待线程数时执行的回调函数,执行完才唤醒线程,执行线程为最后一个线程
cb.await() //如果当前等待线程数不等于计数值就blocked住,并将等待数自增1;否则唤醒所有线程继续执行
getNumberWaiting() //获取等待线程数
getParties() //获取初始化值
public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);//创建CyclicBarrier对象并设置3个公共屏障点
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点1,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
cb.await();//到此如果没有达到公共屏障点,则该线程处于等待状态,如果达到公共屏障点则所有处于等待的线程都继续往下运行
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点2,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
cb.await();
Thread.sleep((long)(Math.random()*10000));
System.out.println("线程" + Thread.currentThread().getName() +
"即将到达集合地点3,当前已有" + cb.getNumberWaiting() + "个已经到达,正在等候");
cb.await();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
对比
