Example analysis of synchronized keyword and thread safety in Java

  • 2021-07-24 10:45:29
  • OfStack

First, let's review the basic use of synchronized under 1:

synchronized code block, the modified code becomes a synchronization statement block, and its function scope is to call the object of this code block. When we use synchronized keyword, we can narrow the scope of the code segment as much as possible. If we can add synchronization to the code segment, we should not add synchronization to the whole method. This is called reducing the granularity of locks and making code concurrent to a greater extent. synchronized method, the modified method becomes a synchronous method, and its scope is the whole method, and the object of action is the object that calls this method. synchronized static method, which modifies an static static method. Its scope is the whole static method, and its object is all the objects of this class. The synchronized class, whose scope is the parenthesized portion of synchronized after Synchronized (className. class), and whose objects are all objects of the class. synchronized () () is a locked object, synchronized (this) locks only the object itself, synchronized method called by different objects of the same class will not be locked, while synchronized (className. class) realizes the function of global lock, and all the objects of this class call this method are affected by lock. In addition, () can also add a specific object to realize the locking of specific objects.

synchronized (object) {
 // Operating on objects in synchronization code blocks  
}

synchronized Keyword and Thread Safety
I thought it was safe to synchronize threads by wrapping the code with synchronized keyword. Tested it. It was completely wrong to find out. synchronized must be used correctly to be truly thread safe. . . Although I know this writing method, I think I have used the wrong method because I am lazy.
It seems that the foundation has not been laid yet. Still need to review and strengthen! It is unforgivable to make such mistakes at work, knowing that where you use the synchronized keyword is data sensitive! Sweat 1. . .
Paste the code first:


package com; 
 
public class ThreadTest { 
 public static void main(String[] args) { 
  MyThread m1 = new MyThread(1); 
  MyThread m2 = new MyThread(2); 
  m1.start(); 
  m2.start(); 
 } 
} 
 
final class MyThread extends Thread { 
 private int val; 
 
 public MyThread(int v) { 
  val = v; 
 } 
 // This practice is actually non-thread safe  
 public synchronized void print1(int v) { 
  for (int i = 0; i < 100; i++) { 
   System.out.print(v); 
  } 
 } 
 
 public void print2(int v) { 
  // Thread safety  
  synchronized (MyThread.class) { 
   for (int i = 0; i < 100; i++) { 
    System.out.print(v); 
   } 
  } 
 } 
 
 public void run() { 
  print1(val); 
  // print2(val); 
 } 
} 

Or to be lazy, sweat 1. . . Programmers are always lazy. Write less if you can. I wrote MyThread as an anonymous final inner class, which is convenient to call. It uses the most direct inheritance of Thread to implement a thread class, defining the run () method that needs to be run.
First annotate the print2 () method to see how the print1 () results. print1 () is a method defined using the synchronized keyword, which I thought would be thread-safe as well. As everyone knows, I was wrong.
Let's run the main () method directly. The console prints as follows:


1212111121212121212121212121212121212121222222212121212 . . .

It is the result of cross-printing 1 and 2 in series. And in my main method, I run m1 before running m2, which shows that thread synchronization is not achieved!


MyThread m1 = new MyThread(1); 
MyThread m2 = new MyThread(2); 
m1.start(); 
m2.start(); 

Next we comment out print1 () in the run method and run print2 ();
The console prints as follows:


11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111112222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222

Thread is indeed safe, 1 straight thought also know this way of writing, but because this way of writing a little bit more code is not much to consider, today to realize this kind of error. It seems that sometimes it is good not to be lazy. It is important to lay a good foundation. Correct a long-standing mistake.

Let's take a look at the specific reasons.

The synchronized keyword can be used as a modifier of a function or as a statement within a function, that is, a synchronization method and a synchronization statement block. If further classified, synchronized can be applied to instance variables, object reference (object reference), static functions, and class literals (class name literal constant).
Before going one step further, we need to be clear about several points:
A. Whether the synchronized keyword is applied to a method or an object, the lock it acquires is an object, not a piece of code or function as a lock, and the synchronized method is likely to be accessed by objects of other threads as well.
B. Each object has only one lock (lock) associated with it.
C. Synchronization is achieved at the expense of a large system overhead, and may even cause deadlock, so try to avoid unnecessary synchronization control.
Next, we will discuss the impact of synchronized on code when it is used in different places:
Assuming that P1 and P2 are different objects of the same class, this class defines synchronization blocks or synchronization methods in the following situations, and P1 and P2 can call them.
1. When using synchronized as a function modifier, the sample code is as follows:


Public synchronized void methodAAA() 
{ 
// … . 
} 

This is the synchronization method, so which object is synchronized locking at this time? What it locks is the object that calls this synchronization method. That is to say, when an object P1 executes this synchronization method in different threads, they will form mutual exclusion and achieve the effect of synchronization. However, another object, P2, generated by Class to which this object belongs, can call this method with synchronized keyword at will.
The sample code above is equivalent to the following code:


public void methodAAA() 
{ 
synchronized (this) // (1) 
{ 
// … .. 
} 
} 

What does this at (1) mean? It refers to the object that calls this method, such as P1. It can be seen that the essence of synchronization method is to apply synchronized to object reference. The thread that gets the P1 object lock can call the synchronization method of P1, but for P2, the lock of P1 has nothing to do with it, and the program may get rid of the control of synchronization mechanism in this case, causing data confusion!

2. Synchronize the block, with the following example code:


public void method3(SomeObject so) 
{ 
synchronized(so) 
{ 
// … .. 
} 
} 


In this case, the lock is the object so, and whoever gets the lock can run the code it controls. You can write the program this way when you have an explicit object as a lock, but when you just want a piece of code to synchronize without an explicit object as a lock, you can create a special instance variable (which has to be an object) to act as a lock:


class Foo implements Runnable 
{ 
private byte[] lock = new byte[0]; //  Special instance Variable  
Public void methodA() 
{ 
synchronized(lock) { // …  } 
} 
// … .. 
} 

Note: A zero-length byte array object is more economical to create than any object. View the compiled bytecode: It takes only three opcodes to generate a zero-length byte [] object, while Object lock = new Object () requires seven lines of opcodes.
3. Apply synchronized to the static function, with the following example code:


Class Foo 
{ 
public synchronized static void methodAAA() //  Synchronous static  Function  
{ 
// … . 
} 
public void methodBBB() 
{ 
synchronized(Foo.class) // class literal( Class name literal constant ) 
} 
} 

The methodBBB () method in the code takes class literal as a lock, which has the same effect as the synchronous static function, and the obtained lock is very special, which is the class of the object that currently calls this method (Class, instead of a specific object produced by this Class).
I remember that in the book Effective Java, I saw that Foo. class and P1.getClass () are not used as synchronization locks, and P1.getClass () cannot be used to lock this Class. P1 refers to objects generated by the Foo class.
It can be inferred that if an static function A of synchronized and an instance function B of synchronized are defined in a class, the same object Obj of this class will not be synchronized when accessing A and B methods in multithreaded, because their locks are different. The lock of the A method is the object Obj, and the lock of B is the Class to which Obj belongs.
The summary is as follows:
Knowing which object synchronized locks can help us design more secure multithreaded programs.
There are one more tips that can make our synchronous access to shared resources more secure:
1. Define the instance variable of private + its get method instead of the instance variable of public/protected. If the variable is defined as public, the object can bypass the control of the synchronization method and directly obtain it and change it. This is also one of the standard implementations of JavaBean.
2. If the instance variable is an object, such as an array or ArrayList, the above method is still unsafe, because when the external object gets the reference of the instance object through the get method and points it to another object, then the private variable will change, which is not very dangerous. At this point, you need to synchronize the get method with synchronized, and only return the clone () of the private object so that the caller gets a reference to the copy of the object.

Summarize some synchronized precautions:

When two concurrent threads access the synchronized code block in the same object, only one thread can be executed at the same time, and the other thread is blocked, so it must wait for the current thread to execute the code block before executing the code block. The two threads are mutually exclusive, because the current object is locked when executing the synchronized code block, and the object lock cannot be released until the code block is executed, and the next thread can execute and lock the object. When one thread accesses one synchronized (this) synchronization code block of object, another thread can still access a non-synchronized (this) synchronization code block in the object. (Both threads use the same object) When one thread accesses one synchronized (this) synchronization block of object, access by other threads to all other synchronized (this) synchronization blocks of object is blocked (as above, both threads use the same object).


Related articles: