preface

Before we introduced a lot of content about multithreading, in multithreading there is a very important topic we need to overcome, that is thread safety. Thread safety problem refers to data contamination or other unexpected results caused by simultaneous operation between threads in multithreading.

Thread safety

1) Non-thread-safe examples

For example, when A and B transfer money to C at the same time, suppose C originally had A balance of 100 yuan, A transfers 100 yuan to C and is on the way of transfer. At this time, B also transfers 100 yuan to C. At this time, A successfully transfers money to C first, and the balance becomes 200 yuan. But B checked in advance that THE balance of C was 100 yuan, and it was also 200 yuan after the successful transfer. When both A and B transfer money to C, the balance is still $200 instead of $300 as expected, which is A typical thread-safe problem.

2) Non-thread-safe code examples

It doesn’t matter if you don’t understand the above content, let’s look at the non-thread-safe code in detail:

class ThreadSafeTest { static int number = 0; Public static void main(String[] args) throws InterruptedException {Thread thread1 = new Thread(() -› addNumber()); Thread2 = new Thread(() -› addNumber()); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("number: "+ number); } public static void addNumber() { for (int i = 0; I ‹ 10000; i++) { ++number; }}}Copy the code

The execution results of the above procedures are as follows:

Number: 12085

The result of each execution may vary slightly, but it is almost never equal to the (correct) total sum of 20000.

3) Thread-safe solutions

Thread-safe solutions have the following dimensions:

  • Data is not shared and is visible to single threads, such as ThreadLocal;
  • Use thread-safe classes such as StringBuffer and safe classes under JUC (java.util.Concurrent) (covered in a later article);
  • Use synchronization code or locks.

Thread synchronization and locking

1) synchronized

(1) introduce synchronized

Synchronized is a synchronization mechanism provided by Java. When a thread is working on a block of synchronized code (code modified by synchronized), other threads can only block and wait for the original thread to complete execution.

(2) use synchronized

Synchronized can modify blocks of code or methods, as shown in the following example:

Synchronized (this) {// do something} synchronized void method() {// do something}Copy the code

Use synchronized to refine the non-thread-safe code at the beginning of this article.

Method 1: Use synchronized to modify the code block as follows:

class ThreadSafeTest { static int number = 0; Public static void main(String[] args) throws InterruptedException {Thread sThread = new Thread(() -› {// Synchronize the code synchronized (ThreadSafeTest.class) { addNumber(); }}); Synchronized (ThreadSafeTest. Class) {addNumber(); }}); sThread.start(); sThread2.start(); sThread.join(); sThread2.join(); System.out.println("number: "+ number); } public static void addNumber() { for (int i = 0; I ‹ 10000; i++) { ++number; }}}Copy the code

The execution results of the above procedures are as follows:

Number: 20000

Method 2: Use synchronized modification method, the code is as follows:

class ThreadSafeTest { static int number = 0; Public static void main(String[] args) throws InterruptedException {Thread sThread = new Thread(() -› addNumber()); Thread sThread2 = new Thread(() -› addNumber()); sThread.start(); sThread2.start(); sThread.join(); sThread2.join(); System.out.println("number: "+ number); } public synchronized static void addNumber() { for (int i = 0; I ‹ 10000; i++) { ++number; }}}Copy the code

The execution results of the above procedures are as follows:

Number: 20000

③ Principle of synchronized

Synchronized is essentially thread-safe by entering and exiting Monitor objects. Take the following code for example:

public class SynchronizedTest { public static void main(String[] args) { synchronized (SynchronizedTest.class) { System.out.println("Java"); }}}Copy the code

When compiled using Javap, the resulting bytecode looks like this:

Compiled from "SynchronizedTest.java" public class com.interview.other.SynchronizedTest { public com.interview.other.SynchronizedTest(); Panel 1: Invokespecial #1 // Method Java /lang/Object." panel 1 ":()V 4: return public static void main(java.lang.String[]); Code: 0: ldc #2 // class com/interview/other/SynchronizedTest 2: dup 3: astore_1 4: monitorenter 5: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream; 8: ldc #4 // String Java 10: invokevirtual #5 // Method java/io/PrintStream.println:(Ljava/lang/String;) V 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return Exception table: from to target type 5 15 18 any 18 21 18 any }Copy the code

As you can see, the JVM (Java VIRTUAL machine) uses monitoRenter to synchronize with monitoreXit. Monitorenter locks and Monitorexit releases locks. Monitorenter and Monitorexit are implemented based on Monitor.

2) already

(1) already introduced

ReentrantLock is a lock implementation provided by Java 5 that performs essentially the same functions as synchronized. Reentry locks are acquired by calling the lock() method and released by calling unlock().

(2) already use

ReentrantLock basic use, code as follows:

Lock lock = new ReentrantLock(); lock.lock(); // lock // business code... lock.unlock(); / / unlockCopy the code

To complete the non-thread-safe code at the beginning of this article with ReentrantLock, refer to the following code:

public class LockTest { static int number = 0; Public static void main(String[] args) throws InterruptedException {// ReentrantLock Uses Lock Lock = new ReentrantLock(); Thread1 = new Thread(() -› {try {lock.lock(); addNumber(); } finally { lock.unlock(); }}); Thread2 = new Thread(() -› {try {lock.lock(); addNumber(); } finally { lock.unlock(); }}); thread1.start(); thread2.start(); thread1.join(); thread2.join(); System.out.println("number: "+ number); } public static void addNumber() { for (int i = 0; I ‹ 10000; i++) { ++number; }}}Copy the code

Attempt to acquire a lock

ReentrantLock can attempt to access the lock without blocking, using the tryLock() method as follows:

Lock reentrantLock = new ReentrantLock(); {try {reentrantLock.lock(); Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }).start(); Thread.sleep(1 * 1000); // thread.sleep (1 * 1000); System.out.println(reentrantLock.tryLock()); Thread.sleep(2 * 1000); System.out.println(reentrantLock.tryLock()); } catch (InterruptedException e) { e.printStackTrace(); } }).start();Copy the code

The execution result of the above code is as follows:

false true

Try to acquire the lock over a period of time

TryLock () has an extension tryLock(long timeout, TimeUnit Unit) to try to obtain the lock for a period of time.

Lock reentrantLock = new ReentrantLock(); {try {reentrantLock.lock(); System.out.println(LocalDateTime.now()); Thread.sleep(2 * 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { reentrantLock.unlock(); } }).start(); Thread.sleep(1 * 1000); // thread.sleep (1 * 1000); System.out.println(reentrantLock.tryLock(3, TimeUnit.SECONDS)); System.out.println(LocalDateTime.now()); } catch (InterruptedException e) { e.printStackTrace(); } }).start();Copy the code

The execution result of the above code is as follows:

2019-07-05 19:53:51 true 2019-07-05 19:53:53

The timeout parameter in the tryLock(long timeout, TimeUnit Unit) method refers to the maximum waiting time for the lock to be acquired.

3 Precautions for ReentrantLock

To use ReentrantLock, always remember to release the lock, otherwise the lock will be permanently held.

Related interview questions

1. What are the common methods of ReentrantLock?

A: The common methods of ReentrantLock are as follows:

  • Lock () : Used to obtain the lock
  • Unlock () : Releases the lock
  • TryLock () : Attempts to obtain the lock
  • GetHoldCount () : Queries the number of times the current thread executes the lock() method
  • GetQueueLength () : Returns the number of threads queuing to acquire this lock
  • IsFair () : Whether the lock is a fair lock

What are the advantages of ReentrantLock?

A: ReentrantLock has the ability to obtain locks in a non-blocking way, using the tryLock() method. ReentrantLock can interrupt the acquired lock using the lockInterruptibly() method. After the acquired lock is acquired, if the thread is interrupted, an exception is thrown and the acquired lock is released. ReentrantLock can obtain the lock within a specified time range, using the tryLock(long timeout,TimeUnit Unit) method.

3.ReentrantLock how to create a fair lock?

A: New ReentrantLock() creates an unfair lock by default. If you want to create a fair lock, use new ReentrantLock(true).

4. What are the differences between fair and unfair locks?

A: A fair lock means that the thread acquires the lock in the order in which it is locked. A non-fair lock means that the thread that locks () first does not necessarily acquire the lock first.

5. What is the difference between lock() and lockInterruptibly() in ReentrantLock?

A: The difference between lock() and lockInterruptibly() is that if the thread is interrupted while retrieving the thread, lock() ignores the exception and continues to wait for the thread. LockInterruptibly () throws InterruptedException. Execute the following code to use lock() and lockInterruptibly() respectively in the thread to view the results:

Lock interruptLock = new ReentrantLock(); interruptLock.lock(); Thread thread = new Thread(new Runnable() { @Override public void run() { try { interruptLock.lock(); //interruptLock.lockInterruptibly(); // java.lang.InterruptedException } catch (Exception e) { e.printStackTrace(); }}}); thread.start(); TimeUnit.SECONDS.sleep(1); thread.interrupt(); TimeUnit.SECONDS.sleep(3); System.out.println("Over"); System.exit(0);Copy the code

Execute the following code to find that the program does not error when using lock(). And use the lockInterruptibly () will throw an exception Java. Lang. InterruptedException, this means that: Lock () ignores the exception and continues to wait for the thread if the thread is interrupted while it is retrieving it, while lockInterruptibly() throws InterruptedException.

6. What’s the difference between synchronized and ReentrantLock?

A: Synchronized and ReentrantLock are thread-safe, and the differences are as follows:

  • ReentrantLock is flexible to use, but must have a lock release action;
  • ReentrantLock must manually acquire and release the lock, while synchronized does not need to manually release and unlock the lock.
  • ReentrantLock applies only to code block locks, while synchronized can be used to modify methods, code blocks, and so on;
  • ReentrantLock performs slightly better than synchronized.

7.ReentrantLock tryLock(3, timeunit.seconds) means to wait 3 SECONDS before retrieving the lock. Why is that?

A: No, tryLock(3, timeunit.seconds) means that the maximum wait time for obtaining the lock is 3 SECONDS, and the lock will be tried all the time, instead of waiting 3 SECONDS before obtaining the lock.

8. How does synchronized achieve lock upgrade?

A: There is a threadid field in the lock object’s object header. On first access, threadid is null. The JVM (Java virtual machine) lets it hold the biased lock and sets threadid to its threadid. Again into the will determine whether threadid thread id is consistent, especially if the same can be used directly, if not consistent, the upgrade biased locking for lightweight lock, get locked by a certain number of times the spin cycle, not blocked, after the execution of a certain number of times will be upgraded to a heavyweight locks, blocks, the entire process is the process of lock escalation.

conclusion

This paper introduces two methods of thread synchronization: synchronized and ReentrantLock. Among them, ReentrantLock is more flexible and efficient, but ReentrantLock can only modify code blocks. Using ReentrantLock requires the developer to manually release the lock, which will remain in use if forgotten. Synchronized uses a wider range of scenarios, can modify ordinary methods, static methods and code blocks, etc., xiaobian here also summarizes a multithreaded thinking brain map, in order to facilitate partners to better organize technical points, share to everyone!