[java] Java implements synchronization method

Article directory

  • foreword
    • Why use synchronization?
    • Five ways to achieve synchronization in java:
  • 1. Synchronization method:
  • 2. Synchronized code block
  • 3. Use special domain variables (volatile) to achieve thread synchronization
  • 4. Use reentrant locks to achieve thread synchronization
  • 5. Use local variables to achieve thread synchronization

Foreword

Why use synchronization?

Avoid multi-thread data inconsistency. Java allows multi-thread concurrency control. When multiple threads operate a shareable resource variable at the same time (such as adding, deleting, modifying and checking data), the data will be inaccurate and conflict with each other. Therefore, add The synchronization lock prevents the thread from being called by other threads before the thread completes the operation, thereby ensuring the uniqueness and accuracy of the variable.

Five ways to achieve synchronization in java:

  1. A synchronization method decorated with the synchronized keyword;

  2. Code blocks decorated with the sychronized keyword

  3. Use special domain variables (volatile) member variables

  4. Use reentrantLock, which has lock and unlock methods

  5. Use local variables and manage variables with threadLoacl

Note: Using sychronized is a heavyweight synchronization method, and volatile is lightweight

1. Synchronization method:

That is, there is a method modified by the synchronized keyword.

Since each object of java has a built-in lock, when the method is decorated with this keyword, the built-in lock will protect the entire method. Before calling this method, the built-in lock needs to be obtained, otherwise it will be in a blocked state.

Code such as:

public synchronized void save(){<!-- -->}

Note: The synchronized keyword can also modify the static method. If the static method is called at this time, the entire class will be locked

2. Synchronized code block

 is a statement block modified by the synchronized keyword. The statement block modified by this keyword will be automatically added with a built-in lock to achieve synchronization

Code such as:
 synchronized(object){<!-- -->
 
    }
Note: Synchronization is a high-overhead operation, so the content of synchronization should be minimized.

Usually it is not necessary to synchronize the entire method, just use the synchronized code block to synchronize the key code.

Code example:
package com.lm.thread;
    /** The use of thread synchronization */
    public class SynchronizedThread {<!-- -->
        class Bank {<!-- -->
            private int account = 100;
            public int getAccount() {<!-- -->
                return account;
            }
            /*** implement with synchronous method */
            public synchronized void save(int money) {<!-- -->
                account + = money;
            }
   /** * Implemented with a synchronized code block */
            public void save1(int money) {<!-- -->
                synchronized (this) {<!-- -->
                    account + = money;
                }
            }
        }
        class NewThread implements Runnable {<!-- -->
            private Bank bank;
            public NewThread(Bank bank) {<!-- -->
                this.bank = bank;
            }
            @Override
            public void run() {<!-- -->
                for (int i = 0; i < 10; i ++ ) {<!-- -->
                    // bank. save1(10);
                    bank. save(10);
           System.out.println(i + "Account balance is: " + bank.getAccount());
                }
            }
         }
        /*** Create a thread and call the inner class */
        public void useThread() {<!-- -->
            Bank bank = new Bank();
            NewThread new_thread = new NewThread(bank);
            System.out.println("Thread 1");
            Thread thread1 = new Thread(new_thread);
            thread1. start();
            System.out.println("Thread 2");
            Thread thread2 = new Thread(new_thread);
            thread2.start();
        }
        public static void main(String[] args) {<!-- -->
            SynchronizedThread st = new SynchronizedThread();
            st. useThread();
        }
   }

3. Use special domain variables (volatile) to achieve thread synchronization

  • The volatile keyword provides a lock-free mechanism for domain variable access, which belongs to the lightweight synchronization strategy

  • Using volatile to modify the domain is equivalent to telling the virtual machine that the domain may be updated by other threads,

  • Therefore, each time the field is used, it must be recalculated instead of using the value in the register

  • Volatile does not provide any atomic operations, nor can it be used to modify variables of final type, ensuring visibility and prohibiting instruction rearrangement

For example:

In the above example, you only need to add volatile modification in front of account to realize thread synchronization.

//Only give the code to be modified, the rest of the code is the same as above
        class Bank {<!-- -->
            //Variables that need to be synchronized plus volatile
            private volatile int account = 100;
            public int getAccount() {<!-- -->
                return account;
            }
            // synchronized is no longer needed here
            public void save(int money) {<!-- -->
                account + = money;
            }
        }

Note: The asynchronous problem in multithreading mainly occurs in the reading and writing of domains. If the domain itself avoids this problem, there is no need to modify the method of operating the domain.

Using final domains, lock-protected domains and volatile domains can avoid asynchronous problems.

4. Use reentrant locks to achieve thread synchronization

In JavaSE5.0, a java.util.concurrent package has been added to support synchronization.

The ReentrantLock class is a reentrant, mutually exclusive lock that implements the Lock interface. It has the same basic behavior and semantics as using the synchronized method and block, and extends its capabilities. The common methods of the ReentrantLock class are:

  • ReentrantLock() : Create a ReentrantLock instance
  • lock() : acquire a lock
  • unlock() : release the lock

Note: ReentrantLock() also has a construction method that can create a fair lock, but it is not recommended to use it because it can greatly reduce the running efficiency of the program; without parameters, it defaults to an unfair lock.

For example:

 //Only give the code to be modified, the rest of the code is the same as above
    class Bank {<!-- -->
        private int account = 100;
        //Need to declare this lock
        private Lock lock = new ReentrantLock();//No parameter is an unfair lock
        public int getAccount() {<!-- -->
            return account;
        }
        // synchronized is no longer needed here
        public void save(int money) {<!-- -->
            lock. lock();
            try{<!-- -->
                account + = money;
            }finally{<!-- -->
                lock. unlock();
            }
        }
    }

Note: About the choice of Lock object and synchronized keyword:

a. It is best not to use both, and use a mechanism provided by the java.util.concurrent package, which can help users deal with all lock-related codes.

b. If the synchronized keyword can meet the user’s needs, use synchronized because it can simplify the code

c. If you need more advanced functions, use the ReentrantLock class. At this time, pay attention to releasing the lock in time, otherwise a deadlock will occur, and the lock is usually released in the finally code

5. Use local variables to achieve thread synchronization

If you use ThreadLocal to manage variables, each thread that uses the variable gets a copy of the variable, and the copies are independent of each other, so that each thread can modify its copy of the variable at will without affecting other threads.

  • Common methods of the ThreadLocal class
  • ThreadLocal() : Create a thread local variable
  • get() : returns the value in the current thread’s copy of this thread-local variable
  • initialValue() : Returns the current thread’s “initial value” for this thread-local variable
  • set(T value) : Sets the value in the current thread’s copy of this thread-local variable to value

Based on the above example, the modified code is:

//Only change the Bank class, and the rest of the code is the same as above
public class Bank{<!-- -->
            //Use the ThreadLocal class to manage the shared variable account
   private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){<!-- -->
                @Override
                protected Integer initialValue(){<!-- -->
                    return 100;
                }
            };
            public void save(int money){<!-- -->
                account.set(account.get() + money);
            }
            public int getAccount(){<!-- -->
                return account. get();
            }
        }

Note: ThreadLocal and synchronization mechanism

a. Both ThreadLocal and synchronization mechanisms are designed to solve the access conflict problem of the same variable in multiple threads.

b. The former adopts the method of “exchanging space for time”, while the latter adopts the method of “exchanging time for space”