synchronized in Java: a brief analysis of characteristics, usage, locking mechanism and strategy

Features of synchronized

mutual exclusivity

synchronized ensures that only one thread can enter the synchronized block or synchronized method at the same time, avoiding the conflict problem of multiple threads concurrently accessing shared resources.
synchronized will have a mutual exclusion effect. When a thread executes synchronized on an object, other threads will block and wait if they also execute synchronized on the same object.
Let's look at an example below. Two threads acquire the same lock. After the lock is occupied, the remaining thread will block and wait.

public class test2 {
    
    
    public static void main(String[] args) {
    
    
        Object object = new Object();
        Thread t1 =  new Thread(()->{
    
    
        //进入 synchronized 修饰的代码块, 相当于 加锁
          synchronized (object) {
    
    
              for (int i = 0; i < 5; i++) {
    
    
                  System.out.println("线程t1获取锁");
                  try {
    
    
                      Thread.sleep(1000);
                  } catch (InterruptedException e) {
    
    
                      throw new RuntimeException(e);
                  }
              }
          }
        //退出 synchronized 修饰的代码块, 相当于 解锁  
        });
        Thread t2 = new Thread(()->{
    
    
            synchronized (object) {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    System.out.println("线程特t2获取锁");
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();
        t2.start();
    }
}

Insert image description here
From the results, we can know that after thread one releases the lock, thread two must be awakened by the operating system to obtain the lock.

The bottom layer of synchronized is implemented using the mutex lock of the operating system.

visibility

Memory visibility means that when one thread modifies the value of a shared variable, other threads can immediately see the modified value. In a multi-threaded environment, since multiple threads access shared variables at the same time, each thread has its own working memory, and the working memory stores a partial copy of the data in the main memory. Therefore, when a thread modifies the value of a shared variable, but the modification has not yet been flushed to the main memory, other threads may not be able to see the modification immediately and continue to use the old value in their own working memory, causing memory invisibility. .

synchronized can ensure both atomicity and memory visibility. Modifications of shared variables by one thread are visible to other threads.

class Counter {
    
    
    public static int flag = 0;
}

public class test3 {
    
    
    public static void main(String[] args) {
    
    
        Object object = new Object();
        Thread t1 = new Thread(() -> {
    
    
            while (true) {
    
    
                synchronized (object) {
    
    
                    if (Counter.flag != 0) {
    
    
                        break;
                    }
                }
            }
            System.out.println("线程一知道了共享变量改为" + Counter.flag);
        });
        Thread t2 = new Thread(() -> {
    
    
            Scanner scanner = new Scanner(System.in);
            System.out.println("输入一个整数:");
            Counter.flag = scanner.nextInt();
        });
        t1.start();
        t2.start();
    }
}

Insert image description here
If the thread is not synchronized, it will not be aware of changes in the shared variables, so that the program will always be running.
Insert image description here

Reentrancy

The synchronized synchronization block is reentrant for the same thread, and there will be no problem of locking itself.
It can be understood that a thread did not release the lock, and then tried to lock again.
According to the previous understanding of locks, if the lock is not released, locking again will block. The second lock cannot be acquired until the first lock is released, but the first lock is also released by the thread. , as a result, this thread can't do anything now, and it can only form a deadlock.
Such a lock is called a non-reentrant lock.

Our synchronized is a reentrant lock.
There are two pieces of information inside the reentrant lock, namely "program counter" and "thread holder"

  • If a thread finds that the lock is already occupied by someone else when it locks, but it happens to be occupied by itself, it can still continue to acquire the lock and let the counter increment.
  • When the counter is decremented to 0 during unlocking, the lock is actually released.

How to use synchronized

  1. Directly modify ordinary methods: the SynchronizedDemo object of the lock
public synchronized void methond() {
    
    
}
  1. Modify static method: lock object of SynchronizedDemo class
public synchronized static void method() {
    
    
}
  1. Decorate code block: explicitly specify which object to lock
  • Lock the current object
public void method() {
    
    
synchronized (this) {
    
    
}
}
  • lock object
public void method() {
    
    
synchronized (SynchronizedDemo.class) {
    
    
}
}

synchronized lock mechanism

  1. Object lock: The synchronized keyword can be applied directly to instance methods or instance code blocks. When a thread enters a synchronized instance method or instance code block, it automatically acquires the object's built-in lock. Only after the thread releases the lock can other threads enter the synchronized block.

  2. Class lock: The synchronized keyword can be applied to static methods or class code blocks. When a thread enters a static method or class code block modified by synchronized, it will automatically acquire the built-in lock of the Class object of the class. Class locks belong to the entire class. For different instances of the same class, they share the same class lock.

  3. Lock object: You can use the synchronized keyword to lock the specified object. By specifying an object as a lock, multiple threads can synchronize based on this object. When a thread enters a synchronized code block, it will try to acquire the built-in lock of the specified object. Only after the thread releases the lock, other threads can obtain the lock and execute synchronized code.

Common lock strategies

Optimistic locking and pessimistic locking

Pessimistic locking locks data before it is used to prevent data from being modified by other threads.
Optimistic locking checks whether the data has been modified by other threads when updating the data. If not, the update is successful, otherwise it returns failure.
Synchronized initially uses an optimistic locking strategy. When lock competition is found to be frequent, it will automatically switch to a pessimistic locking strategy.

Heavyweight locks and lightweight locks

A lightweight lock is an optimized lock that uses the CPU's spin mechanism during CAS operations. If the spin is successful, the lock is acquired, otherwise it goes to sleep.
A heavyweight lock is a traditional lock that relies on the operating system's MutexLock (mutex lock) to implement. When multiple threads compete for the same lock, other threads will be blocked waiting for release.

Fair lock and unfair lock

Suppose there are three threads A, B, and C that acquire the same lock in sequence . Thread A acquires the lock successfully, but threads B and C fail to acquire it.
After waiting for thread A to release the lock, how do threads B and C acquire the lock? Insert image description here
Fair lock policy: Follow "first come, first served". B came before C. When A releases the lock, B can acquire the lock before C.
Unfair lock strategy: "first come, first served" is not respected. Both B and C may acquire the lock.
synchronized is an unfair lock

Reentrant locks and non-reentrant locks

Reentrant locks mean that the same thread can acquire the same lock multiple times.
In Java, any lock named starting with Reentrant is a reentrant lock, and all ready-made Lock implementation classes provided by the JDK, including synchronized keyword locks, are reentrant.
It can be understood that a thread did not release the lock, and then tried to lock again.
According to the previous understanding of locks, if the lock is not released, locking again will block. The second lock cannot be acquired until the first lock is released, but the first lock is also released by the thread. , as a result, this thread can't do anything now, and it can only form a deadlock.
Such a lock is called a non-reentrant lock.

synchronized is a reentrant lock

spin lock

In order to prevent the thread from entering the blocked state after failing to grab the lock, it will take a long time to be scheduled again.

while (!locked.compareAndSet(false, true)) {
    
    
            // 不断循环直到获取到锁
        }

If acquisition of the lock fails, immediately try to acquire the lock again, looping infinitely until the lock is acquired. The first attempt to acquire the lock fails, and the second attempt will come within a very short period of time.
Disadvantages: If the lock is held by other threads for a long time, CPU resources will continue to be consumed.
The lightweight lock strategy in synchronized is most likely implemented through spin locks.

read-write lock

There are two main operations for a thread to access data: reading data and writing data.

  • Both threads only read one piece of data, and there is no thread safety issue at this time. Just read it concurrently.

  • Both threads have to write a piece of data, which has thread safety issues.

  • When one thread reads and another thread writes, there are also thread safety issues.

    Read-write locks treat read operations and write operations differently. The Java standard library provides the ReentrantReadWriteLock class to implement read-write locks.

  • The ReentrantReadWriteLock.ReadLock class represents a read lock. This object provides lock/unlock methods for locking and unlocking.

  • The ReentrantReadWriteLock.WriteLock class represents a write lock. This object also provides lock/unlock methods for locking and unlocking.

There is no mutual exclusion between read locking and read locking. There is mutual exclusion
between write locking and write locking. There is mutual
exclusion between read locking and write locking.

Synchronized is not a read-write lock

If you want to know more, you can also read my notes column hahaInsert image description here

Guess you like

Origin blog.csdn.net/st200112266/article/details/133100680