美文网首页java面试
二、并发编程与线程安全

二、并发编程与线程安全

作者: AKyS佐毅 | 来源:发表于2018-03-22 16:39 被阅读127次

1、并发模拟

  • Postman:Http请求模拟工具
  • Apache附带的工具
  • JMeter

2、并发的测试代码

3、线程安全性

  • 当多个线程访问某个类时,不管运行环境采用何种调度方式或者这些进程将如何交替进行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称为线程安全的。

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

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

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

4、Atomic包

  • 1、AtomicXXX: CAS、Unsafe.compareAndSwapInt。
    AtomicInteger.getAndIncrement的源码
 /**
  * Atomically increments by one the current value.
  *
  * @return the previous value
  */
 public final int getAndIncrement() {
      // 主要是调用了unsafe的方法 
      //     private static final Unsafe unsafe = Unsafe.getUnsafe();
     return unsafe.getAndAddInt(this, valueOffset, 1);
 }
  /**
*  获取底层当前的值并且+1
* @param var1 需要操作的AtomicInteger 对象
* @param var2 当前的值 
* @param var4 要增加的值
*/
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 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;
   }
  • 2、AtomicLong、LongAdder。
   // 请求总数
 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();
 }
  // 请求总数
    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();
    }
  • LongAdder在AtomicLong的基础上将单点的更新压力分散到各个节点,在低并发的时候通过对base的直接更新可以很好的保障和AtomicLong的性能基本保持一致,而在高并发的时候通过分散提高了性能。
  • 缺点是LongAdder在统计的时候如果有并发更新,可能导致统计的数据有误差。
    • 3、AtomicReference、AtomicReferenceFieldUpdater。
   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());
    }
 private static AtomicIntegerFieldUpdater<AtomicExample5> updater =
            AtomicIntegerFieldUpdater.newUpdater(AtomicExample5.class, "count");

    @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());
        }
    }
  • 4、 AtomicBoolean,在这个Boolean值的变化的时候不允许在之间插入,保持操作的原子性。方法和举例:compareAndSet(boolean expect, boolean update)。这个方法主要两个作用 :

      1. 比较AtomicBoolean和expect的值,如果一致,执行方法内的语句。其实就是一个if语句
      1. 把AtomicBoolean的值设成update 比较最要的是这两件事是一气呵成的,这连个动作之间不会被打断,任何内部或者外部的语句都不可能在两个动作之间运行。为多线程的控制提供了解决的方案。
   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");
      }
  }

5、synchronized:依赖JVM实现锁,因此在这个关键字作用对象的作用范围内,都是同一时刻只能有一个线程进行操作的。

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

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

6、Lock:依赖特殊的CPU指令,代码实现,ReentrantLock.

可中断锁,多样化同步,竞争激烈时能保持常态。

7、线程安全性 - 可见性

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

  • 线程交叉执行。

  • 重排序结合线程交叉执行。

  • 共享变量更新后的值没有在工作内存与主存间及时更新。

  • JMM关于synchronized的两条规定

    • 线程解锁前,必须把共享变量的最新值刷新到主内存中。
    • 线程加锁时,将清空工作内存中共享变量的值,从而使用共享变量时需要从主内存中重新读取最新的值。(加锁和解锁必须是同一把锁)
  • volatile
    通过加入内存屏障和禁止重排序优化来实现。

    • 对volatile变量写操作时,会在写操作后加入一条store平展执行,将本地内存中的共享变量值刷新到主内存中。
    • 对volatile变量读操作时,会在读操作前加入一条load屏障指令,从主内存中读取共享变量。
 // 请求总数
    public static int clientTotal = 5000;

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

    public static volatile int count = 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);
    }

    private static void add() {
        count++;
        // 1、count
        // 2、+1
        // 3、count
    }
  • volatile使用条件
    • 1.对变量写操作不依赖于当前值
    • 2.该变量没有包含在具有其他变量的不必要的式子中

微信扫码关注java技术栈,每日更新面试题目和答案,并获取Java面试题和架构师相关题目和视频。

相关文章

网友评论

    本文标题:二、并发编程与线程安全

    本文链接:https://www.haomeiwen.com/subject/taaoqftx.html