Java并发编程与高并发解决方案--线程安全性

版权声明: https://blog.csdn.net/qq_40722284/article/details/85089033

目录

原子性

Atomic包--CAS(重点)

CAS原理

CAS的ABA问题

LongAdder与AtomicLong

LongAdder

 AtomicLong

AtomicLong和LongAdder区别(重点)

AtomicReference与AtomicIntegerFieldUpdater

AtomicReference

 AtomicIntegerFieldUpdater

AtomicBoolean(可用于要求只执行一次的场景)

锁--synchronized

原子性对比

可见性

可见性-Synchronized

可见性-volatile

有序性

happens-before原则


原子性:提供了互斥访问,同一时刻只能有一个线程来对他进行操作

可见性:一个线程对主内存的修改可以及时的被其他线程观察到

有序性:一个线程观察其他线程中的指令执行顺序,由于指令重排序的存在,该观察结果一般杂乱无序

原子性

Atomic包--CAS(重点)

AtpmicXXX:CAS、 unsafe.compareAndSwapInt

@Slf4j
@ThreadSafe
public class ConcurrencyExample2 {

   //请求总数
   public static int clientTotal = 5000;

   //同时并发执行的线程数
   public static int threadTotal = 200;

   public static AtomicInteger count = new AtomicInteger(0);

   public static void main(String[] args) {
      ExecutorService exc = newCachedThreadPool();
      final Semaphore semaphore =new Semaphore(threadTotal);
      final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);

      for (int i = 0;i < clientTotal;i++) {
         exc.execute(() -> {
            try {
               semaphore.acquire();
               add();
               semaphore.release();
            } catch (Exception e) {
               log.error("exception", e);
            }
            countDownLatch.countDown();
         });
      }

      try {
         countDownLatch.await();
      } catch (InterruptedException e) {
         e.printStackTrace();
      }
      exc.shutdown();
      log.info("count:{}",count.get());
   }

   private static void add(){
      //count++;
      //count.getAndIncrement();
      count.incrementAndGet();
   }
}
public final int incrementAndGet() {
    return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); //java底层方法

public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);

CAS原理

var1:传入的对象 var2:当前的值 var5:点用户底层方法得到底层当前值,没有其他线程来处理count这个对象时,应等于var2.

 compareAndSwapInt(var1, var2, var5, var5 + var4)

当前count的这个对象var1,如果当前的值var2和底层的值var5相等,则替换成var5+var4;

CAS的ABA问题

AtomicStampedReference类

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) ||
         casPair(current, Pair.of(newReference, newStamp)));
}
 

在CAS操作的时候,其他线程将变量A的值改成了B,又改回了A,本线程使用期望值A与当前变量进行比较时发现A变量没有变,于是CAS 就将A值进行了交换操作,实际上该值已被其他线程改变过,这与设计思想不符合。

解决思路:

每次变量更新的时候,将变量版本号加1,只要该变量被其他线程更改过,版本号就会发生递增变化。

LongAdder与AtomicLong

LongAdder

@Slf4j
@ThreadSafe
public class AtomicExample3 {
    // 请求总数
    public static int clientTotal = 5000;
    // 同时并发执行的线程数
    public static int threadTotal = 200;
    public static LongAdder count = new LongAdder();
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }
    private static void add() {
        count.increment();
    }
}
 

 AtomicLong

 @Slf4j
@ThreadSafe
public class AtomicExample2 {
    // 请求总数
    public static int clientTotal = 5000;
    // 同时并发执行的线程数
    public static int threadTotal = 200;
    public static AtomicLong count = new AtomicLong(0);
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count.get());
    }
 
    private static void add() {
        count.incrementAndGet();
        // count.getAndIncrement();
    }
}

AtomicLong和LongAdder区别(重点)

扩展知识点:对普通类型的Long和double变量,jvm允许将64位的读操作或写操作拆成2个32位的操作

LongAddr核心

将热点数据分离,可以将AtomicLong内部核心数据value,分离成为一个数组,每个线程访问时,通过hash等算法映射到其中一个数字进行计数,而最终的计算结果为这个数组求和累加。热点数据value被分离成多个cell,每个cell独立维护内部值,当前实际值有每个cell累积合成。热点就被有效分离,提高了并行度。LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点上,在低并发的时候通过对base的直接更新,可以很好的保障和AtomicLong性能基本一致。而在高并发的时候提高分散提高性能。

LongAddr缺陷

在统计的时候如果有并发更新,可能会导致统计的数据有误差,实际使用中有高并发计数的时候,我们可以优先使用LongAddr,而不是继续使用AtomicLong,当然在线程竞争很低的情况下进行计数,使用AtomicLong还是更简单,更直接一些,并且效率会稍高一点。
 

AtomicReference与AtomicIntegerFieldUpdater

AtomicReference


@Slf4j
@ThreadSafe
public class AtomicExample4 {
    private static AtomicReference<Integer> count = new AtomicReference<>(0);
    public static void main(String[] args) {
        count.compareAndSet(0, 2); // 2
        count.compareAndSet(0, 1); // no
        count.compareAndSet(1, 3); // no
        count.compareAndSet(2, 4); // 4
        count.compareAndSet(3, 5); // no
        log.info("count:{}", count.get());
    }
}

 AtomicIntegerFieldUpdater

 @Slf4j
@ThreadSafe
public class AtomicExample5 {
    private static AtomicIntegerFieldUpdater<AtomicExample5> updater = AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");  //字段被volatile修饰,非static
    @Getter
    public volatile int count = 100;
    public static void main(String[] args) {
        AtomicExample5 example5 = new AtomicExample5();
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 1, {}", example5.getCount());
        }
        if (updater.compareAndSet(example5, 100, 120)) {
            log.info("update success 2, {}", example5.getCount());
        } else {
            log.info("update failed, {}", example5.getCount());
        }
    }
}

AtomicBoolean(可用于要求只执行一次的场景

@Slf4j
@ThreadSafe
public class AtomicExample6 {
    private static AtomicBoolean isHappened = new AtomicBoolean(false);
    // 请求总数
    public static int clientTotal = 5000;
    // 同时并发执行的线程数
    public static int threadTotal = 200;
    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    test();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("isHappened:{}", isHappened.get());
    }
    //只会执行一次
    private static void test() {
        if (isHappened.compareAndSet(false, true)) {  //可用于要求只执行一次的场景
            log.info("execute");

        }
    }
}

锁--synchronized

synchronized:同步锁,依赖JVM实现锁,作用对象的作用范围内,都是同一时刻只有一个线程操作

修饰的地方

  • 修饰代码块:大括号括起来的代码,作用于调用的对象
  • 修饰方法:整个方法,作用于调用的对象
  • 修饰静态方法:整个静态方法,作用于所有对象
  • 修饰类:synchronized后面括号括起来的部分,作用于所有对象

Lock:提供Lock接口类依赖于特殊的CPU指令,代码实现ReentrantLock

@Slf4j
public class SynchronizedExample1 {
    // 修饰一个代码块
    public void test1(int j) {
        synchronized (this) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }
 
    // 修饰一个方法
    public synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }
 
    public static void main(String[] args) {
        SynchronizedExample1 example1 = new SynchronizedExample1();
        SynchronizedExample1 example2 = new SynchronizedExample1();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            example1.test2(1);
        });
        executorService.execute(() -> {
            example2.test2(2);
        });
    }
}

 @Slf4j
public class SynchronizedExample2 {
    // 修饰一个类
    public static void test1(int j) {
        synchronized (SynchronizedExample2.class) {
            for (int i = 0; i < 10; i++) {
                log.info("test1 {} - {}", j, i);
            }
        }
    }
    // 修饰一个静态方法
    public static synchronized void test2(int j) {
        for (int i = 0; i < 10; i++) {
            log.info("test2 {} - {}", j, i);
        }
    }
    public static void main(String[] args) {
        SynchronizedExample2 example1 = new SynchronizedExample2();
        SynchronizedExample2 example2 = new SynchronizedExample2();
        ExecutorService executorService = Executors.newCachedThreadPool();
        executorService.execute(() -> {
            example1.test1(1);
        });
        executorService.execute(() -> {
            example2.test1(2);
        });
    }

原子性对比

synchronized:不可中断锁,适合竞争不激烈,可读性好

Lock:可中断锁,多样化同步,竞争激烈时能维持常态

Atomic:竞争激烈时能维持常态,比Lock性能好;但是只能同步一个值

可见性

导致共享变量在线程间不可见的原因

  • 线程交叉执行
  • 重排序结合线程交叉执行
  • 共享变量更新后的值没有在工作内存与主内存间及时更新

可见性-Synchronized

JMM关于Synchronized的两条规定

  1. 线程解锁前,必须把共享变量的最新值刷新到主内存
  2. 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取最新值(注意:加锁与解锁是同一把锁)

可见性-volatile


通过加入内存屏障禁止重排序优化来实现

  • 对volatile变量写操作时,会在写操作后加入一条store屏障指令,将本地内存中的共享变量值刷新到主内存
  • 对volate变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量

换句话说:volate变量被线程访问时,读操作会强迫从主内存读取变量值,当写操作时,会强迫将修改值舒心到主内存。

  

 public static volatile int count = 0;

。。。。。。

private static void add() {

count++;

        // 1、count  --取得当前值

        // 2、+1  --加1

        // 3、count -- 刷新到主存

}      //假如两个进程同时得到当前值,但是他们同时进行了加1 操作,然后就漏掉了一次加1操作。 

volatile 不适合累加场景,只能在有限的一些情形下使用 volatile 变量替代锁。但适合状态变量。还适合doublecheck场景.

volatile boolean inited = false;
//线程1
context = loadContext();
inited = true;
//线程2
while(!inited){
sleep();
}
doSomethingWithConfig(context)

 要使 volatile 变量提供理想的线程安全,必须同时满足下面两个条件:

  1. 对变量的写操作不依赖于当前值。
  2. 该变量没有包含在具有其他变量的不变式中。

      实际上,这些条件表明,可以被写入 volatile 变量的这些有效值独立于任何程序的状态,包括变量的当前状态。
第一个条件的限制使 volatile 变量不能用作线程安全计数器。虽然增量操作(x++)看上去类似一个单独操作,实际上它是一个由读取-修改-写入操作序列组成的组合操作,必须以原子方式执行,而 volatile 不能提供必须的原子特性。实现正确的操作需要使 x 的值在操作期间保持不变,而 volatile 变量无法实现这点。(然而,如果将值调整为只从单个线程写入,那么可以忽略第一个条件。)

    大多数编程情形都会与这两个条件的其中之一冲突,使得 volatile 变量不能像 synchronized 那样普遍适用于实现线程安全。

有序性

Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序的过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。

保证有序性方法
volatile 、snychronized、 Lock

happens-before原则

如果两个执行操作不能通过h-b原则推导出来,则不能保证执行顺序,虚拟机会随意的对其进行重排序

  1. 程序顺序序规则:一个线程内,按照代码执行,书写在前面的操作先行发生于书写在后面的操作。
  2. 监视器锁定规则:一个unLock操作先行发生于后面对同一个锁的lock操作
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作
  4. 传递性规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C
  5. 线程启动原则:Thread对象的start()方法先行发生于此线程的每一个动作
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()方法返回值手段检测到线程已经终止执行
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始

猜你喜欢

转载自blog.csdn.net/qq_40722284/article/details/85089033