preface
Thread safety is a very basic piece of knowledge, but basic does not mean simple, and a person’s basic capabilities often determine whether he can write high-quality, high-performance code. Synchronized, Lock, and volatile are concepts that you can all agree on, but you can also write a deadlock if you’re not careful. Based on the producer-consumer model and specific cases, this article will explain the birth background and solutions of thread safety problems step by step. This article will help you grasp the application scenarios of synchronized and the differences with Lock.
1. The birth background and solution of thread safety problem
1.1 Why do Threads need to communicate?
Thread is the basic unit of CPU execution. In order to improve CPU utilization and simulate the scenario of multiple applications running at the same time, the concept of multithreading was derived. Under the JVM architecture, heap memory and method areas can be shared by threads, so why design this way? Here’s an example to briefly describe:
Now we need to make a network request, and render the response to the mobile phone interface. In order to improve the user experience, Android treats the main thread as the UI thread and only does interface rendering. Time-consuming operations should be handed over to the worker thread. If time-consuming operations on the UI thread can block, the most intuitive feeling is that the interface is stuck. The network request is an IO operation and will block. Imagine that the UI thread is not allowed to block, so the network request must be thrown to the worker thread, but how to get the packet to the UI thread? The most common approach is to call back the interface, parse the HTTP packet to the local model, and pass the value of the heap memory address corresponding to the local model to the UI thread through the interface.
The process by which a worker thread passes the address of a heap object to the UI thread is called interthread communication, which is why the JVM sets heap memory to be shared by threads. An easy-to-understand description of interthread communication is that “multiple threads operate on the same resource” located in the heap or method area
1.2 Safety problems caused by single production and single consumption
“Multiple threads operating on the same resource” sounds so simple that it can cause fatal problems if you’re not careful. Oh, what do you mean? Take your time. Let me tell you… There is a vehicle company in this case, which mainly deals in four-wheel cars and two-wheel bicycles. Workers are responsible for production and salesmen are responsible for sales. How can the above cases be implemented through applications? The idea is as follows: Define a vehicle resource class that can be set for cars and bicycles
public class Resource {
// A car corresponds to an ID
private int id;
/ / a multitude
private String name;
// The number of wheels
private int wheelNumber;
// tag (used later)
private boolean flag = false; . Ignore setters, getters...@Override
public String toString() {
return "id=" + id + "--- name=" + name + "--- wheelNumber="+ wheelNumber; }}Copy the code
Define a worker thread task that is specifically used to produce four-wheel cars and two-wheel bicycles for the producer
public class Input implements Runnable{
private Resource r;
public Input(Resource r){
this.r = r;
}
public void run() {
// Unlimited vehicle production
for(int i =0;; i++){if(i%2= =0){
r.setId(i);// Set the car id
r.setName("小汽车");// Set the car type
r.setWheelNumber(4);// Set the number of wheels
}else{
r.setId(i);// Set the car id
r.setName("Electric car");// Set the car type
r.setWheelNumber(2);// Set the number of wheels}}}}Copy the code
Define a salesman thread task that is specifically used to sell vehicles for consumers
public class Output implements Runnable{
private Resource r;
public Output(Resource r){
this.r = r;
}
public void run() {
// Unlimited consumption of vehicles
for(;;) {// Consume vehicles
System.out.println(r.toString()); }}}Copy the code
Start producing and consuming
// Resource object, corresponding to the vehicle
Resource r = new Resource();
// Producer runnable, corresponding to worker
Input in = new Input(r);
// Consumer runnable, corresponding to salesman
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
// Start the producer thread
t1.start();
// Start the consumer thread
t2.start();
Copy the code
Print result:
. id=51-- name= ev -- wheelNumber=2
id=52--- name=小汽车--- wheelNumber=2.Copy the code
Everything in an orderly way, the boss counted the money that call a happy. Just as the boss was preparing to hand out bonuses to the employees, there was a serious problem: car no. 52 was missing two wheels!! Well, not only did I lose the bonus, but I had to work all night to troubleshoot the problem. The same below
- The producer thread gets CPU execution authority and will
name
andwheelNumber
Set to electric car and 2, then switch the CPU to the consumer thread. - The consumer thread gets CPU execution authority at this point
name
andwheelNumber
Don’t letElectric cars
and2
, and then printName = ev -- wheelNumber=2
The CPU switches to the producer thread. - The producer thread gets CPU execution again, sets name to car (wheelNumber is not set), then name and wheelNumber are car and 2 respectively, and the CPU switches to the consumer thread.
- The consumer thread gets CPU execution, at which point the name and wheelNumber are car and 2, and then prints
Name = car -- wheelNumber=2
Worker: "Halfway to production, you salesman took it to sell, I don't carry this pot."
Solution: The cause is that the producer has not finished an operation on Resource, but the consumer intervenes. At this point, the synchronized keyword can be introduced, so that consumers can not intervene before the producer’s work is finished. The modified code is as follows:
#Input
public void run() {
// Unlimited vehicle production
for(int i =0;; i++){ synchronized(r){if(i%2= =0){
r.setId(i);// Set the car id
r.setName("小汽车");// Set the car type
r.setWheelNumber(4);// Set the number of wheels
}else{
r.setId(i);// Set the car id
r.setName("Electric car");// Set the car type
r.setWheelNumber(2);// Set the number of wheels
}
}
}
}
#Output
public void run() {
for(;;) { synchronized(r){// Consume vehicles
System.out.println(r.toString()); }}}Copy the code
Synchronized is added to the producer and consumer for loops, and the corresponding lock is r
. id=79-- name= ev -- wheelNumber=2
id=80--- name=小汽车--- wheelNumber=4
id=80--- name=小汽车--- wheelNumber=4.Copy the code
Everything is back to normal. But a more serious problem was revealed, the car number 80 was consumed (sold) twice and the salesman sold the same car to two customers, what a business genius!! Causes:
- The producer thread gets CPU execution authority and will
name
andwheelNumber
Set toThe car
and4
CPU execution is then switched to the consumer thread. - The consumer thread gets CPU execution authority at this point
name
andwheelNumber
Don’t letThe car
and4
, and then printName = car -- wheelNumber=4
, but the CPU execution is not switched to the producer thread after consumption, but is continued by the consumer thread, so the number is80
theThe car
bePrint (consumption)
The two
Solution: The cause of the problem is that consumers do not wait for resources after consumption, but continue to consume. In this case, wait and notify mechanisms can be introduced to make the salesman wait after selling a car. When the worker produces a new car, the salesman will notify the salesman and sell the car after receiving the message from the worker. The changed code looks like this:
#Input
public void run() {
// Unlimited vehicle production
for(int i =0;; i++){ synchronized(r){// If flag is true, it indicates that production has been completed. In this case, the current thread waits for consumers to consume
if(r.isFlag()){
try {
r.wait();
} catch(InterruptedException e) { e.printStackTrace(); }}if(i%2= =0){
r.setId(i);// Set the car id
r.setName("小汽车");// Set the car model
r.setWheel(4);// Set the number of wheels
}else{
r.setId(i);// Set the car id
r.setName("Electric car");// Set the car model
r.setWheel(2);// Set the number of wheels
}
r.setFlag(true);
// Wake up the thread in the thread pool
r.notify();
}
}
}
#Output
public void run() {
// Unlimited consumption of vehicles
for(;;) { synchronized(r){// Flag is false, indicating that the vehicle currently produced has been consumed,
// Enter the wait state for producer production
if(! r.isFlag()){try {
r.wait();
} catch(InterruptedException e) { e.printStackTrace(); }}// Consume vehicles
System.out.println(r.toString());
r.setFlag(false);
// Wake up the thread in the thread poolr.notify(); }}}Copy the code
Print result:
. id=129-- name= ev -- wheelNumber=2
id=130--- name=小汽车--- wheelNumber=4
id=131-- name= ev -- wheelNumber=2.Copy the code
This time there was no problem. Both the workers and the salesman got the bonus from the boss as they wished.
Synchronized parentheses pass in a lock, which can be any type of object. Producers and consumers must use the same lock to achieve synchronization. The purpose of this design is to make the use of synchronized blocks more flexible, otherwise the whole process is so synchronized that it is not clear who does not lock
Note 2:
Wait and notify are object methods that can only be called by a lock within a synchronized block; otherwise exceptions are thrown. Each lock corresponds to an area of the thread pool. The thread being waited will be placed in the area of the lock and the lock will be released. Notify will randomly wake up any thread in the corresponding thread pool. If the thread is woken up, the lock will be re-locked
2. The deadlock problem shows the application scenario of Lock
2.1 What is deadlock?
A deadlock, as the name implies, is a thread that is suspended but not dead, stuck in the middle and unable to be collected. Here’s an example:
class Deadlock1 implements Runnable{
private Object lock1;
private Object lock2;
public Deadlock1(Object obj1,Object obj2){
this.lock1 = obj1;
this.lock2 = obj2;
}
public void run() {
while(true){
synchronized(lock1){
System.out.println("Deadlock1----lock1");
synchronized(lock2){
System.out.println("Deadlock1----lock2");
}
}
}
}
}
class Deadlock2 implements Runnable{
private Object lock1;
private Object lock2;
public Deadlock2(Object obj1,Object obj2){
this.lock1 = obj1;
this.lock2 = obj2;
}
public void run() {
while(true){
synchronized(lock2){
System.out.println("Deadlock2----lock2");
synchronized(lock1){
System.out.println("Deadlock2----lock1"); }}}}} # runprivate static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Deadlock1 d1 = new Deadlock1(lock1,lock2);
Deadlock2 d2 = new Deadlock2(lock1,lock2);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
Copy the code
Print the result after running:
Deadlock1----lock1
Deadlock2----lock2
Copy the code
The run() method says an infinite loop, which is supposed to print indefinitely. But when the program runs, it only prints these two lines without me terminating the console. This process actually causes a deadlock for the following reasons:
- thread
t1
Execute to determine the first synchronized code block, which lockslock1
Yes, so hold the locklock1
Enter the first sync block and print:Deadlock1----lock1
Then the thread switches to the threadt2
- thread
t2
Execute to determine the first synchronized code block, which lockslock2
Yes, so hold the locklock2
Enter the first sync block and print:Deadlock2----lock2
, then proceed down to determine the locklock1
Not available (because of a locklock1
Has been threadedt1
Occupied), so the threadt1
Wait. Then switch to the thread againt1
- thread
t1
Execute to determine the second synchronized code block, which lockslock2
Not available (becauselock2
Has been threadedt2
Occupied), threadst1
Also entered the waiting state
Described by above knowable, thread t1 hold thread t2 waiting for the lock, thread t2 hold thread t1 need to wait for, lock two threads with each other need to lock in a stalemate phenomenon, cause the thread to feign death namely dead lock The above case is just one kind of deadlock, the deadlock of standard is to determine whether a thread is in a state of suspended animation
2.2 How to avoid deadlock in production and consumption scenario?
The first section is mainly about single production and single consumption. In order to further improve operating efficiency, multiple production and multiple consumption can be appropriately introduced, that is, multiple producers and multiple consumers. Continue with the example in section 1, with minor changes:
// The producer task
class Input implements Runnable{
private Resource r;
// Write I as a member variable instead of in the for loop for the sake of producing more consume more
private int i = 0;
public Input(Resource r){
this.r = r;
}
public void run() {
// Unlimited vehicle production
for(;;) { synchronized(r){// If flag is true, it indicates that production has been completed. In this case, the current thread waits for consumers to consume
if(r.isFlag()){
try {
r.wait();
} catch(InterruptedException e) { e.printStackTrace(); }}if(i%2= =0){
r.setId(i);// Set the car id
r.setName("小汽车");// Set the car model
r.setWhell(4);// Set the number of wheels
}else{
r.setId(i);// Set the car id
r.setName("Electric car");// Set the car model
r.setWhell(2);// Set the number of wheels
}
i++;
r.setFlag(true);
// Wake up the thread in the thread poolr.notify(); }}}}public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread in1= new Thread(in);
Thread in2 = new Thread(in);
Thread out1 = new Thread(out);
Thread out2 = new Thread(out);
in1.start();// Start the producer 1 thread
in2 .start();// Start the producer 2 thread
out1 .start();// Start consumer 1 thread
out2 .start();// Start consumer 2 thread
}
Copy the code
Running results:
id=211-- name= bicycle -- wheelNumber=2
id=220--- name=小汽车--- wheelNumber=4
id=220--- name=小汽车--- wheelNumber=4
id=220--- name=小汽车--- wheelNumber=4.Copy the code
Safety issues arose again, the 211-220 vehicle was not printed, that is, produced and not consumed. At the same time, the vehicle number 220 was printed three times. Don’t worry, I will continue to give you analysis:
Producer thread IN1
Got the enforcement authority, gave birthid
for211
The vehicle willflag
Set totrue
, loop back and determine the mark astrue
And at this time ofwait()
The method enters the wait stateProducer thread in2
Get enforcement authority, judge marked astrue
, the implementation ofwait()
The method enters the wait state.The consumer thread out1
Get enforcement authority, judge marked astrue
Instead of waiting, they chose to consumeid
for211
After consumption, the mark is set asfalse
And performnotify()
Wake up any thread in the thread poolin1
Producer thread IN1
Gets the executive power again, nowProducer thread IN1
When awakened, it will not judge the mark and will choose to produce oneid
for1
, and then set the marker totrue
And performnotify()
Wake up any thread in the thread poolin2
Producer thread in2
Gets the executive power again, nowProducer thread in2
When they wake up, they don’t judge the mark but produce oneid
for212
The vehicle then wakes upin1
productionid
for213
The vehicle, and then wake upin2
.
The above is the reason why the vehicle no. 211-220 is not printed, and the same is true for the vehicle no. 220. How to solve it? In fact, it is very simple to change the “if” in the place where producers and consumers judge the flag to “while”, and then judge the flag again after being woken up. The code will not be repeated, the results are as follows:
id=0--- name=小汽车--- wheelNumber=4
id=1-- name= ev -- wheelNumber=2
id=2--- name=小汽车--- wheelNumber=4
id=3-- name= ev -- wheelNumber=2
id=4--- name=小汽车--- wheelNumber=4
Copy the code
It looks normal, but when I print to vehicle number 4 without the console, it stops. Yes, deadlocks appear. The specific reasons are as follows:
Thread in1
Start executing and produce a car that willflag
Set totrue
, loop back to judgeflag
Enter thewait()
State, where the threads waiting in the thread pool are:in1
Thread in2
Start executing, judgeflag
fortrue
Enter thewait()
State, where the threads waiting in the thread pool are:In1 and in2
Thread the out1
Start executing, judgeflag
fortrue
, the consumption of a car willflag
Set tofalse
And wake up a thread, let’s say wake up isin1
(It should be noted here that being awakened does not mean that you will execute immediately, but that you are currently qualified to execute but not authorized to execute),Thread the out1
Loop back and readflag
Enter thewait
State, where threads in the thread pool haveIn2, out1
And thenout2
Get enforcement authorityThread out2
Start execution, judge marked asfalse
, enters the wait state when the threads in the thread pool haveIn2, out1, out2
Thread in1
Start execution, judge marked asfalse
The production of a car is bound toflag
Set totrue
And wakes up a thread in the thread pool, let’s sayin2
And thenin1
Cycle judgmentflag
Enter thewait()
State, where threads in the thread pool haveIn1, out1, out2
Thread int2
Get enforcement authority, judge marked asfalse
And into thewait()
State, where threads in the thread pool haveIn1, in2, out1, out2
All producer consumer threads were waited, resulting in a deadlock. Object also has a notifyAll() method, which wakes up all threads in the corresponding thread pool. NotifyAll () is used instead of notifyAll to solve the deadlock problem
2.3 Use Lock to solve deadlock problems gracefully
2.2 notifyAll can solve the deadlock problem, but it is not elegant, because notifyAll() wakes up all the threads in the corresponding thread pool. In fact, notifyAll() only needs to wake up one thread, which causes the thread to be waited repeatedly, resulting in performance problems. Therefore, Java introduced the concept of display Lock Lock in version 1.5, which can flexibly specify the scope of wait and notify, specifically used to solve this problem. 2.2 deadlock problem improved by displaying Lock Lock:
# producersclass Input implements Runnable{
private Resource r;
private int i = 0;
private Lock lock;
private Condition in_con;// Producer monitor
private Condition out_con;// Consumer monitor
public Input(Resource r,Lock lock,Condition in_con,Condition out_con){
this.r = r;
this.lock = lock;
this.in_con = in_con;
this.out_con = out_con;
}
public void run() {
// Unlimited vehicle production
for(;;) { lock.lock();/ / acquiring a lock
// If flag is true, it indicates that production has been completed. In this case, the current thread waits for consumers to consume
while(r.isFlag()){
try {
in_con.await();// The same as wait
} catch(InterruptedException e) { e.printStackTrace(); }}if(i%2= =0){
r.setId(i);// Set the car id
r.setName("小汽车");// Set the car model
r.setWhell(4);// Set the number of wheels
}else{
r.setId(i);// Set the car id
r.setName("Electric car");// Set the car model
r.setWhell(2);// Set the number of wheels
}
i++;
r.setFlag(true);
// Wake up the consumer thread in the thread pool
out_con.signal();
lock.unlock();/ / releases the lock}}}/ / consumer
class Output implements Runnable{
private Resource r;
private Lock lock;
private Condition in_con;// Producer monitor
private Condition out_con;// Consumer monitor
public Output(Resource r,Lock lock,Condition in_con,Condition out_con){
this.r = r;
this.lock = lock;
this.in_con = in_con;
this.out_con = out_con;
}
public void run() {
// Unlimited consumption of vehicles
for(;;) { lock.lock();/ / acquiring a lock
while(! r.isFlag()){try {
out_con.await();// Make the consumer thread wait
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(r.toString());
r.setFlag(false);
in_con.signal();// Wake up the producer thread
lock.unlock();/ / releases the lock}}}public static void main(String[] args) {
Resource r = new Resource();
Lock lock = new ReentrantLock();
// Producer monitor
Condition in_con = lock.newCondition();
// Consumer monitor
Condition out_con = lock.newCondition();
Input in = new Input(r,lock,in_con,out_con);
Output out = new Output(r,lock,in_con,out_con);
Thread t1 = new Thread(in);
Thread t2 = new Thread(in);
Thread t3 = new Thread(out);
Thread t4 = new Thread(out);
t1.start();// Start the producer thread
t2.start();// Start the producer thread
t3.start();// Start the consumer thread
t4.start();// Start the consumer thread
}
Copy the code
This time it really is. Where Lock corresponds to synchronized, Condition refers to the monitor under Lock, and each monitor corresponds to a scope of wait and notify
From what has been discussed above
- Multithreading is used to increase CPU usage
- Multiple threads accessing the same resource can raise security issues
- Synchronized, together with Wait and Notify, can solve thread safety problems
- Lock can overcome the limitations of synchronized wait and notify
It’s 12 o ‘clock at night… If you’re interested, feel free to leave a comment in the comments section. In my next post, I’ll cover the background of volatile design and its differences from synchronized