In the non-thread-safe case, when multiple threads concurrently access an instance variable in the same object, the result is dirty reads, in which the retrieved data has actually been altered.

Non-thread-safe problems exist in instance variables, and there are no “non-thread-safe” problems if they are private variables inside a method.

1 Synchronized

1.1 a synchronized method

Use the same lock object when using synchronized; otherwise, synchronized will become invalid.

public class ThreadTest {
    public static void main(String[] args) {
        Add add = new Add();
        Add add1 = new Add();
        ThreadAA threadAA = new ThreadAA(add);
        threadAA.start();
        ThreadBB threadBB = new ThreadBB(add1);
        threadBB.start();
    }
}

class ThreadAA extends Thread{
    private Add a;
    public ThreadAA(Add add){
        this.a = add;
    }
    @Override
    public void run(){
        a.add("a");
    }
}

class ThreadBB extends Thread{
    private Add b;
    public ThreadBB(Add add){
        this.b = add;
    }
    @Override
    public void run(){
        b.add("b"); } } class Add{ private int num = 0; Synchronized public void add(String username){try{if (username.equals("a")){
                num = 100;
                System.out.println("add a end");
                Thread.sleep(2000);
            }else {
                num = 200;
                System.out.println("add b end");
            }
            System.out.println(username + " name "+ num); }catch (Exception e){ e.printStackTrace(); }}}Copy the code

Print the result

add a end
add b end
b name 200
a name 100
Copy the code

The result shows that the print order is not synchronized, but crossed, because the lock obtained by the keyword synchronized is the object lock. So in the example above, if that thread executes the synchronized method first, that thread holds the lock on the object that the method belongs to, and the other threads can only wait if multiple threads access the same object.

Verify that a lock held by a synchronized method is an object lock

Public class ThreadTest {public static void main(String[] args) {Add Add = new Add(); // Add add1 = new Add(); ThreadAA threadAA = new ThreadAA(add); threadAA.start(); ThreadBB threadBB = new ThreadBB(add); threadBB.start(); }}Copy the code

The results

add a end
a name 100
add b end
b name 200
Copy the code

The long run results are printed sequentially.

1.2 synchronized Code blocks

The synchronized declaration method has drawbacks in some cases, such as when thread A calls the synchronized method to perform A long task, and then other threads must wait longer. In such cases, we can solve the problem with synchronized blocks, which wrap the parts of the code that must be synchronized.

public class ThreadFunction {
    public static void main(String[] args) {
        ObjFunction objFunction = new ObjFunction();
        FunA funA = new FunA(objFunction);
        funA.setName("a");
        funA.start();
        FunB funB = new FunB(objFunction);
        funB.setName("b");
        funB.start();

    }
}

class FunB extends Thread{
    private ObjFunction objFunction;
    public FunB(ObjFunction objFunction){
        this.objFunction = objFunction;
    }
    @Override
    public void run(){
        objFunction.objMethod();
    }
}

class FunA extends Thread{
    private ObjFunction objFunction;
    public FunA(ObjFunction objFunction){
        this.objFunction = objFunction;
    }
    @Override
    public void run(){
        objFunction.objMethod();
    }
}

class ObjFunction{
    public void objMethod(){
        try{
            System.out.println(Thread.currentThread().getName() + " start");
            synchronized (this) {
                System.out.println("start time = " + System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("end time = "+ System.currentTimeMillis());
            }
            System.out.println(Thread.currentThread().getName() + " end"); }catch (Exception e){ e.printStackTrace(); }}}Copy the code

The results

a start
b start
start time = 1559033466082
end time = 1559033468083
a end
start time = 1559033468083
end time = 1559033470084
b end
Copy the code

As you can see, the code outside the synchronized code block is executed asynchronously, while the code inside the synchronized code block is executed synchronously. Synchronized (this) is also the current object.

In addition to using this as the lock object, Java also supports synchronization on any object, but it is important to note that the synchronization monitor must be the same object, otherwise the result will be asynchronous calls.

1.3 synchronized Static synchronization

The keyword synchronized can also be applied to static static methods, in which the Class corresponding to the current *. Java file is used as the lock object.

Synchronized (class)

public class ThreadTest {
    public static void main(String[] args) {
        ThreadAA threadAA = new ThreadAA();
        threadAA.start();
        ThreadBB threadBB = new ThreadBB();
        threadBB.start();
    }
}

class ThreadAA extends Thread{
    @Override
    public void run(){
        Add.add("a");
    }
}

class ThreadBB extends Thread{
    @Override
    public void run(){
        Add.add("b"); } } class Add{ private static int num = 0; Synchronized static public void add(String username){try{synchronized static public void add(String username){if (username.equals("a")){
                num = 100;
                System.out.println("add a end");
                Thread.sleep(2000);
            }else {
                num = 200;
                System.out.println("add b end");
            }
            System.out.println(username + " name "+ num); }catch (Exception e){ e.printStackTrace(); }}}Copy the code

The results

add a end
a name 100
add b end
b name 200
Copy the code

1.4 synchronized class

Use the keyword synchronized to describe a class in which all methods are synchronized, and all methods are automatically synchronized when compiled.

class ClassName {
   public void method() {
      synchronized(ClassName.class) {
         // todo
      }
   }
}
Copy the code

1.5 Synchronized lock reentrant

Synchronized has the function of reentrant, which means that when a thread obtains an object lock, it can obtain the object lock again when it requests the object lock again. This means that the interior of a synchronized method/block can always be used to call another synchronized method/block of the class.

public class ThreadAgain {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                new Service().service1();
            }
        }).start();
    }
}

class Service{
    synchronized public void service1(){
        System.out.println("service1");
        service2();
    }

    synchronized private void service2() {
        System.out.println("service2");
        service3();
    }

    synchronized private void service3() {
        System.out.println("service3"); }}Copy the code

The results

service1
service2
service3
Copy the code

2 volatile

The main purpose of the volatile keyword is to make variables visible across multiple threads.

Is to force the value of a variable to be fetched from the public heap rather than from the thread’s private stack. In multithreading, stack and program counters are private, heap and global variables are public.

Look at the code

public class MyVolatile {
    public static void main(String[] args) {
        try {
            RunThread runThread = new RunThread();
            runThread.start();
            Thread.sleep(2000);
            runThread.setRun(false);
            System.out.println("Copy false for runThread");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}

class RunThread extends Thread{
    private boolean isRun = true;

    public boolean isRun() {
        return isRun;
    }

    public void setRun(boolean run) {
        isRun = run;
    }

    @Override
    public void run(){
        System.out.println("Enter the run method");
        while (isRun == true){
        }
        System.out.println("Exit the run method and the thread stops"); }}Copy the code

As you can see from the console, the thread is not terminated. This problem is caused by the fact that the values on the private stack are out of sync with the values on the active stack. To solve this problem, use the volatile keyword.

Modify the code in the RunThread class

class RunThread extends Thread{
   volatile private boolean isRun = true;

    public boolean isRun() {
        return isRun;
    }

    public void setRun(boolean run) {
        isRun = run;
    }

    @Override
    public void run(){
        System.out.println("Enter the run method");
        while (isRun == true){
        }
        System.out.println("Exit the run method and the thread stops"); }}Copy the code

Run again and the thread terminates normally.

While the volatile keyword can make instance variables visible across multiple threads, volatile has the fatal disadvantage of not supporting atomicity.

Verify that volatile does not support atomicity

public class IsAtomic {
    public static void main(String[] args) {
        MyAtomicRun[] myAtomicRuns = new MyAtomicRun[100];
        for(int i = 0; i<100; i++){ myAtomicRuns[i] = new MyAtomicRun(); }for(int i = 0; i<100; i++){ myAtomicRuns[i].start(); } } } class MyAtomicRun extends Thread{ volatile public static int count; private static voidcount() {for(int i = 0; i<100; i++){ count++; } System.out.println("count: " + count);
    }
    @Override
    public void run(){ count(); }}Copy the code

A printout

Count: 5000 count: 4900 count: 4800 count: 4700 count: 4600 count: 4500 count: 4400 count: 4400Copy the code

From the result of the output, it does not output 10000 as our ideal state.

Make code improvements

class MyAtomicRun extends Thread{ volatile public static int count; Synchronized private static void synchronized private static void synchronized private static void synchronized private static voidcount() {for(int i = 0; i<100; i++){ count++; } System.out.println("count: " + count);
    }
    @Override
    public void run(){ count(); }}Copy the code

A printout

count: 9300
count: 9400
count: 9500
count: 9600
count: 9700
count: 9800
count: 9900
count: 10000
Copy the code

This time the output is the correct result.

The main use of the keyword volatile is when multiple threads can sense that an instance variable has changed and can obtain the latest value, i.e., when multiple threads read a shared variable, they can obtain the latest value.

2.1 Comparison between volatile and synchronized

  1. Volatile is a lightweight implementation of thread synchronization, so it certainly performs better than synchronized, and volatile only modifies variables, whereas synchronized modifies methods, as well as code blocks.
  2. Volatile does not block when accessing multiple threads, whereas synchronized does.
  3. Volatile guarantees visibility, but not atomicity; Synchronized guarantees atomicity and, indirectly, visibility, because synchronized synchronizes data in private and public memory.
  4. Volatile addresses the visibility of variables across multiple threads; Synchronized addresses the synchronization of access to resources between multiple threads.

2.2 Variables work in memory

Operations such as the ++ operation on volatile variables are not atomic and are therefore not thread-safe.

i++

  1. Fetch the value of I from memory
  2. Calculate the I
  3. Write I to memory

If another thread changes the value of I in the second step, then the data will be dirty.

  • Read and load phases: copy variables from main memory to the current thread’s working memory;
  • Use and assign phases: execute code to change shared variable values;
  • Store and write phases: Flush main memory variable values with working memory data.

In a multithreaded environment, use and assign is several times, but the operation is not atomic, namely after reading stage, if the variable values in the main memory is modified, the worker thread of memory because it is loaded, so won’t produce corresponding change, creates a private memory and public memory variable values are not synchronized, The calculated results are not the same as expected, and there are non-thread safety problems.

For volatile variables, the JVM only guarantees that the values loaded from main memory into working memory are up to date.