美文网首页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