高并发与多线程网络学习笔记(七)并发包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++代码,没有办法修改,只能使用。当然不推荐使用。

  • 可以绕过构造函数实例化
  • 可以直接为变量开辟内存空间
  • 可以直接给类的变量赋值
  • 可以读取字节码文件构建实例
  • 可以拿到父类
  • 可以计算类的内存偏移量

我觉得基本上不会主动使用它,所以就不详细看了。

并行串行

使用场景

  1. 在串行化的过程中把部分改为并行化
  2. 在并行化的过程中把部分改为串行化

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();
    }
}

对比

 

 

posted @ 2021-01-17 15:02  王者之剑KO  阅读(80)  评论(0)    收藏  举报