Skip to content

Java Concurrency

Threads vs CPU

Essentially,Threads and CPU have Producer and Consumer relationship. Threads are the producers produce tasks (from instructions). CPUs are the consumer consume (execute) these tasks. Some tasks should wait for other resources (e.g. I/O). If CPUs have no other tasks to do during waiting, CPUs's capacity will be wasted.

Multi Threads aims to produce enough tasks to feed CPU's capacity, to improve CPU's utilization.

JMM Abstraction from the point of view of concurrency

JMM is abstract memory model from a different aspect comparing to the real JVM Memory model. It defines the abstract relationship between threads and main memory:

  • shared variables between threads are stored in the main memory (Main Memory).
  • each thread has a private local memory (Local Memory), and the local memory stores the Threads to read/write copies of shared variables.
  • Local memory is an abstraction of JMM and does not really exist. It covers caches, write buffers, registers, and other hardware and compiler optimizations.

Race condition happens when 2+ threads process the same shared variable concurrently.

Key concerns of Java Concurrency

Visibility, Atomicity, Orderliness

Mutable and Immutable Objects

Mutable object (non-thread-safe) – You can change the states and fields after the object is created. For examples: StringBuilder, java.util.Date and etc.

Immutable object (thread-safe) – You cannot change anything after the object is created. For examples: String, boxed primitive objects like Integer, Long and etc.

Happens-before relationship

Happens-before relationship is a guarantee that action performed by one thread is visible to another action in different thread. Happens-before defines a partial ordering on all actions within the program.

Synchronized, Volatile vs CAS (Compare and Swap)

Synchronized: The synchronized keyword in Java provides pessimistic locking, which ensures mutually exclusive access to the shared resource and prevents data race. It is based on Monitor of system (MONITORENTER, MONITOREXIT). We can use jvm parameters e.g. -XX:+UseBiasedLocking to change the default lock type of jvm.

Volatile: Java volatile variable, which will instruct JVM threads to read the value of the volatile variable from main memory and don’t cache it locally. Use case example: Control flag.

CAS: java.util.concurrent.atomic, e.g. AtomicBoolean,AtomicInteger,AtomicLong, etc. Use case example: Counter

It is Optimistic locking:

  • if value in memory matches old value in hand, then swap. [Value in Memory, Old Value(in hand), New value]
  • if the values do not match it means some thread in between has changed the value. It will recalculate the old value and new value and then go ahead to do step A.

Cons:
1. CPU high cost for spin.
2. can not ensure atomic of one block.
3. ABA Problem (solution -> add timestamp, e.g. AtomicStampedReference).

HOW-MANY threads are appropriate?

It depends on use cases.

Use Case 1: Only CPU calculations

In theory, it could be

Number of Threads == Number of CPUs

Normally, we could set

Number of Threads = Number of CPUs + 1

the additional one as a buffer.

Use Case 2: CPU + I/O Operations

In case of one CPU,

Number of Threads == 1 + (I/O Processing Time / Processing Time of CPU)

In case of multiple CPUs,

Number of Threads == Number of CPU * [ 1 + (I/O Processing Time / Processing Time of CPU) ]

Lessons Learned

  • Prefer to use Synchronized rather than the Locks(e.g. ReentrantLock ) in J.U.C.
  • Threads must be provided by the thread pool. Explicit creation of threads in the application is not allowed.
  • Executors are not allowed to create thread pools. Instead, ThreadPoolExecutor should be used. This way of processing allows the coders to be more specific about the running rules of thread pools and avoid the risk of resource exhaustion.

  1. Java Concurrency in Practice - Brian Goetz, Tim Peierls, Joshua Bloch, Joseph Bowbeer, David Holmes, Doug Lea 

  2. JAVA并发编程实战 - 极客时间 王宝令 

  3. Java并发编程的艺术 - 方腾飞 / 魏鹏 / 程晓明 

  4. 阿里巴巴Java开发手册