Showing posts with label Low Level Concurrency. Show all posts
Showing posts with label Low Level Concurrency. Show all posts

Sunday, November 15, 2015

Low-level synchronization in Java

Low level synchronization is the mechanism used before package java.util.concurrent was introduced into Java 1.5. You may not use it since the modern Java provide more high level sync mechanism since Java 1.5, but understand low-level sync can help you understand high-level counterparts like java.util.concurrent.locks.Condition, which not only duplicates low-level's function but also provides more flexibility.

The low-level sync mechanism requires 2 things:

  • keyword synchronized
  • methods wait/notify(notifyAll)

BTW, you may notice that wait()/notify() methods are build-in nature for every Java Object.

image

In this article, we try to build a simple producer/consumer model. They both work on an ArrayList, but consumer thread will block if the list is empty. As long as a producer thread put in a Integer into the list, the consumer can resume from the blocking and continue. (In reality, java.util.concurrent.BlockingQueue should be used instead write your own one).

0. How it works

In low-level sync, lock can be acquired from every object. we call it monitor lock, monitor, technically speaking, is the object whose lock you are acquiring. In this demo an ArrayList instance (called queue, see blow java definition) is the monitor we try to get lock from. 

Keyword synchronized(...) is used to acquire locks. although it looks like a methods but it's a keyword( You can  NOT Ctrl + click to goes into its implementation like a method of any Object). Synchronized block is always based on an object instance, the monitor, which is the parameter of synchronized(...)

wait() and notify() can only be called after the lock acquired, which means these methods can only be called within synchronized block.

when wait() invoked,  lock will be immediately release. then thread suspend. You may think "thread steps back from running and enters some kind of a  waiting-room" . The thread stays in "waiting-room" until notify() get called. Then this thread will try to get the lock again.After acquired the lock again, the wait() method finishes.

when notify() invoked, "one of the waiting thread gets out of the waiting-room" . normally this thread will soon get CPU time and  run again. notify() don't release lock, so it normally is put just before end of the synchronized block. When synchronized block ends, lock released.  If there are more threads in "waiting-room" which one get out is random. notifyAll() make all waiting threads get out of the waiting room, but only one of them can get lock.  So these thread will run one by one.Running order is not guaranteed.

1. Demo

There are 3 classes, consumer, producer and main class. Firstly define MyProducer class.

class MyProducer implements Runnable {
private List<Integer> queue;

public MyProducer(List<Integer> queue) {
this.queue = queue;
}

public void put(Integer e) throws InterruptedException {
synchronized (queue) {
System.out.printf("put %d to queue\n", e);
queue.add(e);
queue.notify();
}
}

@Override
public void run() {
try {
put(new Integer((int) (Math.random() * 10))); // random 0 ~ 9
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

The producer class implement Runnable interface and just input a random number into to list.  The instance variable queue are pass in by constructor, on which lock will be acquired by call synchronized(queue). The notify() is invoked to notify any thread may stay in the "wait-room" that this queue is not empty anymore.  


Secondly define MyConsumer class.

class MyConsumer implements Runnable {
private List<Integer> queue;

public MyConsumer(List<Integer> queue) {
this.queue = queue;
}

public Integer take() throws InterruptedException {
synchronized (queue) {
while (queue.isEmpty()) {
queue.wait();
}
return queue.remove(0);
}
}

@Override
public void run() {
try {
System.out.printf("Try to take from queue\n");
System.out.printf("take %d from queue\n\n", take());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

The consumer will try to get an element out of the list. if the list is empty, consumer thread will wait.


Finally the main class.

public class MyArrayBlockingQueue {
static private List<Integer> queue = new ArrayList<>();

public static void main(String[] args) throws InterruptedException {
MyProducer pr = new MyProducer(queue);
MyConsumer cr = new MyConsumer(queue);

Thread p1 = new Thread(pr);
Thread p2 = new Thread(pr);

Thread c1 = new Thread(cr);
Thread c2 = new Thread(cr);

p1.start();
Thread.sleep(1000);
c1.start();
Thread.sleep(1000);
c2.start();
Thread.sleep(5000);
p2.start();
}
}

The main class creates 2 producer threads and 2 consumer threads. The first producer thread p1 put a integer into the list and first consumer thread c1 take it out. Then second consumer c2 try to take from the empty list, it will block until 5 seconds later, the second producer p2 put integer into the list.


2. what's more.


Now you should understand how low-level synchronization mechanism works and the usage for methods wait()/notify() of every objects. In reality, you should use high-level sync mechanism like Lock, Condition and many other power ways provided in package java.util.concurrent and java.util.concurrent.locks.

Tuesday, October 27, 2015

In practical the most used thread methods before thread.start()

The java.lang.Thread class is the core of the low level multi-threading in Java. Almost every java developer know start a new thread is very simple. first, create a instance from Thread or subclass of Thread, then call start() method of that instance. WALA, the new thread is in runnable state and ready to run.

In a nontrivial project, very possibly there are some other methods need to be invoked after you create the thread instance and before start() get called. Let's take a look at these candidates in a hello world level demo.

package com.shengwang.demo;

class MyDummyTask implements Runnable {

@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.printf("Start working in %s\n", threadName);
throw new RuntimeException("some thing wrong");
}

}

public class MyThread {

public static void main(String[] args) throws InterruptedException {
MyDummyTask r = new MyDummyTask();
Thread t = new Thread(r);

//-----------------------------
// opt 1. set thread name
//-----------------------------
t.setName("helloThread");

//-----------------------------
// opt 2. set Exception handler
//-----------------------------
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {

@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println(formatException(t, e));
}

private String formatException(Thread t, Throwable e) {
StackTraceElement[] ste = e.getStackTrace();
String exceptionRootLocation = ste.length > 0 ? ste[0].toString() : "Root cause not specified";

StringBuilder sb = new StringBuilder();
sb.append("Thread Exception [");
sb.append(t.getName());
sb.append("]: ");
sb.append(e.toString());
sb.append(" at <");
sb.append(exceptionRootLocation);
sb.append(">");
return sb.toString();
}

});

//-----------------------------
// opt 3. set daemon
//-----------------------------
t.setDaemon(true);

t.start();
t.join();
}
}

1. Set thread's name


This is the most possible thing you may want to do before start the new thread by calling setName(). The default thread name will be 'Thread-n' if create by hand or 'pool-x-thread-y' if created by default ThreadFactory of Execuors. It will be very much helpful to set it  a meaningful name.


2. Set the exception handler


In production, it's not rare to see a  thead in multi-thread application raises exception. This exception need to be handled or logged to make the application get improved. The developer should set a customized handler to make this done using setUncaughtExceptionHandler()


In Java when uncaught Exception happens in thread, the order to handle the exception is thead's uncaughtExceptionHandler (like we set in the demo above)-> thread group's uncaughtException() method -> default uncaught exception handler. You can choose which layer you want to cut in. 


3. Set Daemon


This is much more optional. It depends on your application. JVM will wait for any non-daemon thread to finish before application can finish. So if  sub threads' interruption is not your concern, You an set them to daemon so you can quite the application fast.


4. More


In real project, the concurrency is based more on java.util.concurrent.ExecutorService instead of using creating Thread directly. So a customized ThreadFactory will always be used to set the properties on the thread instances before thread instances get involved in any kind of thread pools.

Powered by Blogger.

About The Author

My Photo
Has been a senior software developer, project manager for 10+ years. Dedicate himself to Alcatel-Lucent and China Telecom for delivering software solutions.

Pages

Unordered List