Java thread memory model, thread, working memory, main memory

Java thread memory model, thread, working memory, main memory

java thread memory model

Diagram of the interaction between threads, working memory, and main memory:

 

key edeas

All threads share main memory
Each thread has its own working memory
refreshing local memory to/from main memory must comply to JMM rules

 

Reasons for thread safety

The working memory of a thread is an abstract description of the registers and caches of the cpu: In modern computers, the cpu does not always read data from the memory when calculating. Its data read order priority is: register-cache- RAM. The thread consumes the CPU. When the thread calculates, the original data comes from the memory. During the calculation process, some data may be frequently read. These data are stored in registers and caches. When the thread calculates, the cached data Data should be written back to memory when appropriate. When multiple threads read and write a certain memory data at the same time, there will be a multi-thread concurrency problem, which involves three characteristics: atomicity, orderliness, and visibility. Platforms that support multi-threading will face this problem. Languages running on multi-threaded platforms that support multi-threading should provide solutions to this problem.

JVM is a virtual computer, it will also face the problem of multi-thread concurrency. Java programs run on the java virtual machine platform. It is impossible for java programmers to directly control the synchronization between the underlying thread and the register cache memory, so java from the syntax At the level, developers should be provided with a solution such as synchronized, volatile, locking mechanisms (such as synchronized blocks, ready queues, blocking queues) and so on. These schemes are only at the grammatical level, but we have to understand it in essence;

 

Each thread has its own execution space (that is, working memory). When a thread is executed, when a variable is used, first copy the variable from the main memory in its own working memory space, and then operate on the variable: read, modify, Assignment, etc., these are all completed in the working memory, and the variables are written back to the main memory after the operation is completed;

Each thread obtains data from the main memory, and the data between threads is not visible; for example: the original value of the main memory variable A is 1, thread 1 takes out the variable A from the main memory, and changes the value of A to 2, in the thread 1 When the variable A is not written back to the main memory, the value of the variable A obtained by the thread 2 is still 1;

This leads to the concept of "visibility": when a shared variable has copies in the working memory of multiple threads, if a thread modifies the copy value of the shared variable, then other threads should be able to see the modified value The value of this is the visibility problem of multithreading.

Common variable situation: If thread A modifies the value of a common variable and then writes it back to the main memory, another thread B reads from the main memory after thread A writes back, and the value of the new variable is only Visible to thread B;

 

How to ensure thread safety 
Writing thread-safe code is essentially about managing access to state, and it is usually shared and mutable state. The state here is the variable (static variable and instance variable) of the object. 
The premise of thread safety is whether the variable is accessed by multiple threads. To ensure the thread safety of the object, synchronization is required to coordinate access to its variable state; At this point, it will lead to dirty data and other unpredictable consequences. Whenever more than one thread accesses a given state variable, and one of the threads will write to the variable, synchronization must be used to coordinate thread access to the variable. The primary synchronization mechanism in Java is the synchronized keyword, which provides an exclusive lock. In addition, the term "synchronization" also includes volatile variables, showing the use of locks and atomic variables. 
Without proper synchronization, if multiple threads access the same variable, there are hidden dangers in your program. There are 3 ways to fix it: 
l don't share variables across threads; 
l make state variables immutable; or 
l use synchronization when accessing state variables.

 

Volatile requires the program to write back to the main memory for every modification of the variable. This solves the problem of visibility for other thread courseware, but does not guarantee the consistency of the data; special attention: atomic operation: according to the Java specification, for basic Type assignment or return value operations are atomic operations. But the basic data types here do not include long and double, because the basic storage unit seen by the JVM is 32 bits, and both long and double are represented by 64 bits. So it cannot be completed in one clock cycle 

 

In layman's terms, the state of an object is its data, stored in state variables, such as instance domains or static domains; at any time, as long as more than one thread accesses a given state variable. And one of the threads will write to the variable, and synchronization must be used to coordinate thread access to the variable at this time;

Synchronization lock: Each JAVA object has and only one synchronization lock. At any time, only one thread is allowed to own this lock.

When a thread tries to access a code block marked with synchronized (this), it must obtain the lock of the object referenced by the this keyword. In the following two cases, the thread has different fate.
1. If this lock is already occupied by other threads, JVM will put this thread in the lock pool of this object. This thread enters a blocking state. There may be many threads in the lock pool. When other threads release the lock, the JVM will randomly take a thread from the lock pool, make this thread own the lock, and go to the ready state.
2. If the lock is not occupied by other threads, this thread will acquire the lock and start executing the synchronized code block.
(Under normal circumstances, the synchronization lock will not be released when the synchronization code block is executed, but there are also special circumstances that release the object lock. For
example, when the synchronization code block is executed, an exception is encountered and the thread is terminated, and the lock will be released; , Execute the wait() method of the object to which the lock belongs, this thread will release the object lock and enter the waiting pool of the object)

 

The Synchronized keyword guarantees issues such as data read and write consistency and visibility, but it is a blocking thread control method. During the keyword usage, all other threads cannot use this variable, which leads to a kind of non-blocking synchronization. The need for thread safety of control;

ThreadLocal analysis

As the name suggests, it is a local variable (thread local variable). Its function is very simple. It provides a copy of the variable value for each thread that uses the variable. Each thread can change its own copy independently without conflicting with other threads' copies. From the perspective of threads, it is as if every thread completely owns the variable.

Each thread maintains an implicit reference to a copy of its thread-local variable, as long as the thread is active and the  ThreadLocal  instance is accessible; after the thread disappears, all copies of its thread-local instance will be garbage collected (unless there is a copy of these Other references to copies).

 

Object wait() and notify() method analysis

Object's wait() and notify(), notifyAll() methods use an object as a lock, and then call wait() to suspend the current thread and release the object lock at the same time;

The use of notify() must first acquire the object lock, and then wake up the suspended thread (suspended because of waiting for the object lock)

 

notifyAll(): wake up all threads waiting on this object monitor.

wait()  causes the current thread to wait before other threads call notify() methods or  notifyAll()methods of this object  .

Throws:-if theIllegalMonitorStateException  current thread is not the owner of this object monitor

package com.taobao.concurrency;

public class WaitTest {
    public static String a = "";//  

    public static void main(String[] args) throws InterruptedException {
        WaitTest wa = new WaitTest();
        TestTask task = wa.new TestTask();
        Thread t = new Thread(task);
        t.start();
        Thread.sleep(12000);
        for (int i = 5; i > 0; i--) {
            System.out.println(" ************");
            Thread.sleep(1000);
        }
        System.out.println(" ************");
        synchronized (a) {
            a.notifyAll();
        }
    }

    class TestTask implements Runnable {

        @Override
        public void run() {
            synchronized (a) {
                try {
                    for (int i = 10; i > 0; i--) {
                        Thread.sleep(1000);
                        System.out.println("  ***************");
                    }
                    a.wait();
                    for (int i = 10; i > 0; i--) {
                        System.out.println(" ********** *******");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
} 


Use wait notify to solve the producer consumer problem code:

package com.taobao.concurrency;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
class Meal {
    private final int orderNum;

    public Meal(int orderNum) {
        this.orderNum = orderNum;
    }

    public String toString() {
        return "Meal " + orderNum;
    }
}

class WaitPerson implements Runnable {
    private Restaurant restaurant;

    public WaitPerson(Restaurant r) {
        this.restaurant = r;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (restaurant.meal == null)
                        wait();// ..for the chef to produce a meal
                }
                System.out.println("WaitPerson got" + restaurant.meal);
                synchronized (restaurant.chef) {
                    restaurant.meal = null;
                    restaurant.chef.notifyAll();// ready for another
                }
            }
            TimeUnit.MICROSECONDS.sleep(100);
        } catch (InterruptedException e) {
            System.out.println("WaitPerson interrupted");
        }
    }
}

class Chef implements Runnable {
    private Restaurant restaurant;
    private int count = 0;

    public Chef(Restaurant r) {
        this.restaurant = r;
    }

    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                synchronized (this) {
                    while (restaurant.meal != null)
                        wait();// ...for the meal to be taken
                }
                if (++count == 10) {
                    System.out.println("Out of food,closing");
                    restaurant.exec.shutdownNow();
                }
                System.out.println("Order up!");
                synchronized (restaurant.waitPerson) {
                    restaurant.meal = new Meal(count);
                    restaurant.waitPerson.notifyAll();
                }

            }
        } catch (InterruptedException e) {
        }
    }
}

public class Restaurant {
    Meal meal;
    ExecutorService exec = Executors.newCachedThreadPool();
    WaitPerson waitPerson = new WaitPerson(this);
    Chef chef = new Chef(this);

    public Restaurant() {
        exec.execute(chef);
        exec.execute(waitPerson);
    }
    public static void main(String[] args) {
        new Restaurant();
    }

} 


Use ArrayBlockingQueue to solve the producer-consumer problem ; unfair locks are used by default

take(): Take the first object in the BlockingQueue. If the BlockingQueue is empty, block and enter the waiting state until Blocking has a new object added. If the request fails, the thread will be added to the blocking queue;

If a fair lock is used, when there is content to be consumed, the consumer thread will be taken from the head of the queue for consumption (that is, the thread with the longest waiting time)

add(anObject): add anObject to the BlockingQueue, that is, if the BlockingQueue can accommodate, it returns true, otherwise the recruitment is abnormal

package com.taobao.concurrency;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestBlockingQueues {

    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
        Thread pro = new Thread(new Producer(queue), " ");
        pro.start();
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Concumer(queue), "  " + i);
            t.start();
        }

    }

}

class Producer implements Runnable {
    BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
                System.out.println("   " + i);
                queue.put("   " + i++);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println(" ");
            }
        }
    }
}

class Concumer implements Runnable {
    BlockingQueue<String> queue;

    public Concumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            try {
                System.out.println(Thread.currentThread().getName() + " "
                        + queue.take());
            } catch (InterruptedException e) {
                System.out.println(" ");
            }
        }
    }
}


  

Consumer 0 Request
Consumer 2 Request
Consumer 4 Request
Consumer 6 Request
Consumer 8 Request
Consumer 5 Request Consumer
Producer to Produce Food, Food Number: 0
Consumer 0 Consumer: Food 0
Consumer 1 Request for
consumer 3 Request for
consumer 7 Request for
consumer 9 Request for
consumer 0 Request for consumer
producer to produce food, food number: 1
consumer 2 consumer: food 1
consumer 2 request consumer
producer to produce food, food The number is: 2
consumer 4 consumption: food 2
consumer 4 request consumer
producer to produce food, food number: 3
consumer 6 consumption: food 3
consumer 6 request consumer
producer to produce food, food number: 4
consumer 8 Consumption: Food 4
Consumer 8 Request the consumer
producer to produce food, Food code: 5
Consumer 5 Consumption: Food 5 Producer produces food, Food code: 8 Consumer 7 Consumption: Food 8
Consumer 5 Request consumer
producer to produce food, Food No.: 6
Consumer 1 Consumption: Food 6
Consumption Person 1 requests the consumer
producer to produce food, the food number is: 7
consumer 3 consumption: food 7
consumer 3 requests consumption


consumer 7 requests consumer
producer to produce food, food code is: 9
consumer 9 consumption: food 9
consumer 9 requests consumption


 


Multiple producers, multiple consumers

package com.taobao.concurrency;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

public class TestBlockingQueues {

    public static void main(String[] args) {
        BlockingQueue<String> queue = new ArrayBlockingQueue<String>(20);
        for (int i = 0; i < 10; i++) {
            Thread pro = new Thread(new Producer(queue), " " + i);
            pro.start();
        }
        for (int i = 0; i < 10; i++) {
            Thread t = new Thread(new Concumer(queue), "  " + i);
            t.start();
        }

    }

}

class Producer implements Runnable {
    BlockingQueue<String> queue;

    public Producer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {

        int i = 0;
        while (true) {
            try {
               
                    System.out.println(Thread.currentThread().getName()
                            + "   " + Thread.currentThread().getName()
                            + i);
                    queue.put("   " + Thread.currentThread().getName() + i++);
                    Thread.sleep(10000);
               
            } catch (InterruptedException e) {
                System.out.println(" ");
            }
        }
    }
}

class Concumer implements Runnable {
    BlockingQueue<String> queue;

    public Concumer(BlockingQueue<String> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName() + "  ");
            try {
                System.out.println(Thread.currentThread().getName() + " "
                        + queue.take());
                Thread.sleep(100);
            } catch (InterruptedException e) {
                System.out.println(" ");
            }
        }
    }
}

 0   00
 2   20
 1   10
 3   30
 4   40
 6   60
 8   80
 5   50
 7   70
 9   90
  0  
  0     00
  2  
  2     20
  1  
  1     10
  4  
  4     30
  3  
  6  
  6     40
  3     60
  8  
  8     80
  5  
  5     50
  7  
  7     70
  9  
  9     90
  0  
  1  
  2  
  4  
  3  
  5  
  7  
  9  
  6  
  8  
 0   01
  0     01
 2   21
 4   41
  1     21
 1   11
  2     41
  4     11
 3   31 


 

Conditional queue explanation:

Condition queues  are like the "toast is ready" bell on your toaster. If you are listening for it, you are notified promptly when your toast is ready and can drop what you are doing (or not, maybe you want to finish the newspaper first ) and get your toast. If you are not listening for it (perhaps you went outside to get the newspaper), you could miss the notification, but on return to the kitchen you can observe the state of the toaster and either retrieve the toast if it is finished or start listening for the bell again if it is not.


Condition-based: In the case of multi-threading, a certain condition is false at a certain moment, which does not mean that it has been false all the time.


The default lock used by Lock is an unfair lock; the condition object inherits the co-flatness characteristics of the related lock. If it is a fair lock, the thread will be released from Condition.wait in the order of FIFO; one of the ArrayBlockingQueue is less The good place is that after every production, the producer must notify the consumer, as to whether there is a performance loss TODO