Learn java thread communication with me 2

1. Introduction

This post would demo how to do the thread communication by using volatile. If you want to know the wait-notify communication, you can refer to the previous article.

1. Environments

  • Java 1.8

2.1 The java heap memory and thread memory

As we know, the object we defined in class level would be allocated in the java heap memory, and every thread has its own memory, which is located at the stack, usually in the CPU buffer memories.

When we access the heap variable from thread, it really copies the value of the object and access it from local. As the following picture shows:

heap_and_thread_memory

2.2 The volatile keyword introduction

By using volatile , you can make the variable seen by all the threads, and the changes to the volatile variable would be seen immediately by all the threads, there is no latency for the synchronization.

Using volatile forces all accesses (read or write) to the volatile variable to occur to main memory, effectively keeping the volatile variable out of CPU caches. This can be useful for some actions where it is simply required that visibility of the variable be correct and order of accesses is not important. –by Lawrence Dol.

2.3 What happens without volatile in multithreading environment?

Here I write an example without volatile, you can see that Not using volatile would cause Unsynchronized problems in multithreading environment.

    private static boolean flag = true; //line 1

    public static void main(String[] args) throws InterruptedException {
        (new Worker("worker1",500,0)).start();
        (new Worker("worker2",500,0)).start();
        Thread.sleep(1000);

        System.out.println(System.currentTimeMillis()+" main stopped the flag");
        flag = false; //line 2
    }

    private static class Worker extends Thread {
        private long sleepBeforeStart;
        private long sleepInterval;

        public Worker(String name,long sleepBeforeStart,long sleepInterval) {
            super.setName(name);
            this.sleepBeforeStart = sleepBeforeStart;
            this.sleepInterval = sleepInterval;
        }

        public void run() {
            try {
                Thread.sleep(sleepBeforeStart);
                while(flag) {
                    Thread.sleep(sleepInterval);
                }
                System.out.println(System.currentTimeMillis()+" "+getName()+" stopped");
            }catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

The above code explanations:

  • The above example just define a global flag and starts two worker threads that monitors the flag, if the main thread changes the flag, the workers should stop immediately.
  • line 1: we define a variable named flag in the heap space. And all threads would monitor this flag , if it changed to false, then all threads should stop immediately.
  • line 2: change the heap variable from main thread

Run the above code sometimes, we would find the output like this:

1526435276010 main stopped the flag
1526435276010 worker1 stopped
1526435276011 worker2 stopped

Process finished with exit code 0

We found that,the workers stopped Unsynchronizedly. That is :

  • The main thread changed the flag to false in the heap memory
  • Worker1 copied the value to local stack memory and stopped
  • After a while(only 1 millisecond later) the worker2 copied the value to local stack memory and stopped

But if we change the flag to volatile like this:

private static volatile boolean flag = true;

Then all the workers thread should see the flag and react to the change without hesitation.

1526437973014 main stopped the flag
1526437973014 worker1 stopped
1526437973014 worker2 stopped

Process finished with exit code 0

We run the code for 20 times,the result is in line with expectations.

2.4 The volatile is not synchronized!

Volatile only make the heap variable visible to all threads, e.g. They would get the change infomation immediately when the change happens. But if the heap variable is mutable and all thread read and write it concurrently, it would cause many problems.

Here we supply an example about the problem:

private static volatile int count = 0;

public static void main(String[] args) throws InterruptedException {
    Worker worker1 = (new Worker("worker1",1));
    Worker worker2 = (new Worker("worker2",1));
    worker1.start();
    worker2.start();

    for(int i=0;i<10000;i++) {
        count ++;
    }
    worker1.join();
    worker2.join();
    System.out.println("count="+count);
}

private static class Worker extends Thread {
    private long sleepBeforeStart;

    public Worker(String name,long sleepBeforeStart) {
        super.setName(name);
        this.sleepBeforeStart = sleepBeforeStart;
    }

    public void run() {
        try {
            Thread.sleep(sleepBeforeStart);
            for(int i=0;i<10000;i++) {
                count ++;
            }
            System.out.println(System.currentTimeMillis()+" "+getName()+" stopped");
        }catch (Exception ex) {
            ex.printStackTrace();
        }
    }
}

In the above example, we defined a volatile variable named count,which would be changed by the main thread/worker threads. The worker thread just sleep for a while and then add the count,The main thread start the workers and then add the count and print the count at last.

Our expectation is the count should equals to 30000.

But we get this:

1526439030006 worker2 stopped
1526439030006 worker1 stopped
count=21457

Process finished with exit code 0

The reason I think is:

  • Volatile can not assure the synchronization read/write of the threads
  • Multiple threads would write to this count concurrently, it would cause problem

So if we want the synchronization of the threads, we need lock.

New synchronized version with volatile:

    private static volatile AtomicInteger count = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {
        Worker worker1 = (new Worker("worker1",1));
        Worker worker2 = (new Worker("worker2",1));
        worker1.start();
        worker2.start();

        for(int i=0;i<10000;i++) {
            count.getAndIncrement();
        }
        worker1.join();
        worker2.join();
        System.out.println("count="+count.get());
    }

    private static class Worker extends Thread {
        private long sleepBeforeStart;

        public Worker(String name,long sleepBeforeStart) {
            super.setName(name);
            this.sleepBeforeStart = sleepBeforeStart;
        }

        public void run() {
            try {
                Thread.sleep(sleepBeforeStart);
                for(int i=0;i<10000;i++) {
                    count.getAndIncrement();
                }
                System.out.println(System.currentTimeMillis()+" "+getName()+" stopped");
            }catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

Run the code several times, we got the same result:

1526439393907 worker1 stopped
1526439393907 worker2 stopped
count=30000

Process finished with exit code 0

3. summary

Volatile keyword only make variable visible to all threads, and we can NOT do synchronization by it. If you need to read/write same variable by multithreads simultaneously, use lock to do this job.

You can find the whole code examples on github project, the class source code is located at this

You can find detail documents about the java stream here: