Site Search:

Chapter 1 Introduction

<Back


fun facts about java multi-threading *

1.1 A (very) brief history of concurrency


1.2 Benefits of threads

When used properly, threads can reduce development and maintenance costs and improve the performance of complex applications.

Where practical, use existing thread-safe objects, like AtomicLong, to manage your class's state. It is simpler to reason about the possible states and state transitions for existing thread-safe objects than it is for arbitrary state variables, and this makes it easier to maintain and verify thread safety.

However creating threads is not free, it takes resources. Sometimes the overhead makes the multi-threaded program even slower than single threaded one. In the following example, we benchmark multi-thread program performance. Benchmark is not as simple as it sounds, we need to use a CountDownLatch to tell when all the threads finished executing. Like any experiments, we need to run the experiment many times, record the values, then take the average value (and sometimes standard deviations). This is necessary because random factors like garbage collection, OS disturbances  can be average out in large numbers. We also need to discard the results of the first rounds of experiment, because during these earlier rounds, activities like compiler optimization is happening, which is environment depended, we should not take them into our comparison.


AtomicAction1.java

======

concurrency>javac AtomicAction1.java 
concurrency>java AtomicAction1
available core number: 4
singleThread average time per run: 5.677777777777778 miliseconds.
multiThreadWithLock average time per run: 27.733333333333334 miliseconds.
multiThreadWithAtomic average time per run: 28.22222222222222 miliseconds.
concurrency>java AtomicAction1
available core number: 4
singleThread average time per run: 5.8 miliseconds.
multiThreadWithLock average time per run: 27.91111111111111 miliseconds.
multiThreadWithAtomic average time per run: 28.011111111111113 miliseconds.
concurrency>java AtomicAction1
available core number: 4
singleThread average time per run: 5.85 miliseconds.
multiThreadWithLock average time per run: 27.5 miliseconds.
multiThreadWithAtomic average time per run: 27.77777777777778 miliseconds.
concurrency>java AtomicAction1
available core number: 4
singleThread average time per run: 5.7555555555555555 miliseconds.
multiThreadWithLock average time per run: 27.477777777777778 miliseconds.
multiThreadWithAtomic average time per run: 27.822222222222223 miliseconds.

concurrency>
======

1.3 Risks of threads


The following example shows how elusive the multithreading bug is, you may run a program for a long time without problem, however unsafe code can still blow anytime.
======

concurrency>javac unsafeBank.java 
concurrency>for i in {1..100}; do java chaosCounter; done | grep -v 100000
99999
concurrency>
concurrency>cat unsafeBank.java 
public class unsafeBank {
  private int sum;
  public void putDeposit(int deposit) {
    for(int i = 0; i < 10000; i++) {}
    sum += deposit;
  } 
  public int publishReport() {return sum;}

  public static void main(String...args) {
    unsafeBank bank = new unsafeBank();
    for(int i = 0; i<100000; i++) {
      bank.putDeposit(1);
    }
    System.out.println(bank.publishReport()); 
  }
}

class chaosCounter {
  private unsafeBank bank;
  public chaosCounter(unsafeBank bank) {
    this.bank = bank;
  }
  public void runTheBank() {
    for(int i = 0; i < 100000; i++) {
      Thread thread = new Thread("customer"+i) {
        public void run(){
          bank.putDeposit(1);
          //System.out.println("$1 added by customer");
        }
      };
      thread.start();
    } 
    System.out.println(bank.publishReport()); 
  }
  public static void main(String...args){
    unsafeBank bank = new unsafeBank();
    chaosCounter chaos = new chaosCounter(bank);
    chaos.runTheBank();
  }
}
concurrency>
concurrency>for i in {1..100}; do java chaosCounter; done | grep -v 100000
99999
concurrency>
======

1.4 Threads are everywhere

Frameworks introduce concurrency into applications by calling application components from framework threads. Components invariably access application state, thus requiring that all code paths accessing that state be thread-safe.