Management of multithreaded learning note sharing model

3.1. Analysis of thread shared variables

Two threads do self increment and self decrement for static variables with an initial value of 0 for 5000 times respectively. Is the result 0?

    static int counter = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter--;
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }

problem analysis

The above results may be positive, negative or zero. Why? Because the self increment and self decrement of static variables in Java are not atomic operations, they must be analyzed from bytecode to fully understand them

For example, for i + + (i is a static variable), the following JVM bytecode instructions will actually be generated:

getstatic i // Gets the value of the static variable i
iconst_1 // Prepare constant 1
iadd // Self increasing
putstatic i // Store the modified value into the static variable i

The corresponding i-- is similar:

getstatic i // Gets the value of the static variable i
iconst_1 // Prepare constant 1
isub // Self subtraction
putstatic i // Store the modified value into the static variable i

The memory model of Java is as follows. To complete the self increase and self decrease of static variables, data exchange needs to be carried out in main memory and working memory:

If it is a single thread, the above 8 lines of code are executed sequentially (without interleaving), there is no problem:

Negative numbers in multithreading:

Positive numbers in multithreading:

Critical Section

  • There is no problem for a program to run multiple threads

  • The problem is that multiple threads access shared resources

    • In fact, there is no problem for multiple threads to read shared resources

    • When multiple threads read and write to shared resources, instruction interleaving occurs, which will cause problems

  • If there are multithreaded read-write operations on shared resources in a code block, this code block is called a critical area

For example, the critical area in the following code

static int counter = 0;
static void increment()
// Critical zone
{
 counter++;
}
static void decrement()
// Critical zone
{
 counter--;
}

Race Condition

When multiple threads execute in the critical area, the results cannot be predicted due to different execution sequences of code, which is called race condition

3.2.synchronized solutions

Mutual exclusion of applications

In order to avoid the competitive conditions in the critical zone, there are many means to achieve the goal.

  • Blocking solution: synchronized, Lock

  • Non blocking solution: atomic variable this lesson uses blocking solution: synchronized to solve the above problems, Commonly known as the [object lock], it uses a mutually exclusive method so that at most one thread can hold the [object lock] at the same time, and other threads will block when they want to obtain the [object lock]. This can ensure that the thread with the lock can safely execute the code in the critical area without worrying about thread context switching

be careful

Although the synchronized keyword can be used to complete mutual exclusion and synchronization in java, they are different:

  • Mutual exclusion is to ensure that the race condition of the critical area occurs. Only one thread can execute the code of the critical area at the same time

  • Synchronization is due to the different execution order and needs of threads

One thread waits for other threads to run to a certain point

synchronized syntax

 synchronized(object) // Thread 1, thread 2(blocked)
 {
     Critical zone
 }

Use synchronized to solve the above code data sharing security problem:

@Slf4j
public class Test1 {
    static int counter = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                // Don't misunderstand it as that the object can be executed all the time
        		synchronized (Test1.class){
                 counter++;
             }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (Test1.class){
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",counter);
    }
}

reflection

synchronized actually uses object locks to ensure the atomicity of the code in the critical area. The code in the critical area is inseparable externally and will not be interrupted by thread switching. To deepen your understanding, please think about the following questions

  • If synchronized(obj) is placed outside the for loop, how to understand it? If the for loop is not completed, the object lock will not be released and other threads cannot access it
  • Atomicity how would t1 synchronized(obj1) and t2 synchronized(obj2) work? The objects of the lock are inconsistent and atomicity cannot be guaranteed
  • What happens if T1 is synchronized (obj) but t2 is not? How to understand? t2 will not acquire the object lock, will not block, and will execute as usual

Improve with object-oriented ideas:

   public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
           room.increment();
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
               room.decrement();
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",room.get());
    }
-------------------------------------------------------------------------------------------------------------------
public class Room {
    int value = 0;
    public void increment() {
        synchronized (this) {
            value++;
        }
    }
    public void decrement() {
        synchronized (this) {
            value--;
        }
    }
    public int get() {
        synchronized (this) {
            return value;
        }
    }

}

3.3. synchronized on method

On member method: lock this

    class Test{
        public synchronized void test() {

        }
    }
    //Equivalent to
    class Test{
        public void test() {
            synchronized(this) {

            }
        }
    }

On static methods: lock the Class object of the current Class

    class Test{
        public synchronized static void test() {
        }
    }
    //Equivalent to
    class Test{
        public static void test() {
            synchronized(Test.class) {

            }
        }
    }

The method without synchronized does not need to obtain the object lock. It is executed asynchronously and cannot guarantee atomicity

3.4. Thread eight lock

In fact, it is to investigate which object synchronized locks and whether it is the same object locked

In the following cases, it should be considered which thread preempts the cpu execution right first

Case 1: output: 12 or 21

class Number{
 public synchronized void a() {
 	log.debug("1");
 }
 public synchronized void b() {
 	log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}

Case 2: 12 after 1 second or 1 after 1 second

class Number{
 public synchronized void a() {
 	sleep(1);
	 log.debug("1");
 }
 public synchronized void b() {
	 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}
// this is all locked. You need to wait for synchronization. It depends on who gets the cpu execution right first

Case 3: output: 12 or 23 after 1 second or 32 after 1 second

class Number{
 public synchronized void a() {
	 sleep(1);
	 log.debug("1");
 }
 public synchronized void b() {
 	log.debug("2");
 }
 public void c() {
 	log.debug("3");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
 new Thread(()->{ n1.c(); }).start();
}
// n1.a(); And N1 b(); this object is locked, N1 c(); There is no need to wait for synchronization without locking

Case 4: output: 1 second later

class Number{
 public synchronized void a() {
	 sleep(1);
	 log.debug("1");
 }
 public synchronized void b() {
	 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 Number n2 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n2.b(); }).start();
}
// The locked object is not the same object, and mutual exclusion will not occur

Case 5: output: 2 1 second later

class Number{
 public static synchronized void a() {
 	sleep(1);
 	log.debug("1");
 }
 public synchronized void b() {
	 log.debug("2");
 }
    }
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}
// The lock is not the same object, and mutual exclusion will not occur. The this of a lock and the number of a lock class

Case 6: output: 12 after 1 second, or 1 after 2 1 second

class Number{
 public static synchronized void a() {
 	sleep(1);
	 log.debug("1");
 }
 public static synchronized void b() {
 	log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n1.b(); }).start();
}
// All locks are number Class object

Case 7: 2 1 second later

class Number{
 public static synchronized void a() {
 	sleep(1);
 	log.debug("1");
 }
 public synchronized void b() {
 	log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 Number n2 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n2.b(); }).start();
}
// The lock objects are inconsistent. One locks this and the other locks number class

Case 8: 12 after 1 second, or 1 after 2 1 second

class Number{
 public static synchronized void a() {
 	sleep(1);
 	log.debug("1");
 }
 public static synchronized void b() {
	 log.debug("2");
 }
}
public static void main(String[] args) {
 Number n1 = new Number();
 Number n2 = new Number();
 new Thread(()->{ n1.a(); }).start();
 new Thread(()->{ n2.b(); }).start();
}
// All locked are number Class, it depends on who gets the cpu execution right first

3.5. Thread safety analysis of variable

Are member and static variables thread safe?

  • If they are not shared, they are thread safe

  • If they are shared, they can be divided into two cases according to whether their state can be changed

    • Thread safe if there are only read operations
    • If there are read and write operations, this code is a critical area and thread safety needs to be considered

Are local variables thread safe?

Local variables are thread safe

However, objects referenced by local variables may not be

  • If the object is not accessed as an escape method, it is thread safe

  • If the object escapes from the scope of the method, thread safety needs to be considered

Thread safety analysis of local variables

public static void test1() {
	 int i = 10;
 	i++;
}

When each thread calls the test1() method, multiple copies of the local variable i will be created in the stack frame memory of each thread, so there is no sharing

VM Stack

  • Stack: the memory space required by threads to run. Each thread is unique
  • Stack frame: the memory required for each method to run (including parameters, local variables, return address, etc.)
  • Each thread has only one active stack frame (the stack frame at the top of the stack), corresponding to the code being executed

Corresponding bytecode file:

public static void test1();
 descriptor: ()V
 flags: ACC_PUBLIC, ACC_STATIC
 Code:
 stack=1, locals=1, args_size=0
 0: bipush 10
 2: istore_0
 3: iinc 0, 1
 6: return
 LineNumberTable:
 line 10: 0
 line 11: 3
 line 12: 6
 LocalVariableTable:
 Start Length Slot Name Signature
 3 4 0 i I

Examples of member variables

class ThreadUnsafe {
 ArrayList<String> list = new ArrayList<>();
 public void method1(int loopNumber) {
 	for (int i = 0; i < loopNumber; i++) {
 		// {critical zone, race conditions will occur
 		method2();
 		method3();
     // }Critical zone
	 }
 }
 private void method2() {
	 list.add("1");
 }
 private void method3() {
	 list.remove(0);
 }
}

implement

static final int THREAD_NUMBER = 2;
static final int LOOP_NUMBER = 200;
public static void main(String[] args) {
	 ThreadUnsafe test = new ThreadUnsafe();
 	for (int i = 0; i < THREAD_NUMBER; i++) {
 		new Thread(() -> {
	 		test.method1(LOOP_NUMBER);
 		}, "Thread" + i).start();
 	}
}

In one case, if thread 2 has not been add ed, thread 1 remove will report an error:

Exception in thread "Thread0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at com.compass.test.ThreadUnsafe.method3(ThreadUnsafe.java:30)
	at com.compass.test.ThreadUnsafe.method1(ThreadUnsafe.java:22)
	at com.compass.test.ThreadUnsafe.lambda$main$0(ThreadUnsafe.java:41)
	at java.lang.Thread.run(Thread.java:748)

analysis:

  • method2 in any thread refers to the list member variable in the same object
  • method3 is the same as method2 analysis
  • list is an unsafe collection. The internal add and remove methods do not implement thread safety mechanism. If some threads do not have time to add and other threads remove, an IndexOutOfBoundsException exception will appear

Change the list to a local variable

    class ThreadSafe {
        public final void method1(int loopNumber) {
            ArrayList<String> list = new ArrayList<>();
            for (int i = 0; i < loopNumber; i++) {
                method2(list);
                method3(list);
            }
        }
        private void method2(ArrayList<String> list) {
            list.add("1");
        }
        private void method3(ArrayList<String> list) {
            list.remove(0);
        }
    }

Then there will be no above problems, because each thread has its own stack, and the list set is created by each thread's own stack frame. The list set is no longer a shared variable, so there is no multi-threaded safety problem.

analysis:

  • list is a local variable. When each thread calls, it will create different instances without sharing
  • The parameters of method2 are passed from method1 and reference the same object as method1
  • The parameter analysis of method3 is the same as that of method2

Thinking brought by the method access modifier, if the methods of method2 and method3 are modified to public, will it proxy thread safety?

Case 1: there are other threads calling method2 and method3

Case 2: on the basis of case 1, add a subclass for ThreadSafe class, which overrides method2 or method3 methods

class ThreadSafe {
    public final void method1(int loopNumber) {
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
            method2(list);
            method3(list);
        }
    }
    public void method2(ArrayList<String> list) {
        list.add("1");
    }
    public void method3(ArrayList<String> list) {
        list.remove(0);
    }
}

class ThreadSafeSubClass extends ThreadSafe{
    // Create a new thread to perform the remove operation,
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

We can see the significance of [security] provided by private or final, so as to avoid the subclass rewriting some methods of the parent class, which may lead to multi-threaded security problems

Common thread safety classes

  • String
  • Integer
  • StringBuffer
  • Random
  • Vector
  • Hashtable
  • java. util. Classes under concurrent package

Here, they are thread safe, which means that when multiple threads call a method of their same instance, they are thread safe. It can also be understood as

Hashtable table = new Hashtable();

new Thread(()->{
 	table.put("key", "value1");
}).start();

new Thread(()->{
 	table.put("key", "value2");
}).start();
  • Each of their methods is atomic
  • However, note that the combination of their multiple methods is not atomic, as shown in the following analysis

Combination of thread safe class methods

Analyze whether the following code is thread safe?

Answer: it is not safe. It will be affected by thread context switching. It is possible that both threads enter the get method at the same time, and the obtained values are null. Subsequent thread 1 sets a value for put, and thread 2 puts another value, overwriting the value of thread 1 put.

public void setVale(){
    Hashtable table = new Hashtable();
	// Thread 1, thread 2
    if( table.get("key") == null) {
        table.put("key", value);
    }
}

Immutable class thread safety

String and Integer are immutable classes. Because their internal state cannot be changed, their methods are thread safe

Some students may have questions. String has methods such as replace and substring that can change the value. How do these methods ensure thread safety?

The reason why the String class is safe: it can only be read and cannot be modified, so it is thread safe. When executing substring (), there is no property of the String object, but a new String is created for operation.

    public String substring(int beginIndex) {
        if (beginIndex < 0) {
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
            throw new StringIndexOutOfBoundsException(subLen);
        }
        // A new string was created
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
    }
    public class Immutable{
        private int value = 0;
        public Immutable(int value){
            this.value = value;
        }
        public int getValue(){
            return this.value;
        }
    }

What if you want to add a new method?

    public class Immutable{
        private int value = 0;
        public Immutable(int value){
            this.value = value;
        }
        public int getValue(){
            return this.value;
        }

        public Immutable add(int v){
            return new Immutable(this.value + v);
        }
    }

Case analysis

Example 1: a single instance of Servlet will be shared by multiple threads of Tomcat, so its member variables may have thread safety problems.

    public class MyServlet extends HttpServlet {
        // Is it safe? It is not thread safe because HashMap is thread unsafe
        Map<String,Object> map = new HashMap<>();
        // Is it safe? Yes, it's an immutable class
        String S1 = "...";
        // Is it safe? Yes, it's an immutable class
        final String S2 = "...";
        // Is it safe? Not thread safe
        Date D1 = new Date();
        // Is it safe? It is not thread safe. Although the D2 reference value cannot be modified, its properties can be modified
        final Date D2 = new Date();

        public void doGet(HttpServletRequest request, HttpServletResponse response) {
            // Use the above variables
        }
    }

Example 2:

public class MyServlet extends HttpServlet {
    // Is it safe? It is not thread safe. It belongs to shared resources and involves data modification
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // Record the number of calls
    private int count = 0;

    public void update() {
        // ...
        count++;
    }
}

Example 3:

@Aspect
@Component
public class MyAspect {
    // Is it safe? It is not thread safe. spring is single instance by default. It is designed to modify the data of shared resources
    private long start = 0L;

    @Before("execution(* *(..))")
    public void before() {
        start = System.nanoTime();
    }

    @After("execution(* *(..))")
    public void after() {
        long end = System.nanoTime();
        System.out.println("cost time:" + (end-start));
    }
}
// Solution: make surround notifications and local variables

// If configured as multi instance? It's also not allowed. Entering the pre notification is an object, and entering the post notification is another object, so the statistical time is incorrect

Example 4:

public class MyServlet extends HttpServlet {
    // Whether it is thread safe, because it does not involve the modification of shared data
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // Whether it is thread safe, because it does not involve the modification of shared data
    private UserDao userDao = new UserDaoImpl();

    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    public void update() {
        String sql = "update user set password = ? where username = ?";
        // Is it thread safe? Connection is a local variable. It is created every time and is unique to each thread
        try (Connection conn = DriverManager.getConnection("","","")){
            // ...
        } catch (Exception e) {
            // ...
        }
    }

}

Example 5:

public class MyServlet extends HttpServlet {
    // Is it thread safe and private? There is no place to modify the shared data
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // Is it thread safe and private? There is no place to modify the shared data
    private UserDao userDao = new UserDaoImpl();

    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // Is the thread safe? If the thread is unsafe, it is made into a member variable and shared by multiple threads. It is possible that other threads have just been created for a long time and are closed ()
    private Connection conn = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}

Example 6:

public class MyServlet extends HttpServlet {
    // Is it safe
    private UserService userService = new UserServiceImpl();

    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    public void update() {
        // Each time a thread comes, a new userDao object is created. There is no thread safety problem [not recommended]
        UserDao userDao = new UserDaoImpl();
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // There is no thread safety issue
    private Connection = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}

Example 7:

public abstract class Test {

    public void bar() {
        // Whether the reference of thread unsafe variables is leaked
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        foo(sdf);
    }

    public abstract foo(SimpleDateFormat sdf);


    public static void main(String[] args) {
        new Test().bar();
    }
}

foo's behavior is uncertain and may lead to insecurity, which is called extraterrestrial method

    public void foo(SimpleDateFormat sdf) {
        String dateStr = "1999-10-11 00:00:00";
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                try {
                    sdf.parse(dateStr);
                } catch (ParseException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }

Ticket selling cases:

@Slf4j
public class ExerciseSell {
    public static void main(String[] args) {
        TicketWindow ticketWindow = new TicketWindow(2000);

        List<Thread> list = new ArrayList<>();
        // Used to store how many tickets you bought
        List<Integer> sellCount = new Vector<>();
        for (int i = 0; i < 2000; i++) {
            Thread t = new Thread(() -> {
                // Analyze the race conditions here
                int count = ticketWindow.sell(randomAmount());
                sellCount.add(count);
            });
            list.add(t);
            t.start();
        }

        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // Sum of tickets bought
        log.debug("selled count:{}",sellCount.stream().mapToInt(c -> c).sum());
        // Remaining votes
        log.debug("remainder count:{}", ticketWindow.getCount());
    }
    // Random is thread safe
    static Random random = new Random();
    // Random 1 ~ 5
    public static int randomAmount() {
        return random.nextInt(5) + 1;
    }
}
class TicketWindow {
    private int count;

    public TicketWindow(int count) {
        this.count = count;
    }

    // If multiple threads modify the count, there will be a security problem. Add synchronized to solve it
    public synchronized int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }

    public int getCount() {
        return count;
    }
}

Test script:

for /L %n in (1,1,10) do java -cp ".;C:\Users\14823\.m2\repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;C:\Users\14823\.m2\repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;C:\Users\14823\.m2\repository\org\slf4j\slf4j-api\1.7.25\slf4j-api-1.7.25.jar"  com.compass.test.ExerciseSell

Transfer exercise

Test the following code for thread safety problems and try to correct them

@Slf4j
public class ExerciseTransfer {
    public static void main(String[] args) throws InterruptedException {
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();// View the total amount after 2000 transfers
        log.debug("total:{}",(a.getMoney() + b.getMoney()));
    }
    // Random is thread safe
    static Random random = new Random();
    // Random 1 ~ 100
    public static int randomAmount() {
        return random.nextInt(100) +1;
    }
}
class Account {
    private int money;
    public Account(int money) {
        this.money = money;
    }
    public int getMoney() {
        return money;
    }
    public void setMoney(int money) {
        this.money = money;
    }

    /**
     * synchronized cannot be added directly to the method, because this locks the this object, not the object lock shared by the two objects
     * What we need to lock here are the two shared variables this Money and target Money can only lock account class
     *
     */
    public  void transfer(Account target, int amount) {
      synchronized (Account.class){
          if (this.money > amount) {
              this.setMoney(this.getMoney() - amount);
              target.setMoney(target.getMoney() + amount);
          }
      }
    }
}

3.6.Monitor concept

The Java object header takes a 32-bit virtual machine as an example:


3.7.monitor principle

Monitor is translated as monitor or tube pass

Each Java object can be associated with a Monitor object. If you use synchronized to lock the object (heavyweight), the pointer to the Monitor object will be set in the Mark Word of the object header. The Monitor structure is as follows

  • At the beginning, the Owner in Monitor is null
  • When Thread-2 executes synchronized(obj), the Owner of the Monitor will be set to Thread-2. There can only be one in the Monitor
    Owner s
  • During Thread-2 locking, if Thread-3, Thread-4 and Thread-5 also execute synchronized(obj), it will enter
    EntryList BLOCKED
  • Thread-2 executes the contents of the synchronization code block, and then wakes up the waiting threads in the EntryList to compete for the lock. The competition is unfair
  • In the figure, Thread-0 and Thread-1 in the WaitSet are threads that have obtained locks before, but the conditions are not met to enter the waitting state, which will be described later
    Wait notify will analyze

Note: synchronized must enter the monitor of the same object to have the above effect. Objects that are not synchronized will not be associated with the monitor and do not comply with the above rules

synchronized principle

static final Object lock = new Object();
static int counter = 0;
public static void main(String[] args) {
	 synchronized (lock) {
 	counter++;
 }
}

The corresponding bytecode is:

public static void main(java.lang.String[]);
 descriptor: ([Ljava/lang/String;)V
 flags: ACC_PUBLIC, ACC_STATIC
Code:
 stack=2, locals=3, args_size=1
 0: getstatic #2 / / < - lock reference (started with synchronized)
 3: dup
 4: astore_1 // lock reference - > slot 1
 5: monitorenter // Set the lock object MarkWord as the Monitor pointer
 6: getstatic #3 // <- i
 9: iconst_1 // Preparation constant 1
 10: iadd // +1
 11: putstatic #3 // -> i
 14: aload_1 // < - lock reference
 15: monitorexit // Reset the lock object MarkWord and wake up the EntryList
 16: goto 24
 19: astore_2 // e -> slot 2
 20: aload_1 // < - lock reference
 21: monitorexit // Reset the lock object MarkWord and wake up the EntryList
 22: aload_2 // <- slot 2 (e)
 23: athrow // throw e
 24: return
 Exception table:
 from to target type
 6 16 19 any
 19 22 19 any
 LineNumberTable:
 line 8: 0
 line 9: 6
 line 10: 14
 line 11: 24
 LocalVariableTable:
 Start Length Slot Name Signature
 0 25 0 args [Ljava/lang/String;
 StackMapTable: number_of_entries = 2
 frame_type = 255 /* full_frame */
 offset_delta = 19
 locals = [ class "[Ljava/lang/String;", class java/lang/Object ]
 stack = [ class java/lang/Throwable ]
 frame_type = 250 /* chop */
 offset_delta = 4

Note that method level synchronized is not reflected in bytecode instructions

synchronized lock release timing:

  • The synchronization method and code block of the current thread are released at the end of execution
  • When the current thread encounters a break or return in the synchronized method or synchronized code block, it is released when the code block or method is finally released.
  • It is released when an unhandled error or exception causes the end of an exception
  • The program executes the wait method of the synchronization object. The current thread pauses and releases the lock

The lock will not be released if:

  • The program calls thread sleep() Thread. Yield () these methods pause the execution of the thread and do not release.
  • When a thread executes a synchronous code block, other threads call the suspend method to suspend the thread, and the thread will not release the lock, so we should avoid using suspend and resume to control the thread

3.8. Advanced principle of synchronized

3.8. 1 lightweight lock

  • Usage scenario of lightweight lock: if an object has multiple threads to lock, but the locking time is staggered (that is, there is no competition), lightweight lock can be used to optimize.
  • Lightweight locks are transparent to the user, that is, the syntax is still synchronized
  • Suppose there are two methods to synchronize blocks and lock with the same object
  static final Object obj = new Object();
    public static void method1() {
        synchronized( obj ) {
            // Synchronization block A
            method2();
        }
    }
    public static void method2() {
        synchronized( obj ) {
            // Synchronization block B
        }
    }

Create a Lock Record object. The stack frame of each thread will contain a Lock Record structure, which can store the Mark Word of the locked object

Let the Object reference in the lock record point to the lock Object, try to replace the Mark Word of the Object with cas, and store the value of Mark Word in the lock record

If the cas replacement is successful, the lock record address and status 00 are stored in the object header, indicating that the thread locks the object. The figure is as follows

  • If cas fails, there are two situations

    • If other threads already hold the lightweight lock of the Object, it indicates that there is competition and enters the lock expansion process

    • If the synchronized lock reentry is performed by yourself, add another Lock Record as the reentry count

When exiting the synchronized code block (when unlocking), if there is a lock record with a value of null, it indicates that there is reentry. At this time, reset the lock record, indicating that the reentry count is reduced by one

When exiting the synchronized code block (when unlocking), the value of the lock record is not null. In this case, cas is used to restore the value of Mark Word to the object header

  • If successful, the unlocking is successful

  • Failure indicates that the lightweight lock has undergone lock expansion or has been upgraded to a heavyweight lock. Enter the heavyweight lock unlocking process

3.8. 2 lock expansion

If the CAS operation fails when trying to add a lightweight lock, then another thread adds a lightweight lock (with competition) to this object. At this time, lock expansion is required to change the lightweight lock into a heavyweight lock.

  static Object obj = new Object();

    public static void method1() {
        synchronized (obj) {
            // Synchronization block
        }
    }

When Thread-1 performs lightweight locking, Thread-0 has already applied lightweight locking to the object

At this time, Thread-1 fails to add a lightweight lock and enters the lock expansion process

  • That is, apply for the Monitor lock for the Object object and let the Object point to the heavyweight lock address

  • Then enter the EntryList BLOCKED of Monitor

  • When Thread-0 exits the synchronization block unlocking, cas is used to restore the value of Mark Word to the object header, which fails. At this time, the heavyweight unlocking process will enter, that is, find the Monitor object according to the Monitor address, set the Owner to null, and wake up the BLOCKED thread in the EntryList

3.8. 3 spin optimization

When the heavyweight lock competes, spin can also be used for optimization. If the current thread spins successfully (that is, the lock holding thread has exited the synchronization block and released the lock), the current thread can avoid blocking. If the spin retry succeeds

Successful spin retry:

Spin retry failed:

  • Spin will occupy CPU time. Single core CPU spin is a waste, and multi-core CPU spin can give play to its advantages.
  • After Java 6, the spin lock is adaptive. For example, if the object has just succeeded in a spin operation, it is considered that the possibility of successful spin this time will increase
    High, spin a few more times; On the contrary, less spin or even no spin. In short, it is more intelligent.
  • After Java 7, you can't control whether to turn on the spin function

3.8. 4 deflection lock

The lightweight lock still needs to perform CAS operation every time it re enters when there is no competition (just its own thread).

Java 6 introduces bias lock for further optimization: only when CAS is used for the first time to set the thread ID to the Mark Word header of the object, and then it is found that the thread ID is its own, it means that there is no competition and there is no need to re CAS. In the future, as long as there is no competition, the object belongs to the thread

    static final Object obj = new Object();
    public static void m1() {
        synchronized( obj ) {
            // Synchronization block A
            m2();
        }
    }
    public static void m2() {
        synchronized( obj ) {
            // Synchronization block B
            m3();
        }
    }
    public static void m3() {
        synchronized( obj ) {
            // Synchronization block C

        }
    }

Recall the object header format

When an object is created:

  • If the bias lock is enabled (enabled by default), after the object is created, the markword value is 0x05, that is, the last three bits are 101, and its
    thread, epoch and age are all 0
  • Bias lock is delayed by default and will not take effect immediately when the program is started. If you want to avoid delay, you can add VM parameters-
    20: Biasedlockingstartupdelay = 0 to disable delay
  • If the bias lock is not enabled, after the object is created, the markword value is 0x01, that is, the last three bits are 001. At this time, its hashcode
    Ages are all 0, and the value will be assigned only when hashcode is used for the first time

Test bias lock, and bias lock delay

@Slf4j
public class Demo {
    // Add virtual machine parameter - XX:BiasedLockingStartupDelay=0
    public static void main(String[] args) throws IOException {
        Dog d = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(d);

        new Thread(() -> {
            log.debug("synchronized -------front--------");
            System.out.println(classLayout.toPrintable());
            synchronized (d) {
                log.debug("synchronized -------in--------");
                System.out.println(classLayout.toPrintable());
            }
            log.debug("synchronized -------after--------");
            System.out.println(classLayout.toPrintable());
        }, "t1").start();
}
}
class Dog{


}

Output:

03:00:46 [DEBUG] [t1] c.c.test.Demo - synchronized -------front--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:00:46 [DEBUG] [t1] c.c.test.Demo - synchronized -------in--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001c049805 (biased: 0x0000000000070126; epoch: 0; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:00:46 [DEBUG] [t1] c.c.test.Demo - synchronized -------after--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001c049805 (biased: 0x0000000000070126; epoch: 0; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Note that after the object in the bias lock is unlocked, the thread id is still stored in the object header

Test disable bias lock: when the above test code runs, add the VM parameter - XX:-UseBiasedLocking to disable bias lock

03:08:33 [DEBUG] [t1] c.c.test.Demo - synchronized -------front--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:08:33 [DEBUG] [t1] c.c.test.Demo - synchronized -------in--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE              // Lightweight lock
  0   8        (object header: mark)     0x000000001c53f628 (thin lock: 0x000000001c53f628)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:08:33 [DEBUG] [t1] c.c.test.Demo - synchronized -------after--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
    // Lock priority: bias lock - > lightweight lock - > heavyweight lock

Test hashCode: Rewriting the hashCode method can effectively avoid calling the hashCode method to make the object lose the bias lock

@Slf4j
public class Demo {
    // Add virtual machine parameter - XX:BiasedLockingStartupDelay=0
    public static void main(String[] args) throws IOException {
        Dog d = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(d);
        // Disable the bias lock for this object
        d.hashCode();
        new Thread(() -> {
            log.debug("synchronized -------front--------");
            System.out.println(classLayout.toPrintable());
            synchronized (d) {
                log.debug("synchronized -------in--------");
                System.out.println(classLayout.toPrintable());
            }
            log.debug("synchronized -------after--------");
            System.out.println(classLayout.toPrintable());
        }, "t1").start();
}
}
class Dog{


}

Output:

03:15:05 [DEBUG] [t1] c.c.test.Demo - synchronized -------front--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000006276ae3401 (hash: 0x6276ae34; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:15:05 [DEBUG] [t1] c.c.test.Demo - synchronized -------in--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE    // If the deflection lock fails, use the lightweight lock
  0   8        (object header: mark)     0x000000001bf0f098 (thin lock: 0x000000001bf0f098)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:15:05 [DEBUG] [t1] c.c.test.Demo - synchronized -------after--------
com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000006276ae3401 (hash: 0x6276ae34; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

The normal state object does not have a hashCode at the beginning and is generated only after the first call. When an object is biased, but once the hashCode method is called, the object cannot use the bias 1 lock and is upgraded to a lightweight lock.

Undo - other threads use the object: when other threads use the biased lock object, the biased lock will be upgraded to lightweight

   private static void test2() throws InterruptedException {
        Dog d = new Dog();
        Thread t1 = new Thread(() -> {
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintable());
            }
            synchronized (Demo.class) {
                Demo.class.notify();
            }

        }, "t1");

        t1.start();
        Thread t2 = new Thread(() -> {
            // Ensure that thread t2 executes after thread t1 executes
            synchronized (Demo.class) {
                try {
                    Demo.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug(ClassLayout.parseInstance(d).toPrintable());
            synchronized (d) {
                log.debug(ClassLayout.parseInstance(d).toPrintable());
            }
            log.debug(ClassLayout.parseInstance(d).toPrintable());
        }, "t2");
        t2.start();
    }
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001be10805 (biased: 0x000000000006f842; epoch: 0; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:38:19 [DEBUG] [t2] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001be10805 (biased: 0x000000000006f842; epoch: 0; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:38:19 [DEBUG] [t2] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001c47f600 (thin lock: 0x000000001c47f600)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:38:19 [DEBUG] [t2] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Undo - call wait/notify: upgrade biased locks or lightweight locks to heavyweight locks, because heavyweight locks only have wait/notify

@Slf4j
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        Dog d = new Dog();

        ClassLayout layout = ClassLayout.parseInstance(d);

        Thread t1 = new Thread(() -> {
            // 1
            log.debug(layout.toPrintable());
            synchronized (d) {
                // 2
                log.debug(layout.toPrintable());
                try {
                    d.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //3
                log.debug(layout.toPrintable());
            }
        }, "t1");
        t1.start();

        new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (d) {
                log.debug("notify");
                d.notify();
            }
        }, "t2").start();
    }
}
class Dog{

}
03:45:23 [DEBUG] [t1] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:45:23 [DEBUG] [t1] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001b462005 (biased: 0x000000000006d188; epoch: 0; age: 0)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

03:45:24 [DEBUG] [t2] c.c.test.Demo - notify
03:45:24 [DEBUG] [t1] c.c.test.Demo - com.compass.test.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000017b0f4aa (fat lock: 0x0000000017b0f4aa)
  8   4        (object header: class)    0x20018c63
 12   4        (object alignment gap)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

Batch re bias

  • If the object is accessed by multiple threads, but there is no competition, the object biased to thread T1 still has the opportunity to re bias to T2, and the re bias will reset the Thread ID of the object

  • When the unbiased lock threshold is revoked more than 20 times, the jvm will think, am I biased wrong, so it will re bias to the locking thread when locking these objects

@Slf4j
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    private static void test3() throws InterruptedException {
        List<Dog> list = new Vector<>();

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 30; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" +ClassLayout.parseInstance(d).toPrintable());
                }
            }
            synchronized (list) {
                list.notify();
            }
        }, "t1");
        t1.start();

        Thread t2 = new Thread(() -> {
            synchronized (list) {
                try {
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.debug("==========================================================================================");
            for (int i = 0; i < 30; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" +ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" +ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" +ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t2");
        t2.start();
    }
}
class Dog{

}
[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - ===============>
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101

Batch undo

When the unbiased lock threshold is revoked more than 40 times, the jvm will feel that it is really biased wrong and should not be biased at all. Therefore, all objects of the whole class will become non biased, and the new objects will also be non biased

@Slf4j
public class Demo {
    public static void main(String[] args) throws InterruptedException {
        test4();
    }
    static Thread t1,t2,t3;
    private static void test4() throws InterruptedException {
        Vector<Dog> list = new Vector<>();
        int loopNumber = 39;
        t1 = new Thread(() -> {
            for (int i = 0; i < loopNumber; i++) {
                Dog d = new Dog();
                list.add(d);
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
            }
            LockSupport.unpark(t2);
        }, "t1");
        t1.start();
        t2 = new Thread(() -> {
            LockSupport.park();
            log.debug("==========================================================================================");
            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
            LockSupport.unpark(t3);
        }, "t2");
        t2.start();
        t3 = new Thread(() -> {
            LockSupport.park();
            log.debug("==========================================================================================");

            for (int i = 0; i < loopNumber; i++) {
                Dog d = list.get(i);
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                synchronized (d) {
                    log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
                }
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintable());
            }
        }, "t3");
        t3.start();
        t3.join();

    }

}
class Dog{

}

3.8. 5 lock elimination

@Slf4j
@Fork(1)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations=3)
@Measurement(iterations=5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class MyBenchmark {
    static int x = 0;
    @Benchmark
    public void a() throws Exception {
        x++;
    }
    @Benchmark
    public void b() throws Exception {
        // This object is not a shared variable, and locking is meaningless. The compiler optimizes it during compilation to eliminate the lock
        Object o = new Object();
        synchronized (o) {
            x++; }
    }
}

java -jar benchmarks.jar

Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.542 0.056 ns/op
c.i.MyBenchmark.b avgt 5 1.518 0.091 ns/op

Close lock elimination jvm instruction: Java - XX: - eliminatelocks - jar benchmarks jar

Benchmark Mode Samples Score Score error Units
c.i.MyBenchmark.a avgt 5 1.507 0.108 ns/op
c.i.MyBenchmark.b avgt 5 16.976 1.572 ns/op

Lock coarsening locks the same object multiple times, resulting in multiple reentry of threads. Lock coarsening can be used for optimization, which is different from the granularity of subdivision lock mentioned earlier.

3.9.wait notify

9.1 principle of wait notify

  • When the Owner thread finds that the conditions are not met, it calls the wait method to enter the WaitSet and change to the WAITING state
  • Both BLOCKED and WAITING threads are BLOCKED and do not occupy CPU time slices
  • The BLOCKED thread wakes up when the Owner thread releases the lock
  • The WAITING thread will wake up when the Owner thread calls notify or notifyAll, but waking up does not mean that the Owner obtains the lock immediately and still needs to enter
  • EntryList re competes

9.2 introduction to wait notify

  • obj.wait() causes the thread entering the object monitor to wait in the waitSet [called by the object lock holder]
  • obj.notify() wakes up one of the waitSet waiting threads on the object [called by the object lock holder]
  • obj.notifyAll() wakes up all the threads waiting for waitSet on the object [called by the object lock holder]

Note: they are all means of cooperation between threads and belong to the methods of Object objects. It must be used with synchronized, and it can be called only after obtaining a lock.

If it is not used in conjunction with synchronized and the lock is not obtained, an IllegalMonitorStateException will appear in the call

The difference between notify and notifyAll

 static final Object lock=new Object();
    public static void main(String[] args) {

        new Thread(()->{
           synchronized(lock){
               log.debug("implement...");
               try {
                   lock.wait();
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
               log.debug("Other codes");
           }

        },"t1").start();

        new Thread(()->{
          synchronized (lock){
              log.debug("implement...");
              try {
                  lock.wait();
              } catch (InterruptedException e) {
                  e.printStackTrace();
              }
              log.debug("Other codes...");
          }
        },"t2").start();

        sleep(1);
        log.debug("Wake up other threads...");
        synchronized (lock){

            //lock.notify(); //  Randomly select a waiting thread to wake up
            lock.notifyAll(); // Wake up all threads blocked on the object
        }

    }

The wait() method will release the lock of the object and enter the WaitSet waiting area, so that other threads can get the lock of the object. Wait indefinitely until notify. wait(long n) is a time limited wait. The wait ends after n milliseconds, or is notified. The second parameter with naos is only greater than 0, so add 1 to the timeOut time, which is not really accurate to nanoseconds

sleep(long n) and wait(long n)

  • sleep is the Thread method, while wait is the Object method
  • sleep does not need to be used with synchronized forcibly, but wait needs to be used with synchronized
  • sleep will not release the object lock while sleeping, but wait will release the object lock while waiting
  • Their status is TIMED_WAITING

9.3 correct use of wait and notify

step1

@Slf4j
public class Test1 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    sleep(2);
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working[Xiaonan]");
                }
            }
        }, "Xiaonan").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("You can start working");
                }
            }, "Other people").start();
    }


           new Thread(() -> {
               // Can I add synchronized (room) here?
               // It cannot be added, because after adding, the smoke sending thread also needs to wait for the object lock, so the smoke sending thread cannot get the object lock and cannot send smoke
               hasCigarette = true;
               log.debug("Here comes the smoke!");
           }, "Smoke delivery").start();
       }

}
  • Xiaonan always holds the object lock after sleeping, so that other threads cannot obtain the object lock. Only when Xiaonan thread releases the lock after waking up can other threads obtain the object lock and execute code
  • Xiaonan thread can't wake up until it has slept for 2s. Even if the smoke is delivered in advance, it can't wake up immediately
  • After the cigarette delivery thread adds synchronized (room), the cigarette delivery thread also needs to wait for the object lock and cannot deliver cigarettes. The main thread does not add synchronized (room)
    synchronized does not need to obtain object locks or block. You can set hasCigarette to true when Xiaonan thread is sleeping
  • The solution is to use the wait - notify mechanism

step2

@Slf4j
public class Test1 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working[Xiaonan]");
                }
            }
        }, "Xiaonan").start();

        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                synchronized (room) {
                    log.debug("You can start working");
                }
            }, "Other people").start();
    }


           new Thread(() -> {
             synchronized (room){
                 // Can I add synchronized (room) here?
                 // It cannot be added, because after adding, the smoke sending thread also needs to wait for the object lock, so the smoke sending thread cannot get the object lock and cannot send smoke
                 hasCigarette = true;
                 room.notify();
                 log.debug("Here comes the smoke!");
             }
           }, "Smoke delivery").start();

   }
}

The advantage of using wait is that the Xiaonan thread blocks but releases the object lock. Other threads can also get the object lock for execution. There is no need to wait until the Xiaonan thread wakes up to execute the code block

step3

@Slf4j
public class Test1 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                if (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                } else {log.debug("No dry survival...");
                }
            }
        }, "Xiaonan").start();
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("No takeout, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "my daughter").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("Here's the takeout!");
                // notify: you can only wake up one thread randomly, but you can't wake up small female threads accurately. The solution is to use notifyAll to wake them up
                room.notifyAll();
            }
        }, "Delivery").start();
   }
}
  • notify can only randomly wake up one thread in the WaitSet. At this time, if other threads are also waiting, it may not wake up the correct thread, which is called false wake-up

  • Solution, change to notifyAll

step4

@Slf4j
public class Test1 {
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (room) {
                log.debug("Any smoke?[{}]", hasCigarette);
                // Use while to avoid false wake-up. Continue to judge again the next time you get the lock. If you use if, you will not judge again the next time you get the lock
                while (!hasCigarette) {
                    log.debug("No smoke, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    break;
                }
                log.debug("Any smoke?[{}]", hasCigarette);
                if (hasCigarette) {
                    log.debug("You can start working");
                } else {log.debug("No dry survival...");
                }
            }
        }, "Xiaonan").start();
        new Thread(() -> {
            synchronized (room) {
                Thread thread = Thread.currentThread();
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (!hasTakeout) {
                    log.debug("No takeout, take a break!");
                    try {
                        room.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("Did you deliver the takeout?[{}]", hasTakeout);
                if (hasTakeout) {
                    log.debug("You can start working");
                } else {
                    log.debug("No dry survival...");
                }
            }
        }, "my daughter").start();
        sleep(1);
        new Thread(() -> {
            synchronized (room) {
                hasTakeout = true;
                log.debug("Here's the takeout!");
                // notify: you can only wake up one thread randomly, but you can't wake up small female threads accurately. The solution is to use notifyAll to wake them up
                room.notify();
            }
        }, "Delivery").start();
   }
}

antic

synchronized(lock) {
 while(The condition is not tenable) {
 lock.wait();
 }
 // work
}
//Another thread
synchronized(lock) {
 lock.notifyAll();
}

3.10 . Sync mode - Protective pause

  1. Definition: Guarded Suspension, which is used when one thread waits for the execution result of another thread
  • One result needs to be passed from one thread to another to associate them with the same GuardedObject
  • If there are results continuously from one thread to another, you can use message queuing (see producer / consumer)
  • In JDK, this mode is adopted for the implementation of join and Future
  • Because we have to wait for the results of the other party, we are classified into synchronous mode

10.0 Basic Edition

@Slf4j
public class PrivatePause {

    public static void main(String[] args) {
        GuardedObject guarded = new GuardedObject();

        new Thread(()->{
            // Download the html code of a web page and put it in the list collection
            List<String> list = Util.download();
            guarded.complete(list);
        },"A").start();

        new Thread(()->{
            List<String> list = (List<String>)guarded.getResult();
            log.debug("Get wait results...");
            log.debug("list="+list.size());
        },"B").start();
    }
}

class GuardedObject{
    private Object response;
    private static  final Object lock=new Object();


    /**
     * After the task is completed, you can set the value of response and wake up the get method
     * @param response
     */
    public void complete( Object response){
        synchronized (this){
            this.response=response;
            this.notifyAll();
        }
    }

    /**
     *  If the response result is null, wait until it is awakened by complete
     * @return
     */
    public Object getResult(){
        synchronized (this){
            while (response==null){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return response;
    }
}
// Download method
public static List<String> download(String targetUrl)   {
        String defaultUrl="https://www.taobao.com";
        List<String> list = new ArrayList<>();
        HttpURLConnection connection=null;
        try {
           if (targetUrl!=null&&targetUrl.length()>0){
               connection = (HttpURLConnection) new URL(targetUrl).openConnection();
           }else {
               connection = (HttpURLConnection) new URL(defaultUrl).openConnection();
           }
        } catch (IOException e) {
            e.printStackTrace();
        }

        try(BufferedReader reader=new BufferedReader(new InputStreamReader(connection.getInputStream(), StandardCharsets.UTF_8))) {
           String line;
            while ((line=reader.readLine())!=null){
                list.add(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }


        return list;
    }

10.1 timeout version

    /**
     *  Version with timeout
     * @Parse Unit: ms
     * @return
     */
    public Object getResult(long timeOut ){
        // start time
        long startTime=System.currentTimeMillis();
        // Experience time
        long passTime=0;

        synchronized (this){
            while (response==null){
                // How long should this cycle wait
                long waitTime = timeOut - passTime;
                // Wait time > = timeout exit the loop directly
                if (waitTime<=0){
                    break;
                }
                try {
                    // Timeout passtime: avoid false wake-up
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Wait time = present time - start time
                passTime = System.currentTimeMillis() - startTime;
            }

        }
        return response;
    }

10.2 join principle

Join: let the thread calling the join wait until the thread object calling the join executes and releases the lock, and then the thread calling Jeon has a chance to obtain the lock and continue to execute

// The lock is the this object. If the thread object calls, the thread object will be used as the lock
public final synchronized void join(long millis)
    throws InterruptedException {
   		 //Get start time
        long base = System.currentTimeMillis();
        long now = 0;
		// If the set time is less than 0, an IllegalArgumentException is thrown
        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }
		// Judge the set waiting time. If = = 0, wait until the thread object calling the join method is executed, and the thread calling jion will not run downward
        if (millis == 0) {
            // If the thread calling the join is still alive, it will always enter the waiting state
            while (isAlive())
                wait(0);
            }
        } else {
   		  // Wait with timeout
            while (isAlive()) {
                // Time to wait per cycle = time to wait - time spent per cycle
                long delay = millis - now;
                // The final waiting time < = 0 ends the waiting
                if (delay <= 0) {
                    break;
                }
                wait(delay)
                 // After executing a cycle, update the time spent in each cycle
                now = System.currentTimeMillis() - base;
            }
        }
    }

10.3 multitasking version

In the figure, Futures is like a mailbox on the first floor of a residential building (each mailbox has a room number). t0, t2 and t4 on the left are like residents waiting for mail. t1, t3 and t5 on the right are like postmen. If you need to use GuardedObject objects among multiple classes, it is not convenient to pass them as parameters. Therefore, an intermediate class is designed to decouple, which can not only decouple [result waiters] and [result producers] can also support the management of multiple tasks at the same time [Futures is a container for storing data communicated between multiple threads]

The complete code is as follows:

@Slf4j
public class PrivatePause {

    public static void main(String[] args) {
        // Start three receiving residents
       for (int i=0;i<3;i++){
           new People().start();
       }

       sleep(1);
       // Start three postmen

      for (Integer id: Futures.getIds()){
        new PostMan(id, "hello world[" + id + "]").start();
      }


    }
}

@Slf4j
// Resident class
class People extends Thread{
    @Override
    public void run() {
        // Receive a letter
        GuardedObject guarded = Futures.createGuarded();
        log.debug("Receive a letter id:{}",guarded.getId());
        Object email = guarded.getResult(3000);
        log.debug("Received letter id:{},Content:{}",guarded.getId(),email.toString());
    }
}

@Slf4j
// Postman class
class PostMan extends Thread{
    private int id;
    private String data;
    @Override
    public void run() {
        GuardedObject guarded = Futures.getGuardedById(id);

        guarded.complete(data);
        log.debug("Delivery: id:{},Content:{}",id,data);
    }

    public PostMan(int id,String email){
        this.id=id;
        this.data=email;
    }

}

class Futures{
    // The map needs to be thread safe. key is the ID of GuardedObject and value is GuardedObject
    private static Map<Integer,GuardedObject> futures= new ConcurrentHashMap<>();
    private static int id=1;

    /**
     * Generate GuardedObject object object to ensure the uniqueness of id
     * Put the created object into boxes
     * @return Return GuardedObject
     */
    public static GuardedObject createGuarded(){
        GuardedObject guardedObject=new GuardedObject();
        guardedObject.setId(generateId());
        futures.put(guardedObject.getId(),guardedObject);
        return guardedObject;
    }

    /**
     *  Get all IDS
     * @return
     */
    public static Set<Integer> getIds(){
        return futures.keySet();
    }

    /**
     *  Getting the id will be accessed by multiple threads, so thread safety needs to be considered
     * @return Generate a unique id
     */
    public static synchronized int generateId(){
        return id++;
    }

    /**
     *  Get the GuardedObject object object according to the id
     * @param id
     * @return
     */
    public static GuardedObject getGuardedById(int id){
        GuardedObject object = futures.get(id);
        // After obtaining the completion result, remove the GuardedObject from the map collection
        return futures.remove(id);
    }
}

class GuardedObject{
    private Object response;
    private int id;

    public int getId() {
        return id;
    }

    public GuardedObject setId(int id) {
        this.id = id;
        return this;
    }

    /**
     * After the task is completed, you can set the value of response and wake up the get method
     * @param response
     */
    public void complete( Object response){
        synchronized (this){
            this.response=response;
            this.notifyAll();
        }
    }

    /**
     *  If the response result is null, wait until it is awakened by complete
     * @return
     */
    public Object getResult(){
        synchronized (this){
            while (response==null){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        return response;
    }

    /**
     *  Version with timeout
     * @Parse Unit: ms
     * @return
     */
    public Object getResult(long timeOut ){
        // start time
        long startTime=System.currentTimeMillis();
        // Experience time
        long passTime=0;

        synchronized (this){
            while (response==null){
                // How long should this cycle wait
                long waitTime = timeOut - passTime;
                // Wait time > = timeout exit the loop directly
                if (waitTime<=0){
                    break;
                }
                try {
                    // Timeout passtime: avoid false wake-up
                    this.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Wait time = present time - start time
                passTime = System.currentTimeMillis() - startTime;
            }

        }
        return response;
    }
}

3.11. Asynchronous mode - consumer / producer mode

  • Unlike the GuardObject in the previous protective pause, there is no need for one-to-one correspondence between the threads that generate and consume results
  • Consumption queues can be used to balance thread resources for production and consumption
  • The producer is only responsible for generating the result data and does not care about how to deal with the data, while the consumer focuses on dealing with the result data
  • Message queues have capacity limits. When full, data will not be added, and when empty, data will not be consumed
  • This mode is adopted for various blocking queues in JDK

@Slf4j
// Communication between java message threads
public class MessageQueue {
    // Message queue
    LinkedList<Message> list =new LinkedList();
    // Queue message capacity
    public int capacity;

    public MessageQueue(int capacity){
        this.capacity=capacity;
    }

    /**
     * Get message from queue
     * @return
     */
    public Message take(){
        synchronized (list){
            while (list.isEmpty()){
                try {
                    log.debug("Queue is empty... Consumer thread wait");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // Get the message from the queue and remove the message from the queue, allowing the producer to produce the message
            Message message = list.removeFirst();
            log.debug("Consumption news= "+message);
            // Wake up producers to produce data
            list.notifyAll();
            return message;
        }

    }

    /**
     * Add message to queue
     * @param message news
     */
    public void put(Message message){
        synchronized (list){
            // If the queue is full, wait until the consumer has consumed
            while (list.size()>=capacity){
                try {
                    log.debug("Queue full... Producer thread wait");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            log.debug("Production message= "+message);
            // Put the produced data into the queue head
            list.addFirst(message);
            // Wake up the consumer thread for consumption
            list.notifyAll();
        }
    }
}

class Message{
    // Message id
    private int id;
    // data
    private Object data;

    public void setId(int id) {
        this.id = id;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Message(int id,Object data){
        this.id = id;
        this.data = data;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", data=" + data +
                '}';
    }
}
class TestA{
    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);
        // Start 3 producer threads
        for (int i = 0; i < 3; i++) {
           int index=i;
            new Thread(()->{
                queue.put(new Message(index,"news["+index+"]"));
            },"producer"+i).start();
        }
        sleep(1);
        // Start 3 consumer threads
        for (int i = 0; i < 3; i++) {

            new Thread(()->{
                queue.take();
            },"consumer"+i).start();
        }

    }

}

Output:

18:52:37 [DEBUG] [Producer: 0] c.c.d.MessageQueue - Production message= Message{id=0, message=news[0]}
18:52:37 [DEBUG] [Producer: 2] c.c.d.MessageQueue - Production message= Message{id=2, message=news[2]}
18:52:37 [DEBUG] [Producer: 1] c.c.d.MessageQueue - Queue full... Producer thread wait
18:52:38 [DEBUG] [Consumer: 0] c.c.d.MessageQueue - Consumption news= Message{id=0, message=news[0]}
18:52:38 [DEBUG] [Consumer: 2] c.c.d.MessageQueue - Consumption news= Message{id=2, message=news[2]}
18:52:38 [DEBUG] [Consumer: 1] c.c.d.MessageQueue - Queue is empty... Consumer thread wait
18:52:38 [DEBUG] [Producer: 1] c.c.d.MessageQueue - Production message= Message{id=1, message=news[1]}
18:52:38 [DEBUG] [Consumer: 1] c.c.d.MessageQueue - Consumption news= Message{id=1, message=news[1]}

3.12.park&unpark

They are methods in the LockSupport class

// Pauses the current thread
LockSupport.park();
// Resume a thread
LockSupport.unpark(Pause thread object)
// park before unpark
   public static void main(String[] args)   {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            sleep(2);
            log.debug("park..");
            // The current thread enters the wait state
            LockSupport.park();
            log.debug("resume...");
        }, "t1");
        t1.start();

        sleep(1);
        log.debug("unpark...");
        // Wake up t1 thread
        LockSupport.unpark(t1);
    }

3.12. 1. Compared with wait & notify

  • wait, notify and notifyAll must be used together with Object Monitor, but park and unpark do not

  • Park & unpark refers to [blocking] and [waking up] threads in threads, while notify can wake up only one waiting thread randomly. notifyAll refers to waking up all waiting threads, which is not so [accurate]

  • Park & unpark can unpark first, while wait & notify cannot notify first

3.12.2.unpark and park principle

Each thread has its own Parker object, which is composed of three parts_ counter [0 or 1], _cond and _mutex

  • Calling park is to judge whether the current thread needs to enter the blocking state
    • If_ counter=0, enter blocking state
    • If_ counter=1, no need to enter the blocking state
  • Call unpark to wake up the thread blocked by park
    • If the thread is still blocked [c_counter=0], wake up and let it continue to execute downward
    • If the thread is still running [_counter=1], the next time it calls park, it will continue to execute downward without blocking

3.13. Thread state transition

3.13.1 NEW --> RUNNABLE

When the t.start() method is called, new -- > runable

       // New [new status]
        Thread t1 = new Thread(() -> {
            log.debug("RUNNABLE");
        }, "t1");

3.13.2 RUNNABLE <–> WAITING

  • After the t thread obtains the object lock with synchronized(obj)
    • Call obj When using the wait() method, the t thread starts from runnable -- > waiting
    • Call obj notify() , obj. When notifyall(), t.interrupt()
      • The contention lock succeeds, and the t thread starts running -- > from waiting
      • Contention lock failed, t thread from waiting -- > block

3.13.3RUNNABLE <–> WAITING

  • When the current thread calls the t.join() method, the current thread starts from runnable -- > waiting
    • Note that the current thread is waiting on the monitor of the t thread object
  • t when the thread ends running, or the interrupt() of the current thread is called, the current thread starts from waiting -- > runnable

3.13.4RUNNABLE <–> WAITING

  • The current thread calls locksupport The park () method will make the current thread from runnable -- > waiting
  • Call locksupport Unpark (target thread) or interrupt() of the thread is called to make the target thread from waiting -- > runnable

3.13.5 RUNNABLE <–> TIMED_WAITING

  • After the thread obtains the object lock with synchronized(obj)
  • Call obj When using the wait (long n) method, the t thread starts from runnable -- > timed_ WAITING
    • T thread waiting time exceeds n milliseconds, or call obj notify() , obj. When notifyall(), t.interrupt()
    • Contention lock successful, t thread from timed_ WAITING --> RUNNABLE
    • Contention lock failed, t thread from timed_ WAITING --> BLOCKED

3.13.6 RUNNABLE <–> TIMED_WAITING

  • When the current thread calls the t.join(long n) method, the current thread starts from runnable -- > timed_ WAITING
    • Note that the current thread is waiting on the monitor of the t thread object
  • When the waiting time of the current thread exceeds n milliseconds, or the running of the t thread ends, or the interrupt() of the current thread is called, the current thread starts from timed_ WAITING --> RUNNABLE

3.13.7 RUNNABLE <–> TIMED_WAITING

  • The current thread calls thread Sleep (long n), the current thread from runnable -- > timed_ WAITING
  • The waiting time of the current thread has exceeded n milliseconds. The current thread is from timed_ WAITING --> RUNNABLE

3.13.8 RUNNABLE <–> TIMED_WAITING

  • The current thread calls locksupport Parknanos (long nanos) or locksupport When parkuntil (long miles), the current thread starts from runnable -- > timed_ WAITING
  • Call locksupport Unpark (target thread) or call the thread interrupt() or wait timeout will cause the target thread to start from TIMED_WAITING–> RUNNABLE

3.13.9 RUNNABLE <–> BLOCKED

  • When the t thread obtains the object lock with synchronized(obj), if the contention fails, it will start from runnable -- > blocked
  • After the synchronization code block of the obj lock thread is executed, all BLOCKED threads on the object will be awakened to compete again. If t threads compete
    Successful, from BLOCKED -- > runnable, other failed threads are still BLOCKED

3.13.10 RUNNABLE <–> TERMINATED

  • After all the codes of the current thread have been run, enter TERMINATED

3.14. Multiple locks

When two operations are not related and there is no shared data, you can use multiple locks to reduce the granularity of locks. The smaller the granularity, the higher the concurrent reading. If two unrelated methods share the same lock, the granularity of locks will be too large and the concurrency will be low.

  • Subdivide the granularity of locks
    • The advantage is that it can enhance concurrency
    • The disadvantage is that if a thread needs to obtain multiple locks at the same time, it is prone to deadlock

The following codes share the same object lock this. When calling the study method, the study method occupies the object lock this time, and other threads also need this object lock when they want to call the sleep method. The thread calling sleep can continue to execute only after waiting for the thread calling study to release the lock, which leads to, Two irrelevant methods produce dependency relations, and the granularity of locks becomes larger. The solution is to let study and sleep hold different object locks

@Slf4j
public class Test1 {

    public static void main(String[] args) {

        BigRoom room = new BigRoom();
        new Thread(()->{
            room.sleep();
        },"A").start();

        new Thread(()->{
            room.study();
        },"B").start();
    }
}
@Slf4j
class BigRoom{

    public void  sleep(){
        synchronized (this){
            log.debug("Sleeping...");
            // Sleep for 2 seconds
            Util.sleep(2);
        }
    }

    public void study(){
        synchronized (this){
            log.debug("I am learning...");
            // Sleep for 2 seconds
            Util.sleep(3);
        }
    }
}

Improved code: the two methods hold different object locks, do not need to wait for synchronization, and can run at the same time

@Slf4j
class BigRoom{
   static final Object sleep = new Object();
   static final Object study = new Object();
    public void  sleep(){
        synchronized (sleep){
            log.debug("Sleeping...");
            // Sleep for 2 seconds
            Util.sleep(2);
        }
    }

    public void study(){
        synchronized (study){
            log.debug("I am learning...");
            // Sleep for 2 seconds
            Util.sleep(3);
        }
    }
}

3.15 thread activity

3.15.1 deadlock

There is such a situation: a thread needs to obtain multiple locks at the same time, and deadlock is easy to occur

  • The t1 thread obtains the lock of the A object. Next, it wants to obtain the lock of the B object

  • The t2 thread obtains the lock of the B object. Next, it wants to obtain the lock of the A object

The following code is a deadlock:

@Slf4j
public class Test1 {
    static final Object A = new Object();
    static final Object B = new Object();
    public static void main(String[] args) {


        new Thread(()->{
           synchronized (A){
               // Sleep for 0.5 seconds, let t2 obtain the B object lock first
               sleep(500L);
               log.debug("get A Object lock");
               synchronized (B){
                   log.debug("get B Object lock");
               }
           }
        },"t1").start();

        new Thread(()->{
           synchronized (B){
               // Sleep for 0.5 seconds and let t1 obtain the A object lock first
               sleep(500L);
               log.debug("get B Object lock");
               synchronized (A){
                   log.debug("get A Object lock");
               }
           }
        },"t2").start();
    }
}

How to locate the code location where the deadlock occurs?

To detect deadlock, you can use jconsole tool, or use jps to locate the process id, and then use jstack to locate the deadlock:

1. Use the jps command to view the IDs of all java processes

2. Use jstack 184344 (java process id) to view the status information of java threads

Details:

2021-11-21 16:50:12
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):

"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x0000000003264000 nid=0x2d230 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"t2" #13 prio=5 os_prio=0 tid=0x000000001bcab000 nid=0x2cbf8 waiting for monitor entry [0x000000001c27f000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.compass.test.Test1.lambda$main$1(Test1.java:41)
        - waiting to lock <0x00000000d6cfa208> (a java.lang.Object)
        - locked <0x00000000d6cfa218> (a java.lang.Object)
        at com.compass.test.Test1$$Lambda$2/1792393294.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"t1" #12 prio=5 os_prio=0 tid=0x000000001bca8000 nid=0x2d1c4 waiting for monitor entry [0x000000001c17e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.compass.test.Test1.lambda$main$0(Test1.java:30)
        - waiting to lock <0x00000000d6cfa218> (a java.lang.Object)
        - locked <0x00000000d6cfa208> (a java.lang.Object)
        at com.compass.test.Test1$$Lambda$1/1914572623.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x0000000019ca0000 nid=0x2d1c8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x0000000019c81000 nid=0x2d1c0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x0000000019c66000 nid=0x2d1b0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x0000000019c7e000 nid=0x2cbf0 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x0000000019c79000 nid=0x2cd50 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x0000000019c5f000 nid=0x2cd38 runnable [0x000000001b1ce000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
        at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
        at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
        - locked <0x00000000d632d5d0> (a java.io.InputStreamReader)
        at java.io.InputStreamReader.read(InputStreamReader.java:184)
        at java.io.BufferedReader.fill(BufferedReader.java:161)
        at java.io.BufferedReader.readLine(BufferedReader.java:324)
        - locked <0x00000000d632d5d0> (a java.io.InputStreamReader)
        at java.io.BufferedReader.readLine(BufferedReader.java:389)
        at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:61)

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000000019bb8800 nid=0x2cce4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000000019b64000 nid=0x2d1a8 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000000335b000 nid=0x2c724 in Object.wait() [0x000000001aecf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6108ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000d6108ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x0000000019b43000 nid=0x2c8e4 in Object.wait() [0x000000001adcf000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000d6106c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000d6106c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x0000000018476000 nid=0x2cec8 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000003279800 nid=0x2cf38 runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x000000000327b000 nid=0x2cf88 runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000327c800 nid=0x2ce08 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000327e000 nid=0x2cdb4 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x0000000003281000 nid=0x2cdf4 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000003282800 nid=0x2ce9c runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000003285800 nid=0x2ce80 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000003286800 nid=0x2d12c runnable

"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x0000000003288000 nid=0x2d160 runnable

"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x0000000003289000 nid=0x2cef0 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x0000000019c67000 nid=0x2cd64 waiting on condition

JNI global references: 317

// A deadlock was found
Found one Java-level deadlock:
=============================
"t2":
  waiting to lock monitor 0x0000000018485108 (object 0x00000000d6cfa208, a java.lang.Object),
  which is held by "t1"
"t1":
  waiting to lock monitor 0x0000000018487578 (object 0x00000000d6cfa218, a java.lang.Object),
  which is held by "t2"

Java stack information for the threads listed above:
===================================================
"t2":
        at com.compass.test.Test1.lambda$main$1(Test1.java:41)
        - waiting to lock <0x00000000d6cfa208> (a java.lang.Object)
        - locked <0x00000000d6cfa218> (a java.lang.Object)
        at com.compass.test.Test1$$Lambda$2/1792393294.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
"t1":
        at com.compass.test.Test1.lambda$main$0(Test1.java:30)
        - waiting to lock <0x00000000d6cfa218> (a java.lang.Object)
        - locked <0x00000000d6cfa208> (a java.lang.Object)
        at com.compass.test.Test1$$Lambda$1/1914572623.run(Unknown Source)
        at java.lang.Thread.run(Thread.java:748)
// A deadlock was found
Found 1 deadlock.

Using jconsole remote connection tool

Dining problem of philosophers

There are five philosophers sitting around the round table.

  • They only do two things, thinking and eating. They think for a while, have a meal, and then think after dinner.
  • When eating, you should use two chopsticks. There are five chopsticks on the table. Each philosopher has one chopstick on his left and right hands.
  • If the chopsticks are held by the people around you, you have to wait [life and death lock will occur]
// Philosophers
@Slf4j
public class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }
    private void eat() {
        log.debug("eating...");

    }

    @Override
    public void run() {
        while (true) {
            // Get left-hand chopsticks
            synchronized (left) {
                // Get right-hand chopsticks
                synchronized (right) {
                    // having dinner
                    eat();
                }
                // Put down your right chopsticks
            }
            // Put down your left chopsticks
        }
    }
}
// Chopsticks
class Chopstick {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("Socrates", c1, c2).start();
        new Philosopher("Plato", c2, c3).start();
        new Philosopher("Aristotle", c3, c4).start();
        new Philosopher("Heraclitus", c4, c5).start();
        new Philosopher("Archimedes", c5, c1).start();

    }
    String name;
    public Chopstick(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "chopsticks{" + name + '}';
    }
}

3.15.2 movable lock

A livelock occurs when two threads change each other's end conditions, resulting in the failure of both end conditions. Finally, no one can end, for example

@Slf4j
public class TestLiveLock {
    static volatile int count=20;
    static final  Object lock =new Object();
    // Solution: the sleep time of the two is inconsistent, and the method of random number is adopted
    public static void main(String[] args) {
        // Count < 0 is expected to exit the loop
        new Thread(()->{
            while (count>0){
                Util.sleep(200L);
                count--;
                log.debug("count:{}",count);
            }
        },"A").start();
        // Count > 40 is expected to exit the loop
        new Thread(()->{
            while (count<40){
                Util.sleep(200L);
                count++;
                log.debug("count:{}",count);
            }
        },"B").start();
    }
}

3.15.3 hunger

As an example of thread starvation, let's take a look at using sequential locking to solve the previous deadlock problem

The previous deadlock Code: let's modify it and lock it in A sequential manner, so there will be no deadlock problem. Then the new problem comes again, which will lead to thread hunger. Some threads have not been able to get the execution right. Sometimes thread A gets the lock for A long time, and sometimes thread B gets the lock for A long time

@Slf4j
public class Test {
    static final Object A = new Object();
    static final Object B = new Object();
    public static void main(String[] args) {


        new Thread(()->{
           while (true){
               synchronized (A){

                   log.debug("get A Object lock");
                   synchronized (B){
                       log.debug("get B Object lock");
                   }
               }
           }
        },"A").start();

        new Thread(()->{
            while (true){
                synchronized (A){

                    log.debug("get B Object lock:" );
                    synchronized (B){
                        log.debug("get A Object lock");
                    }
                }
            }
        },"B").start();
    }
}

3.16 ReentrantLock

Compared with synchronized, it has the following characteristics

  • Interruptible. Timeout can be set
  • Can be set to fair lock
  • Support multiple conditional variables [multiple waitsets]
  • Like synchronized, reentrant basic syntax is supported

Basic syntax:

// Acquire lock
reentrantLock.lock();
try {
 // Critical zone
} finally {
 // Release lock
 reentrantLock.unlock();
}

3.16. 1 reentrant

  • Reentrant means that if the same thread obtains the lock for the first time, it has the right to obtain the lock again because it is the owner of the lock
  • If it is a non reentrant lock, you will be blocked by the lock the second time you obtain the lock
@Slf4j
public class ReentrantLockDemo {

    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }
    public static void method1() {
        lock.lock();
        try {
            log.debug("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }
    public static void method2() {
        lock.lock();
        try {
            log.debug("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }
    public static void method3() {
        lock.lock();
        try {
            log.debug("execute method3");
        } finally {
            lock.unlock();
        }
    }

}
// Call lock without locking Unlock will throw IllegalMonitorStateException

3.16. 2 interruptible

Note that if it is a non interruptible mode, even if interrupt is used, it will not make the wait interrupt

    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            try {
                log.debug("Attempt to acquire lock");
                // If the lock method is used here, it cannot be interrupted
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("No lock was obtained");
                return;
            } finally {
                lock.unlock();
            }
        }, "lock");

        // The main thread obtains the lock first
        lock.lock();
        // Start the main thread
        thread.start();
        // Let the main thread sleep for 1 second first
        Util.sleep(1);
        // Interrupt lock thread
        thread.interrupt();

    }

3.16. 3 timeout

    static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            // Try to obtain a lock. If you can't obtain it, enter the blocking queue. Obtain the lock next time: true; if you don't obtain the lock: false
            // lock.tryLock(1,TimeUtil.SECONDS) / / try to wait for 1 second to obtain the lock. If the lock cannot be obtained after 1 second, continue to execute downward [interruptible support]
              if (!lock.tryLock()){
                  log.debug("Cannot acquire lock");
                  return;
              }
              try {
                  log.debug("Get lock");
              }finally {
                  lock.unlock();
              }
        }, "lock");
        // After the main thread obtains the lock, the lock thread attempts to obtain the lock. If it cannot obtain the lock, it will not block
        // lock.lock();
        thread.start();
        Util.sleep(1);

    }
//  lock.tryLock(1,TimeUtil.SECONDS) can interrupt the wait immediately after the lock is not obtained within the specified time
// lock.tryLock() cannot be interrupted. The wait ends immediately if the lock is not obtained

3.16. 4 solve the dining problem of philosophers

    @Override
    public void run() {
     // Original version
    /*    while (true) {
            // Get left-hand chopsticks
            synchronized (left) {
                // Get right-hand chopsticks
                synchronized (right) {
                    // having dinner
                    eat();
                }
                // Put down your right chopsticks
            }
            // Put down your left chopsticks
        }*/

        // Improved version using ReentrantLock
        while (true){
            // Sleep for 300 milliseconds for easy viewing
            Util.sleep(300L);
            // Try to get left-handed chopsticks
            if (left.tryLock()){
                try {
                    // Try to get right-hand chopsticks
                    if (right.tryLock()){
                        try {
                            // With both chopsticks, you can eat
                            eat();
                        }finally {
                            // Release your right chopsticks after eating
                            right.unlock();
                        }
                    }
                }finally {
                    // Release your left chopsticks after eating
                    left.unlock();
                }
            }


        }
    }

3.16. 5 Fairness

ReentrantLock is unfair by default. It can be set as a fair lock by passing na ture through the construction method

// Setting to true and false has two different effects
static ReentrantLock lock = new ReentrantLock(true);
    public static void main(String[] args) throws InterruptedException {

        new Thread(()->{
            while (true){
                try {
                    lock.lock();
                    log.debug("Obtain lock: A thread ");
                }finally {
                    lock.unlock();
                }
            }
        },"A").start();

        new Thread(()->{
            while (true){
                try {
                    lock.lock();
                    log.debug("Obtain lock: B thread ");
                }finally {
                    lock.unlock();
                }
            }
        },"B").start();
    }

Fair locks are generally unnecessary and reduce concurrency. The principles will be explained later

3.16. 6 conditional variables

There are also conditional variables in synchronized, that is, the waitSet lounge when we talk about the principle. When the conditions are not met, enter the waitSet and wait
ReentrantLock's conditional variables are more powerful than synchronized in that it supports multiple conditional variables.

  • synchronized means that those threads that do not meet the conditions are waiting for messages in a lounge
  • ReentrantLock supports multiple lounges, including a lounge dedicated to waiting for cigarettes and a lounge dedicated to waiting for breakfast. It also wakes up according to the lounge when waking up

Key points of use:

  • You need to obtain a lock before await
  • After await is executed, it will release the lock and enter the conditionObject to wait
  • await's thread is awakened (or interrupted, or timed out) to re compete for the lock lock
  • After the contention lock is successful, the execution continues after await
 static ReentrantLock lock = new ReentrantLock();
    static Condition waitCigaretteQueue = lock.newCondition();
    static Condition waitBreakfastQueue = lock.newCondition();
    static volatile boolean hasCigarette = false;
    static volatile boolean hasBreakfast = false;
    public static void main(String[] args) {
        new Thread(() -> {
           try {
               lock.lock();
               log.debug("Any smoke?[{}]", hasCigarette);
               if (!hasCigarette) {
                   log.debug("No smoke, take a break!");
                   try {
                       waitCigaretteQueue.await();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }

                   log.debug("You can start working[Xiaonan]");

           }finally {
               lock.unlock();
           }
        }, "Xiaonan").start();

        new Thread(() -> {
            try {
                lock.lock();
                log.debug("Do you have breakfast?[{}]", hasBreakfast);
                if (!hasBreakfast) {
                    log.debug("No breakfast, take a break");
                    try {
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }

                log.debug("You can start working[my daughter]");

            }finally {
                lock.unlock();
            }
        }, "my daughter").start();

        new Thread(() -> {
            try {
                 lock.lock();
                 hasCigarette = true;
                 waitCigaretteQueue.signal();
                 log.debug("Here comes the smoke!");
             }finally {
                 lock.unlock();
             }

        }, "Smoke delivery").start();

        new Thread(() -> {
            try {
                lock.lock();
                hasBreakfast = true;
                waitBreakfastQueue.signal();
                log.debug("Breakfast is here!");
            }finally {
                lock.unlock();
            }
        }, "Breakfast delivery").start();

    }

Synchronized and Condition are similar in use, but the biggest difference is that Condition can accurately wake up a thread, while synchronized can only randomly wake up a waiting thread or wake up all waiting threads

3.17. Synchronous mode - sequence control

3.17. 1. Warn and notify (execution sequence)

Now there are two threads: you need to specify that a thread execute first

// Purpose: let thread B output 2 and thread a output 1 first
@Slf4j
public class FixedOrderWait {
    static  final  Object lock = new Object();
    // It is used to mark whether the B thread has been run. If it has been run, it is true
    static  volatile  boolean flag=false;
    public static void main(String[] args) {

        Thread a = new Thread(() -> {
            synchronized (lock){
                // Loop to determine whether thread B has run. If not, it will wait all the time
               while (!flag){
                   try {
                       lock.wait();
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }

                log.debug("1");
            }
        }, "A");

        Thread b = new Thread(() -> {
            log.debug("2");
          synchronized (lock){
              // Set the run flag to true
              flag=true;
              // Wake up thread A to print
               lock.notify();
          }
        }, "B");

        // Start a b thread
        a.start();
        b.start();
    }
}

3.17.2 Park Unpark

It can be seen that the implementation is troublesome:

  • First, you need to ensure that you wait before notify ing, otherwise the wait thread will never wake up. Therefore, the "run flag" is used to judge whether to wait
  • Second, if some interfering threads incorrectly notify the wait thread and wait again when the conditions are not met, a while loop is used to solve this problem
  • Finally, notifyAll needs to be used to wake up the wait thread on the object, because there may be more than one waiting thread on the synchronization object
  • You can use park and unpark of LockSupport class to simplify the above problem:
 public static void main(String[] args) {

        Thread a = new Thread(() -> {
            // Block first and wait for thread B to wake up
            LockSupport.park();
            log.debug("1");
        }, "A");

        Thread b = new Thread(() -> {
           log.debug("2");
           // Wake up A thread
           LockSupport.unpark(a);
        }, "B");

        // Start a b thread
        a.start();
        b.start();
    }

3.17.3 ReentrantLock

    volatile private static int state = 1;
    private static  ReentrantLock lock = new ReentrantLock();
    final private static Condition conditionA = lock.newCondition();

    public static void main(String[] args){

        Thread threadA = new Thread(()->{
            try {
                lock.lock();
                while(state != 2){
                    conditionA.await();
                }
                log.debug("1");

            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally{
                lock.unlock();
            }
        },"A");


        Thread threadB = new Thread(()->{
            try {
                lock.lock();
                log.debug("2");
                state = 2;
                conditionA.signal();
            }finally{
                lock.unlock();
            }
        },"B");

        // Start a b thread
           new Thread(threadA).start();
           new Thread(threadB).start();
    }

3.17. 4 "wait notify"

@Slf4j
public class WaitNotifyDemo{

    public static void main(String[] args) {

        WaitNotify syncWaitNotify = new WaitNotify(1, 5);
        new Thread(() -> {
            syncWaitNotify.print(1, 2, "a");
        }).start();
        new Thread(() -> {
            syncWaitNotify.print(2, 3, "b");
        }).start();
        new Thread(() -> {
            syncWaitNotify.print(3, 1, "c");
        }).start();

    }

}

class WaitNotify{

    /**
     * Wait flag: used to indicate who can run
     */
    private int flag;
    private int loopNumber;
    public WaitNotify(int flag,int loopNumber){
        this.flag=flag;
        this.loopNumber=loopNumber;
    }
    public void SyncWaitNotify(int flag, int loopNumber) {
        this.flag = flag;
        this.loopNumber = loopNumber;
    }
    public void print(int waitFlag, int nextFlag, String str) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                // Wait until the current tag is not the tag value of the next thread
                while (this.flag != waitFlag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                // Modify the tag to execute the next thread
                flag = nextFlag;
                // Wake up all threads
                this.notifyAll();
            }

        }
    }
}

3.17.5 Lock condition variable [alternate printing]

@Slf4j
public class AwaitSignal extends ReentrantLock {

    public void start(Condition first) {
        this.lock();
        try {
            log.debug("start");
            // Wake up thread A first
            first.signal();
        } finally {
            this.unlock();
        }
    }

    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            this.lock();
            try {
                // At the beginning, let all threads enter the wait
                current.await();
                log.debug(str.equals("c") ? str+"\n" : str);
                // Accurately wake up the next thread
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                this.unlock();
            }
        }
    }
    // Number of cycles
    private int loopNumber;
    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public static void main(String[] args) {

        AwaitSignal as = new AwaitSignal(5);

        Condition aWaitSet = as.newCondition();
        Condition bWaitSet = as.newCondition();
        Condition cWaitSet = as.newCondition();

        new Thread(() -> { as.print("a", aWaitSet, bWaitSet); }).start();
        new Thread(() -> { as.print("b", bWaitSet, cWaitSet); }).start();
        new Thread(() -> { as.print("c", cWaitSet, aWaitSet); }).start();

        as.start(aWaitSet);

    }
}

3.17. 6 "Park unpark"

public class ParkDemo {
    public static void main(String[] args) {
        SyncPark syncPark = new SyncPark(5);
        Thread t1 = new Thread(() -> {
            syncPark.print("a");
        });
        Thread t2 = new Thread(() -> {
            syncPark.print("b");
        });
        Thread t3 = new Thread(() -> {
            syncPark.print("c\n");
        });
        syncPark.setThreads(t1, t2, t3);
        syncPark.start();


    }
}
class SyncPark{
    private int loopNumber;
    private Thread[] threads;
    public SyncPark(int loopNumber) {
        this.loopNumber = loopNumber;
    }
    // Pass the created thread
    public void setThreads(Thread... threads) {
        this.threads = threads;
    }
    //
    public void print(String str) {
        for (int i = 0; i < loopNumber; i++) {
            // Let all threads enter the blocking state, only let thread[0] execute first
            LockSupport.park();
            System.out.print(str);
            // Cancels the blocking state of the next thread
            LockSupport.unpark(nextThread());
        }
    }
    //
    private Thread nextThread() {
        // Get current thread
        Thread current = Thread.currentThread();
        int index = 0;
        // Gets the currently running thread
        for (int i = 0; i < threads.length; i++) {
            if(threads[i] == current) {
                index = i;
                break;
            }
        }
        // Returns the next executing thread of the currently running thread
        if(index < threads.length - 1) {
            return threads[index+1];
        } else {
            return threads[0];
        }
    }
    // Traverse all threads, start them, and give threads[0] the right to execute first
    public void start() {

        for (Thread thread : threads) {
            thread.start();
        }
        // If unpark is executed first, then park locking will not succeed, and it can still run downward
        LockSupport.unpark(threads[0]);
    }

}

3.18 summary of this chapter

What we need to focus on in this chapter is

  • Analyze which code fragments belong to critical areas when multithreading accesses shared resources
  • Using synchronized mutex to solve thread safety problems in critical areas
    • Master the syntax of synchronized lock object
    • Master the syntax of synchronized loading member method and static method
    • Master the wait/notify synchronization method
  • Using lock mutual exclusion to solve the thread safety problem of critical area
    • Master the use details of lock: interruptible, lock timeout, fair lock and condition variable
  • Learn to analyze the thread safety of variables and master the use of common thread safety classes
  • Understand thread activity issues: deadlocks, livelocks, starvation
  • Application aspect
    • Mutual exclusion: use synchronized or Lock to achieve the mutual exclusion effect of shared resources
    • Synchronization: use the condition variable of wait/notify or Lock to achieve the effect of inter thread communication
  • Principle aspect
    • monitor, synchronized, wait/notify principles
    • synchronized advanced principle
    • Park & unpark principle
  • Mode aspect
    • Protective pause in synchronization mode
    • Producer consumer of asynchronous mode
    • Sequential control of synchronous mode

Dark horse programmer juc: Learning address

Keywords: Java Back-end Multithreading

Added by Stole on Thu, 23 Dec 2021 01:12:33 +0200