zoukankan      html  css  js  c++  java
  • 《实战Java高并发程序设计》读书笔记四

    第四章 锁的优化及注意事项

    1、锁性能的几点建议

    减小锁持有时间:

    • 系统持有锁时间越长锁竞争程度就越激烈,只对需要同步的方法加锁,可以减小锁持有时间进而提高锁性能。
    • 减少锁的持有时间有助于降低锁冲突的可能性,进而提高锁的并发能力。

    减小锁粒度:

    • 减小锁粒度就是指缩小锁定对象的范围,从而减小锁冲突的可能性,进而提高并发能力。

    读写分离锁代替独占锁(锁分离):

    • 使用读写锁可以减少操作之间相互等待,可以有效的提高性能。ConcurrentLinkedQueue中take和put方法分别使用了两个锁避免了锁竞争,提高了性能。

    锁粗化:

    • 如果一个锁不停的被进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能优化。
    • 虚拟机在遇到一连串连续的对同一锁不断进行请求和释放的操作时,便会把所有的操作整合成对锁的一次请求,从而减少锁的请求同步次数,这个操作叫锁粗化

    2、Java虚拟机对锁优化所做的努力

    锁偏向:

    • 如果一个线程获得了锁,那么锁就进入了偏向模式。当这个线程再次请求锁时,无需再做任何同步操作。
    • 使用Java虚拟机参数-XX:USerBiasedLocking可以开启偏向锁。

    轻量级锁:

    • 如果偏向锁失败,虚拟机并不会立即挂起线程,而是使用轻量级锁进行操作。
    • 轻量级锁他只是简单的将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。
    • 如果线程获得轻量级锁成功,则可以顺利进入临界区。如果轻量级锁加锁失败,则表示其他线程抢先夺到锁,那么当前线程的轻量级锁就会膨胀为重量级锁。

    自旋锁:

    • 无法获得锁,不知道什么时候可以获得锁,不会把线程挂起而是让当前线程做几个空循环(这也就是自旋锁的意义)。
    • 若经过几个空循环可以获取到锁则进入临界区,如果还是获取不到则系统会真正的挂起线程。

    锁消除

    • 去除不可能存在共享资源竞争的锁,通过锁消除可以节省毫无意义的请求锁时间。如在不可能存在并发的场合使用vector。

    3、ThreadLocal

    ThreadLocal的使用:

    • ThreadLocal是线程的局部变量,只有当前线程才可以访问,因此是线程安全的。
    • ThreadLocal只是起到容器的作用,为每一个线程分配不同对象需要应用层面保证。
      package com.ecut.threadlocal;
      
      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class ThreadLocalTest {
          private static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>();
      
          public static class ParseDate implements Runnable {
              int i = 0;
      
              public ParseDate(int i) {
                  this.i = i;
              }
      
              @Override
              public void run() {
                  try {
                      if (threadLocal.get() == null) {
                          threadLocal.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
                      }
                      Date d = threadLocal.get().parse("2019-03-03 19:36:15");
                      System.out.println(i + "、" + Thread.currentThread() + ":" + d);
                  } catch (ParseException e) {
                      e.printStackTrace();
                  }
      
                  // sdf.format(System.currentTimeMillis());
              }
          }
      
          public static void main(String[] args) {
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              ParseDate parseDate = new ParseDate(1);
              for (int i = 0; i < 10; i++) {
                  executorService.submit(new ParseDate(i));
              }
          }
      }

    ThreadLocal的实现原理:

    • 在set时,首先通过getMap()获得当前线程的ThreadLocalMap,并将值设入到这个ThreadLocalMap中,当前线程做为key,需要的值为value。
          public void set(T value) {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null)
                  map.set(this, value);
              else
                  createMap(t, value);
          }
    • 在get时,获得ThreadLocalMap的对象,然后通过将自己做为key 取得相应的数据。
        public T get() {
              Thread t = Thread.currentThread();
              ThreadLocalMap map = getMap(t);
              if (map != null) {
                  ThreadLocalMap.Entry e = map.getEntry(this);
                  if (e != null) {
                      @SuppressWarnings("unchecked")
                      T result = (T)e.value;
                      return result;
                  }
              }
              return setInitialValue();
          }
    • ThreadLocalMap维护的变量是在Thread内部的,线程不退出对象的引用就会一直存在。使用线程池时线程未必会退出,可能会造成内存泄漏(对象无法回收),因此最好要使用ThreadLocal的remove方法。

    4、无锁

    无锁的好处:

    • 由于非阻塞性,对死锁天生免疫
    • 没有锁竞争带来的系统开销
    • 没有锁调度带来的系统开销

    CAS算法:

    • 包含三个参数CAS(V,E,N),V表示要被修改的变量,E表示期望值,N表示要设置的值。
    • 如果V的值和期望值相等则将V的值设置为N,如果不相等表示V的值已经被其他线程修改了,当前线程不做任何操作。
    • 当有多个线程访问的时候只有一个线程成功,其他线程会被告知失败,其他线程可以选择再次执行或者是放弃。

    无锁线程安全的整数:

    • AtomicInteger相比,它是可变的并且线程安全的。
      package com.ecut.atomic;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.atomic.AtomicInteger;
      
      public class AtomicIntegerTest {
      
          static AtomicInteger i = new AtomicInteger();
      
          public static class AtomicIntegerThread implements Runnable {
              @Override
              public void run() {
                  for (int k = 0; k < 10000; k++) {
                      i.incrementAndGet();
                  }
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              AtomicIntegerThread atomicIntegerThread = new AtomicIntegerThread();
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              for (int k = 0 ; k < 10 ; k++){
                  executorService.submit(atomicIntegerThread);
              }
              Thread.sleep(500);
             /* Thread[] thread = new Thread[10];
              for (int k = 0 ; k < 10 ; k++){
                  thread[k] = new Thread(new AtomicIntegerThread());
              }
              for (int k = 0 ; k < 10 ; k++){
                  thread[k].start();
              }
              for (int k = 0 ; k < 10 ; k++){
                  thread[k].join();
              }*/
              System.out.println(i);
          }
      }

      increateAndGet的内部使用CAS将新值写入,并且设置失败会不断重试知道成功。

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

    Java中的指针Unsafe类:

    • 指针是不安全的,因此Java中去除了指针,但是通过Unsafe类封装了一些不安全操作。我们无法使用这个类。

    无锁对象的引用:

    • AtomicReference是作用是对”对象”进行原子操作,但是会丢失状态信息
    • 如果需要保存状态信息则可以使用AtomicStampedReference,AtomicStampedReference还维护了一个时间戳,当被修改时需要更新数据本身和时间戳。
    • AtomicIntegerArray是线程安全的数组
      package com.ecut.atomic;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.atomic.AtomicIntegerArray;
      
      public class AtomicIntegerArrayTest {
          static AtomicIntegerArray array = new AtomicIntegerArray(10);
      
          public static class AtomicIntegerThread implements Runnable {
              @Override
              public void run() {
                  for (int k = 0; k < 10000; k++) {
                      array.getAndIncrement(k % array.length());
                  }
              }
          }
      
          public static void main(String[] args) throws InterruptedException {
              AtomicIntegerThread thread = new AtomicIntegerThread();
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              for (int k = 0; k < 10; k++) {
                  executorService.submit(thread);
              }
              Thread.sleep(5000);
              System.out.println(array);
          }
      }

      运行结果如下:

      [10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]

    原子操作工具类:

    • AtomicIntegerFiedUpdater它可以让你在不改动原有代码的基础上,让普通的变量也享受CAS操作带来的线程安全性。
      package com.ecut.atomic;
      
      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      import java.util.concurrent.atomic.AtomicInteger;
      import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
      
      public class AtomicIntegerFieldUpdaterTest {
      
          public static AtomicInteger allScore = new AtomicInteger();
      
          public final static AtomicIntegerFieldUpdater<Candidate> socoreUpdater =
                  AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
      
          public static class Candidate {
              int id;
              volatile int score;
          }
      
          public static class Vote implements Runnable {
      
              Candidate candidate;
      
              public Vote(Candidate candidate) {
                  this.candidate = candidate;
              }
      
              @Override
              public void run() {
                  if (Math.random() > 0.4) {
                      socoreUpdater.incrementAndGet(candidate);
                      allScore.incrementAndGet();
                  }
              }
          }
      
      
          public static void main(String[] args) throws InterruptedException {
              Candidate candidate = new Candidate();
              Vote vote = new Vote(candidate);
              ExecutorService executorService = Executors.newFixedThreadPool(10);
              for(int i = 0 ; i < 10000 ; i++){
                  executorService.execute(vote);
              }
              Thread.sleep(5000);
              System.out.println(allScore+":"+candidate.score);
          }
      }

      运行结果如下:

      6093:6093

      Candidate.score总是和allScore绝对相等,说AtomicIntegerFieldUpdater很好的保证了Candidate.score的线程安全。

    • 注意事项:
      1、Updater只能修改范围可见的变量,因为Update是通过反射来获取到这个变量的‘’
      2、为了确保变量被正确的读取,它必须是volatile类型的。
      3、由于CAS是通过对象实例中的偏移量直接进行赋值的,因此不支持statcic字段,Unsafe.objectFieldOffset() 不支持静态变量。

    源码地址:

    https://github.com/SaberZheng/concurrent-test

    转载请于明显处标明出处:

    https://www.cnblogs.com/AmyZheng/p/10471171.html

  • 相关阅读:
    BindingException: Parameter 'approval_state' not found. Available parameters are [arg1, arg0, param1, param2]]
    vue.js学习笔记(一)——vue-cli项目的目录结构
    memcpy函数
    与、或、异或运算
    C++中的dynamic_cast和dynamic_pointer_cast
    django中orm之什么是正向查询什么是反向查询
    jquery笔记
    jquery
    前端操作数组
    Auto Layout Guide----(三)-----Anatomy of a Constraint
  • 原文地址:https://www.cnblogs.com/AmyZheng/p/10471171.html
Copyright © 2011-2022 走看看