Click here to Skip to main content
13,634,012 members
Click here to Skip to main content
Add your own
alternative version

Stats

18.7K views
13 bookmarked
Posted 26 Mar 2017
Licenced CPOL

How to Share Resources Between Threads?

, 16 Jan 2018
Rate this:
Please Sign up or sign in to vote.
Synchronization is slow, why and how to do better?

Introduction

Multi threading can improve application performance when IO read/write is involved. Unfortunately, shared resources (shared variables) can have different versions at each CPU cache. The consequence is that the application's behavior cannot be predictable. Java provides synchronized keyword to keep shared resources consistent across CPU's caches. Unfortunately again, synchronized keyword slows down the application.

I use JMH for micro benchmark with AverageTime mode, it mean the result of benchmark is average run time of each testcase, lower output is better. You can find more information about micro benchmark at this link.

Why Synchronized Slowdown Application?

When a thread gets locked and starts to execute instructions in a synchronized block, all other threads will be blocked and become idle. Execution context (CPU cache, instruction set, stack pointer ...) of those threads will be stored and execution context of other active threads will be restored to resume computing. It's called context switch and requires significant effort of the system. Task scheduler also has to run to pick which thread will be loaded.

volatile keyword

volatile keyword just does a few things: tells CPU read value of resources from main memory, not from CPU's cache; A write to a volatile field happens-before every subsequent read of that field. Volatile can never have a higher overhead than synchronized, volatile will have the same overhead with synchronized if synchronized block has only one operation.

volatile keyword works well if there is only one writing thread. If there are 2 or more writing threads, race condition will happen: all writing threads get the latest version of variable, modify value at its own CPU, then write to main memory. The consequence is that data in memory is just the output of one thread, other threads' modification were overridden.

Package java.util.concurrent

Doug Lea did great work when he created and improved this package. This package has a lot of tools for managing threads, and also contains some thread-safe data structures. Those data structures also use synchronized and volatile under the hood but in a sophisticated way, you can benefit from much better performance than writing your own code.

ConcurrentHashMap "obeys the same functional specification as Hashtable" and gives you the advantage of thread-safe.

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 and other similar classes use volatile and Unsafe.compareAndSwapInt. AtomicInteger can call as busy-wait, it mean a thread always checks condition to execution. This thread does nothing but task scheduler cannot detect this check and considers this thread is busy, so that task scheduler cannot take CPU to another thread that is ready for execution. Busy-wait works well if the condition can archive after a few clocks of 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

Lock has more flexible features than synchronized, you can use tryLock() for a specific time to wait or can make sure the longest waiting thread gets the lock with fairness option. But synchronized keyword can guarantee both execution sequence and data freshness, the source code with synchronized is also simple. Lock will be a nightmare if a junior developer forgets to call unlock() or doesn't put unlock() at finally block.

@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

Immutable Object

The idea is simple, if one object never changes values, it's thread-safe. But there is a problem, you have to create a new object each time you want to change some values, consequently there is overheat of GC. Some libraries can make immutable objects more easy to deal with, like https://immutables.github.io.

Conclusion

Sharing resources between threads is easy with synchronized keyword, but it can cause world wide waiting and slowdown your applications. Other simple techniques also can archive thread-safe, but are faster than synchronized.

You can check out the full source code at https://github.com/vudangngoc/java-benchmark.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

vudangngoc
Technical Lead Orchestra Networks
Vietnam Vietnam
Java programmer at Orchestra Networks

You may also be interested in...

Pro
Pro

Comments and Discussions

 
QuestionSharing a resource among Threads, different behavior in different java versions Pin
jamie hennings16-Apr-17 21:59
memberjamie hennings16-Apr-17 21:59 
AnswerRe: Sharing a resource among Threads, different behavior in different java versions Pin
vudangngoc18-Apr-17 20:17
membervudangngoc18-Apr-17 20:17 
GeneralHashmap Test Pin
Axel F13-Apr-17 23:41
memberAxel F13-Apr-17 23:41 
GeneralRe: Hashmap Test Pin
vudangngoc18-Apr-17 20:19
membervudangngoc18-Apr-17 20:19 
Suggestion[My vote of 2] My vote of 2 Pin
Member 1094152329-Mar-17 17:51
memberMember 1094152329-Mar-17 17:51 
GeneralRe: [My vote of 2] My vote of 2 Pin
vudangngoc31-Mar-17 16:44
membervudangngoc31-Mar-17 16:44 
GeneralRe: [My vote of 2] My vote of 2 Pin
Member 109415232-Apr-17 4:27
memberMember 109415232-Apr-17 4:27 
GeneralRe: [My vote of 2] My vote of 2 Pin
vudangngoc2-Apr-17 4:55
membervudangngoc2-Apr-17 4:55 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Cookies | Terms of Use | Mobile
Web02-2016 | 2.8.180712.1 | Last Updated 17 Jan 2018
Article Copyright 2017 by vudangngoc
Everything else Copyright © CodeProject, 1999-2018
Layout: fixed | fluid