如何在线程之间共享资源?

介绍

涉及IO读/写时,多线程能够提升应用程序的性能。不幸的是,共享资源(共享变量)在每一个CPU缓存中可能有不一样的版本。结果是应用程序的行为不可预测。Java提供了synchronized关键字来保持CPU缓存中共享资源的一致性。不幸的是,synchronized关键词减慢了应用程序的速度。html

我使用JMH做为微型基准测试AverageTime模式,这意味着基准测试结果是每一个测试用例的平均运行时间,较低的输出效果更好。您能够在此连接找到更多关于微基准的信息。java

为何同步减速应用程序?

当线程被锁定并开始执行同步块中的指令时,全部其余线程将被阻塞并变为空闲状态。这些线程的执行上下文(CPU缓存,指令集,堆栈指针...)将被存储,其余活动线程的执行上下文将被恢复以恢复计算。它被称为上下文切换,须要系统的大量工做。任务计划程序也必须运行以选择将加载哪一个线程。缓存

易变关键字

volatile关键字只是作了一些事情:告诉CPU从主内存中读取资源的值,而不是从CPU的缓存中读取资源的值; 在每次后续读取该字段以前,都会发生对易失性字段的写入。 易失性永远不会有比同步更高volatile的开销,synchronized若是synchronized块只有一个操做,它将具备相同的开销。安全

volatile若是只有一个写入线程,关键字能够很好地工做。若是有2个或更多的写入线程,就会出现竞争条件:全部写入线程获取最新版本的变量,在本身的CPU上修改值,而后写入主内存。结果是内存中的数据只是一个线程的输出,其余线程的修改被覆盖。数据结构

包java.util.concurrent

Doug Lea在建立和改进这个软件包时作了很棒的工做。这个包有不少用于管理线程的工具,还包含一些线程安全的数据结构。那些数据结构也可使用synchronizedvolatile可是以一种复杂的方式,你能够从编写你本身的代码中得到更好的性能。多线程

ConcurrentHashMap“遵循与”相同的功能规范Hashtable“,并为您提供线程安全的优点。oracle

 

public class TestHashMap {

  @Benchmark
  @BenchmarkMode(Mode.AverageTime)
  @OutputTimeUnit(TimeUnit.NANOSECONDS)
  @Threads(10)
  public void concurrentHashMap(BenchMarkState state){
      Integer temp;
      for(int i = 0; i < 100000; i++){
          temp = Integer.valueOf(i);
          state.chm.put(temp,temp);
      }
  }

  @Benchmark
  @BenchmarkMode(Mode.AverageTime)
  @OutputTimeUnit(TimeUnit.NANOSECONDS)
  @Threads(10)
  public void hashMap(BenchMarkState state){
      Integer temp;
      for(int i = 0; i < 100000; i++){
          temp = Integer.valueOf(i);
          synchronized (state.LOCK_1) {
              state.hm.put(temp,temp);
          }            
      }
  }

  @State(Scope.Benchmark)
  public static class BenchMarkState {
      @Setup(Level.Trial)
      public void doSetup() {
          hm = new HashMap<>(100000);
          chm = new ConcurrentHashMap<>(100000);
      }
      @TearDown(Level.Trial)
      public void doTearDown() {
          hm = new HashMap<>(100000);
          chm = new ConcurrentHashMap<>(100000);
      }
      public HashMap<Integer, Integer> hm = new HashMap<>(100000);
      public ConcurrentHashMap<Integer, Integer> chm = new ConcurrentHashMap<>(100000);
      public final Object LOCK_1 = new Object();
  }

隐藏   复制代码分布式

Benchmark                      Mode  Cnt         Score        Error  Units
TestHashMap.concurrentHashMap  avgt  200  10740649.930 ± 351589.110  ns/op
TestHashMap.hashMap            avgt  200  60661584.668 ± 758157.651  ns/op

AtomicInteger和其余相似的类使用volatileUnsafe.compareAndSwapIntAtomicInteger能够称为忙等待,这意味着一个线程老是检查条件执行。这个线程什么都不作,可是任务调度程序不能检测到这个检查而且认为这个线程很忙,因此任务调度程序不能把CPU拿到另外一个准备执行的线程。若是在几个CPU时钟以后条件能够归档,则忙等待效果很好。微服务

 

public class TestCAS {

  @Benchmark
  @BenchmarkMode(Mode.AverageTime)
  @OutputTimeUnit(TimeUnit.NANOSECONDS)
  @Threads(10)
  public void atomic(BenchMarkState state){
    while(state.atomic.get() < 100000)
      while(true){
        int temp = state.atomic.get();
        if(temp >= 100000 || state.atomic.compareAndSet(temp, temp + 1))
          break;
      }
  }

  @Benchmark
  @BenchmarkMode(Mode.AverageTime)
  @OutputTimeUnit(TimeUnit.NANOSECONDS)
  @Threads(10)
  public void integer(BenchMarkState state){
    while(state.integer < 100000){
      synchronized (state.LOCK) {
        if(state.integer < 100000)
          state.integer += 1;
      }
    }
  }

  @State(Scope.Benchmark)
  public static class BenchMarkState {
    @Setup(Level.Trial)
    public void doSetup() {
      atomic.set(0);
      integer = 0;
    }

    public Object LOCK = new Object();
    public AtomicInteger atomic = new AtomicInteger(0);
    public Integer integer = new Integer(0);
  }

 

Benchmark        Mode  Cnt   Score   Error  Units
TestCAS.atomic   avgt  200  10.053 ± 0.985  ns/op
TestCAS.integer  avgt  200  12.666 ± 1.145  ns/op

Lock具备更多灵活的功能synchronized,您可使用tryLock()特定的时间等待或确保最长的等待线程得到公平选项的锁定。但synchronized关键字能够保证执行顺序和数据新鲜度,源代码synchronized也很简单。Lock工具

@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(10)
public void lock(BenchMarkState state){
  while(state.intLock < 100000){
      state.lock.lock();
      if(state.intLock < 100000)
          state.intLock++;
      state.lock.unlock();
  }
}
@Benchmark
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Threads(10)
public void synchonized(BenchMarkState state){
  while(state.intSync < 100000){
    synchronized (state.LOCK) {
      if(state.intSync < 100000)
        state.intSync += 1;
    }
  }
}

 

Benchmark             Mode  Cnt  Score   Error  Units
TestLock.lock         avgt  200  1.960 ± 0.074  ns/op
TestLock.synchonized  avgt  200  2.394 ± 0.047  ns/op

不变的对象

这个想法很简单,若是一个对象从不改变值,它是线程安全的。可是有一个问题,每次你想改变一些值时你必须建立一个新的对象,所以GC过热。

结论

synchronized关键字之间共享线程之间的资源很容易,但它可能会致使世界各地的等待和放慢您的应用程序。其余简单的技术也能够归档线程安全,但速度比synchronized

​针对上面的技术我特地整理了一下,有不少技术不是靠几句话能讲清楚,因此干脆找朋友录制了一些视频,不少问题其实答案很简单,可是背后的思考和逻辑不简单,要作到知其然还要知其因此然。若是想学习Java工程化、高性能及分布式、深刻浅出。微服务、Spring,MyBatis,Netty源码分析的朋友能够加个人Java进阶群:744642380,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给你们。

相关文章
相关标签/搜索