Management of concurrency sharing model

3. Shared model process

What is thread safety

When multiple threads access a method, no matter how you call it or how these threads execute alternately, we don't need to do any synchronization in the main program. The resulting behavior of this class is the correct behavior we imagine, so we can say that this class is thread safe.

3.1 problems caused by sharing

3.1.1 embodiment of Java code

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);
}

3.1.2 problem analysis

I bytecode

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 in the virtual machine stack 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 in the virtual machine stack 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:

II single thread

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

III multithreading

However, these 8 lines of code may run alternately under multithreading:

When a negative number occurs:

When a positive number occurs:

3.1.3 terminology

I 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--;
}

II 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

III solution

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 variables

3.2 synchronized

3.2.1 definitions

synchronized, commonly known as the [object lock], is mutually exclusive 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 ensures 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 of threads. One thread needs to wait for other threads to run to a certain point

You can make this analogy:

  • Objects in synchronized (objects) can be imagined as a room. A room with a unique entrance (door) can only enter one person at a time for calculation. Threads t1 and t2 can be imagined as two people
  • When thread t1 executes synchronized(room), it is like t1 enters the room, locks the door, takes away the key, and executes count + + code in the door
  • At this time, if t2 also runs to synchronized(room), it finds that the door is locked and can only wait outside the door. Context switching occurs and it is blocked
  • In this process, even if t1's cpu time slice is unfortunately used up and kicked out of the door (don't mistakenly understand that it can be executed all the time if the object is locked), the door is still locked, t1 still holds the key, and t2 thread is still blocked. It can't enter until t1 gets the time slice again next time
  • When t1 finishes executing the code in the synchronized {} block, it will come out of the obj room and unlock the door, wake up the t2 thread and give him the key. The t2 thread can then enter the obj room, lock the door, take the key, and execute its count -- code

3.2.2 syntax

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

Example

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

3.2.3 thinking

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-- Atomicity

Then 20000 lines (5000) in the for loop ✖ 4) The lock will not be released until the bytecode is executed.

There are 4 lines of bytecode in it. After the instruction is executed, the lock will be released.

  • What happens if t1 synchronized(obj1) and t2 synchronized(obj2)-- Lock object

Equivalent to different rooms, it is equivalent to not locking and can not achieve the effect

  • What happens if T1 is synchronized (obj) and t2 is not added? How to understand-- Lock object

When t2 is running, it will not get the time slice, so it will not be blocked. You can directly go in and run the code, which is equivalent to kicking the door

3.2.4 object oriented improvement

Put the shared variables that need to be protected into a class

package demo1;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;

public class test1 {
    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++)
                room.increse();
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++)
                room.decrese();
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(room.getCounter());
    }
}
class Room{
    private int counter=0;

    public void increse(){
        synchronized (this){
            counter++;
        }
    }
    public void decrese(){
        synchronized (this){
            counter--;
        }
    }
    public int getCounter(){
        synchronized (this){
            return counter;
        }
    }
}

3.3 synchronized on method

3.3.1 syntax

Non static method

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

Static method

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

3.3.2 method without synchronized

The method of not synchronizing is like people who don't obey the rules and don't queue honestly (like going through the window)

3.3.3 thread eight lock

In fact, it is to investigate which object synchronized locks

Case 1:12 or 21

@Slf4j(topic = "c.Number")
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 1s, or 1 after 2 1s

@Slf4j(topic = "c.Number")
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();
}

Case 3: 3 1s 12 or 23 1s 1 or 32 1s 1

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();
}

Case 4: 2 1s later 1

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();
}

Case 5:2 1s 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();
}

Case 6: 12 after 1s, or 1 after 2 1s

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();
}

Case 7:2 1s 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();
}

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

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();
}

3.4 thread safety analysis of variables

3.4.1 are member variables 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

3.4.2 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

3.4.3 specific analysis

I local variable analysis

It must be thread safe, because local variables are thread private and cannot be shared by multiple threads

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

II member variable analysis

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

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();
        }
    }

    private void method2() {
        list.add("1");
    }

    private void method3() {
        list.remove(0);
    }

}
Exception in thread "Thread1" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
    at java.util.ArrayList.rangeCheck(ArrayList.java:657)
    at java.util.ArrayList.remove(ArrayList.java:496)
    at cn.itcast.n6.ThreadUnsafe.method3(TestThreadSafe.java:35)
    at cn.itcast.n6.ThreadUnsafe.method1(TestThreadSafe.java:26)
    at cn.itcast.n6.TestThreadSafe.lambda$main$0(TestThreadSafe.java:14)
    at java.lang.Thread.run(Thread.java:748)

list. Bytecode of add ("1")

 0 aload_0
 1 getfield #4 <demo1/ThreadUnsafe.list>
 4 ldc #7 <1>
 6 invokevirtual #8 <java/util/ArrayList.add>
 9 pop
10 return

list. Bytecode of remove (0)

0 aload_0
1 getfield #4 <demo1/ThreadUnsafe.list>
4 iconst_0
5 invokevirtual #9 <java/util/ArrayList.remove>
8 pop
9 return

Analysis of this insecurity

  1. We can't list add("1"); And list Remove (0) is regarded as an atom, but should be analyzed from the perspective of bytecode, possibly list If the remove method is executed halfway through the execution of add ("1") (for example, when the sixth line is not executed), an array out of bounds error will be reported.
  2. No matter which thread, method2/3 refers to the list member variable in the same object, resulting in a variable being accessed by multiple threads.

III citation analysis of local variables

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 such 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

Security impact of access modifiers of IV methods

If you change the methods of method2 and method3 to public, will it cause proxy thread safety problems?

meeting

  • Case 1: there are other threads calling method2 and method3
  • Case 2: on the basis of case 1, add a subclass for ThreadSafe class. The subclass overrides method2 or method3 methods, i.e
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");
    }
    public void method3(ArrayList<String> list) {
        list.remove(0);
    }
}
class ThreadSafeSubClass extends ThreadSafe{
    @Override
    public void method3(ArrayList<String> list) {
        new Thread(() -> {
            list.remove(0);
        }).start();
    }
}

From this example, we can see the meaning of [security] provided by private or final. Please understand [closing] in the opening and closing principle

3.5 common thread safety classes

3.5.1 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

3.5.2 combination of thread safe methods

Analyze whether the following code is thread safe?

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

It can be seen from the above that the combination of thread safe methods is not necessarily thread safe

3.5.3 thread safety of immutable class

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

3.5.4 example analysis

Example 1:

public class MyServlet extends HttpServlet {
    // Is it safe? no
    Map<String,Object> map = new HashMap<>();
    // Is it safe? yes
    String S1 = "...";
    // Is it safe? yes
    final String S2 = "...";
    // Is it safe? no
    Date D1 = new Date();
    // Is it safe? no
    final Date D2 = new Date();// References cannot be changed, but objects stored in the heap can be changed
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        // Use the above variables
    }
}

Example 2:

public class MyServlet extends HttpServlet {
    // Is it safe? no
    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? no
    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));
    }
}

Example 4:

Bottom up analysis

public class MyServlet extends HttpServlet {
    // Is it safe 3 It's safe
    private UserService userService = new UserServiceImpl();
    public void doGet(HttpServletRequest request, HttpServletResponse response) {
        userService.update(...);
    }
}
public class UserServiceImpl implements UserService {
    // Is it safe 2 It's safe
    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 safe 1 It's safe
        try (Connection conn = DriverManager.getConnection("","","")){
            // ...
        } catch (Exception e) {
            // ...
        }
    }
}

Example 5:

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 {
    // Is it safe   
    private UserDao userDao = new UserDaoImpl();
    public void update() {
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // Is it safe 1 Not thread safe
    private Connection conn = null;
    public void update() throws SQLException {
        String sql = "update user set password = ? where username = ?";
        conn = DriverManager.getConnection("","","");
        // ...
        conn.close();
    }
}

Example 6:

This is thread safe because UserDao is used as a local variable in the Service method, but it is not recommended

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() {
        UserDao userDao = new UserDaoImpl();
        userDao.update();
    }
}
public class UserDaoImpl implements UserDao {
    // Is it safe  
    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() {
        // Is it safe
        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();
    }
}

3.6 exercises

3.6.1 ticket selling exercise

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

package demo1;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;

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);//Not adding directly is to ensure the thread safety of this step and control variables
                //There is no need to consider thread safety after combination, because the shared variables of the two methods are not one
            });
            list.add(t);
            t.start();
        }
        list.forEach((t) -> {
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // Sum of tickets bought
        System.out.println(sellCount.stream().mapToInt(c -> c).sum());
        System.out.println(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;
    }

    public int getCount() {
        return count;
    }

    public int sell(int amount) {
        if (this.count >= amount) {
            this.count -= amount;
            return amount;
        } else {
            return 0;
        }
    }
}

The solution is

 public synchronized int sell(int amount) 

3.6.2 transfer exercise

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

package demo1;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;

public class ExerciseSell {
    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
        System.out.println(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;
    }

    public  void transfer(Account target, int amount) {
        if (this.money > amount) {
            this.setMoney(this.getMoney() - amount);
            target.setMoney(target.getMoney() + amount);
        }
    }
}

It needs to be improved, but it cannot be improved one by one. The lock object cannot be this, but should be account class

package demo1;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;

public class ExerciseSell {
    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
        System.out.println(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;
    }

    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.7 Monitor concept

3.7.1 object header

Take a 32-bit virtual machine as an example

Common object

Array object

The Mark Word structure is

64 bit virtual machine Mark Word

reference material:

https://stackoverflow.com/questions/26357186/what-is-in-java-object-header

3.7.2 Monitor principle

I diagram angle

Monitor is translated as monitor or tube

Each java object can be associated with a Monitor. If synchronized is used to lock the object (heavyweight), the Mark Word in the object header will be set as a pointer to the Monitor object, as shown in the following figure

The Monitor structure is as follows

  • At the beginning, the Owner in Monitor is null
  • When Thread-2 executes the synchronized(obj) {} code, the Owner of the Monitor will be set to Thread-2. The lock is successful. There can only be one Owner in the Monitor at the same time
  • When Thread-2 occupies the lock, if Thread-3 and Thread-4 also execute the synchronized(obj) {} code, it will enter the EntryList and become 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 WAITING state. They will be analyzed later when we talk about wait notify

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 monitors and do not comply with the above rules

II bytecode angle

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

be careful
Method level synchronized is not reflected in bytecode instructions

3.7.3 Monitor optimization

I little story

Story character

  • Lao Wang - JVM
  • Xiaonan - thread
  • Little girl - thread
  • Room object
  • Room door - anti theft lock - Monitor
  • Room door - Xiaonan schoolbag - lightweight lock
  • Room door - engraved with Xiao Nan's name - bias lock
  • Batch reprint name - a class's biased lock revocation reaches the threshold of 20
  • Can't engrave name - cancel the bias lock of this type of object in batch, and set this type to be non biased

Xiao Nan wants to use the room to ensure that the calculation is not disturbed by others (atomic). Initially, he uses an anti-theft lock. When the context is switched, he locks the door. In this way, even if he leaves, others can't enter the door, and his job is safe.

However, in many cases, no one competes with him for the right to use the room. The little girl wants to use the room, but the use time is staggered. Xiaonan uses it during the day and the little girl uses it at night. It's too troublesome to lock every time. Is there a simpler way?

Xiaonan and Xiaonv discussed and agreed not to lock the door, but who uses the room and who hangs their schoolbag at the door, but their schoolbag style is the same. Therefore, they have to look through their schoolbag every time before entering the door to see who owns the textbook. If it is their own, they can enter the door, which saves locking and unlocking. In case the schoolbag is not your own, wait outside the door and inform the other party to lock the door next time.

Later, the little girl went back to her hometown and won't use this room for a long time. Xiao Nan hung up his schoolbag and turned it every time. Although it was easier than locking the door, he still felt troublesome.

Therefore, Xiao Nan simply engraved his name on the door: [Xiao Nan's exclusive room, others do not use it]. The next time he comes to use the room, as long as the name is still there, it means that he can use the room safely without being disturbed. If someone else wants to use the room during this period, the user will erase Xiaonan's engraved name and upgrade it to the way of hanging schoolbags.

When the students returned to their hometown during the holiday, Xiao Nan expanded and engraved his name in 20 rooms, which he wanted to enter. Later, he went back to his hometown on his own holiday. At this time, the little girl came back (she also wanted to use these rooms). As a result, he had to erase Xiaonan's engraved names one by one and upgrade to the way of hanging schoolbags. Lao Wang felt that the cost was a little high and proposed a method of mass re engraving names. He asked the little girl not to hang her schoolbag and could directly engrave her name on the door

Later, the phenomenon of engraving names became more and more frequent, and Lao Wang couldn't stand it: forget it, these rooms can't engrave names, so they can only hang schoolbags

II lightweight lock

The usage scenario of lightweight lock is: if an object has multiple threads to lock it, but the locking time is staggered (that is, no one can compete), lightweight lock can be used for optimization.

Lightweight locks are transparent to the user, that is, the syntax is still synchronized

Suppose there are two methods to synchronize blocks and lock 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
     }
}
  1. Each time you point to a synchronized code block, a Lock Record object will be created. Each thread will include a Lock Record structure. The Lock Record can store the object's Mark Word and object reference reference

  1. Let the Object reference in the lock record point to the Object, and try to replace the Mark Word of the Object object with cas(compare and sweep), and store the value of Mark Word in the lock record

  1. If cas replacement is successful, the object header of the object stores the address and status 00 of the lock record, as shown below

  1. If cas fails, there are two situations
    1. If other threads already hold the lightweight lock of the Object, it indicates that there is competition and the lock expansion phase will be entered
    2. If your own thread has executed synchronized locking, add another Lock Record as the count of reentry

  1. When the thread exits the synchronized code block, * * if a null lock record * * is obtained, it indicates that there is a reentry. At this time, the lock record is reset, indicating that the reentry count is reduced by one

    1. When the thread exits the synchronized code block, if the obtained lock admission value is not null, cas is used to restore the value of Mark Word to the object
      1. Successful unlocking
      2. Failure indicates that the lightweight lock has been expanded or upgraded to a heavyweight lock, and enters the heavyweight lock unlocking process

III lock expansion

If the cas operation fails in the process of trying to add a lightweight lock, it is a case that other threads have added a lightweight lock to the object. This is to expand the lock and turn the lightweight lock into a heavyweight lock.

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

  1. At this time, Thread-1 fails to add a lightweight lock and enters the lock expansion process
    1. That is, apply for the Monitor lock for the Object and let the Object point to the heavyweight lock address
    2. Then enter the EntryList of Monitor and change to BLOCKED status

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

IV 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 synchronization at this time)
Block, the lock is released, and the current thread can avoid blocking.

Spin retry success

V bias lock

Biased state

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

For example:

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
    }
}

When an object is created:

  1. If the bias lock is enabled (it is enabled by default), the value of the last three digits of Mark Word is 101 just after the object is created, and its Thread, epoch and age are all 0. Set these values when locking
  2. The bias lock is delayed by default and will not take effect immediately when the program starts. If you want to avoid the delay, you can add a virtual machine parameter to disable the delay: - XX:BiasedLockingStartupDelay=0 to disable the delay
  3. Note: after the object in the bias lock is unlocked, the thread id is still stored in the object header
  4. If the bias lock is not turned on, after the object is created, the markword value is 0x01, that is, the last three bits are 001. At this time, its hashcode and age are all 0. It will be assigned when hashcode is used for the first time
  5. When the above test code runs, disable bias locking when adding VM parameter - XX:-UseBiasedLocking

Undo skew lock - call object hashcode
  1. The hashCode of the object is called, but the thread id is stored in the object MarkWord that biases the lock. If the hashCode is called, the biased lock will be revoked

    1. Lightweight locks record hashCode in the lock record
    2. The heavyweight lock records the hashCode in the Monitor
  2. Test hashCode

    1. The normal state object does not have a hashCode at first, and is generated only after the first call
Unlock - objects used by other threads

When other threads use the biased lock object, the biased lock will be upgraded to a lightweight lock

private static void test2() throws InterruptedException {
    Dog d = new Dog();
    Thread t1 = new Thread(() -> {
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        synchronized (TestBiased.class) {
            TestBiased.class.notify();
        }
        // If you do not use wait/notify to use join, you must open the following comments
        // Because: the t1 thread cannot end, otherwise the underlying thread may be reused by the jvm as a t2 thread, and the underlying thread id is the same
        /*try {
			System.in.read();
		} catch (IOException e) {
			e.printStackTrace();
		}*/
    }, "t1");
    t1.start();
    Thread t2 = new Thread(() -> {
        synchronized (TestBiased.class) {
            try {
                TestBiased.class.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
    }, "t2");
    t2.start();
}

output

[t1] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 01000001 00010000 00000101
[t2] - 00000000 00000000 00000000 00000000 00011111 10110101 11110000 01000000
[t2] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
Undo - call wait/notify
public static void main(String[] args) throws InterruptedException {
    Dog d = new Dog();
    Thread t1 = new Thread(() -> {
        log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        synchronized (d) {
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
            try {
                d.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug(ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t1");
    t1.start();
    new Thread(() -> {
        try {
            Thread.sleep(6000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (d) {
            log.debug("notify");
            d.notify();
        }
    }, "t2").start();
}

output

[t1] - 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000101
[t1] - 00000000 00000000 00000000 00000000 00011111 10110011 11111000 00000101
[t2] - notify
[t1] - 00000000 00000000 00000000 00000000 00011100 11010100 00001101 11001010
Batch re bias

Batch reorientation: when a thread creates a large number of objects and performs the initial synchronization operation, then another thread also operates these objects as lock objects, which will lead to the operation of lock reorientation.

Batch revocation: in the case of fierce multi-threaded competition, the use of bias lock will reduce efficiency, so a batch revocation mechanism is produced.

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

The first 19 will be biased from t1. When synchronized, the biased lock will be cancelled and become a lightweight lock. After synchronized, it will become normal.

When the threshold for revoking biased locks reaches 20 times, the jvm will think like this, am I biased wrong, so it will re bias to the locking thread when locking these objects

After that, the object of this thread will directly change from the bias lock pointing to t1 to the bias lock pointing to t2 when it encounters synchronized

private static void test3() throws InterruptedException {
    Vector<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).toPrintableSimple(true));
            }
        }
        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).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t2");
    t2.start();
}

output

[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
[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 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

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).toPrintableSimple(true));
            }
        }
        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).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
        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).toPrintableSimple(true));
            synchronized (d) {
                log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
            }
            log.debug(i + "\t" + ClassLayout.parseInstance(d).toPrintableSimple(true));
        }
    }, "t3");
    t3.start();
    t3.join();
    log.debug(ClassLayout.parseInstance(new Dog()).toPrintableSimple(true));
}

In the above

  1. The objects of t1 thread are all biased towards t1
  2. In t2 thread
    1. From 0 to 18, the biased lock will be revoked, become a lightweight lock, and then become normal
    2. 19-38 will perform batch redirection (not revocation), biased to t2
  3. In t3 thread
    1. 0-18 because it has been revoked, the initial state is normal. When synchronized, it becomes a lightweight lock, and then it becomes normal
    2. 19-38 will cancel the bias lock and bias t3

Therefore, in the above, 0-18 and 19-38 cancel the bias lock at t2 and t3 respectively, so the new class is not biased at the 40th time, that is, the new object is a lightweight lock

VI lock elimination

Lock elimination

@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 {
        Object o = new Object();
        synchronized (o) {
            x++;
        }
    }
}

Lock elimination occurs by default

You can see that there is little difference in time

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 Java - XX: - eliminatelocks - jar benchmarks jar

This is an optimization of the JIT compiler to eliminate locks that do not escape

3.8 wait notify

3.8.1 why do I need to wait notify

  • Because the conditions are not met, Xiaonan cannot continue the calculation

  • But if Xiaonan keeps occupying the lock, others will have to block all the time. The efficiency is too low

  • So Lao Wang opened a single Lounge (calling the wait method) and asked Xiao Nan to wait in the WaitSet. But at this time, the lock was released and others could enter the room at random

  • Until xiaom sends the cigarette, shout "your cigarette is here" (call the notify method)

  • Xiao Nan can then leave the lounge and re-enter the queue of competing locks

3.8.2 principle

  • 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 after waking up, it does not mean that the Owner obtains the lock immediately. It still needs to enter the EntryList to compete again

3.8.3 API introduction

Calling wait notify expands to heavyweight locks

  • obj.wait() causes the thread entering the object monitor to wait in the waitSet
  • obj.notify() selects one of the threads waiting for waitSet on the object to wake up
  • obj.notifyAll() wakes up all the threads waiting for waitSet on the object

They are all means of cooperation between threads and belong to the methods of Object objects. You must obtain a lock on this Object to call these methods

final static Object obj = new Object();
public static void main(String[] args) {
    new Thread(() -> {
        synchronized (obj) {
            log.debug("implement....");
            try {
                obj.wait(); // Let the thread wait on obj
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("Other codes....");
        }
    }).start();
    new Thread(() -> {
        synchronized (obj) {
            log.debug("implement....");
            try {
                obj.wait(); // Let the thread wait on obj
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("Other codes....");
        }
    }).start();
    // The main thread executes in two seconds
    sleep(2);
    log.debug("awaken obj Other threads on");
    synchronized (obj) {
        obj.notify(); // Wake up obj previous thread
        // obj.notifyAll(); //  Wake up all waiting threads on obj
    }
}

A result of notify

20:00:53.096 [Thread-0] c.TestWaitNotify - implement....
20:00:53.099 [Thread-1] c.TestWaitNotify - implement....
20:00:55.096 [main] c.TestWaitNotify - awaken obj Other threads on

Results of notifyAll

19:58:15.457 [Thread-0] c.TestWaitNotify - implement....
19:58:15.460 [Thread-1] c.TestWaitNotify - implement....
19:58:17.456 [main] c.TestWaitNotify - awaken obj Other threads on
19:58:17.456 [Thread-1] c.TestWaitNotify - Other codes....
19:58:17.456 [Thread-0] c.TestWaitNotify - Other codes....
  • 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
    Until notify
  • wait(long n) is a time limited wait that ends after n milliseconds or is notified

3.8.4 difference between sleep and wait

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

3.8.5 correct use

I

static final Object room = new Object();
static boolean hasCigarette = false;
static boolean hasTakeout = false;

Think about the following solutions. Why?

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").start();

for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        synchronized (room) {
            log.debug("You can start working");
        }
    }, "Other people").start();
}
sleep(1);
new Thread(() -> {
    // Can I add synchronized (room) here?
    hasCigarette = true;
    log.debug("Here comes the smoke!");
}, "Cigarette delivery").start();

output

20:49:49.883 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:49:49.887 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!
20:49:50.882 [Cigarette delivery] c.TestCorrectPosture - Here comes the smoke!
20:49:51.887 [Xiaonan] c.TestCorrectPosture - Any smoke?[true]
20:49:51.887 [Xiaonan] c.TestCorrectPosture - You can start working
20:49:51.887 [Other people] c.TestCorrectPosture - You can start working
20:49:51.887 [Other people] c.TestCorrectPosture - You can start working
20:49:51.888 [Other people] c.TestCorrectPosture - You can start working
20:49:51.888 [Other people] c.TestCorrectPosture - You can start working
20:49:51.888 [Other people] c.TestCorrectPosture - You can start working
  • Other working threads have to be blocked all the time, which is too inefficient
  • 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 adding synchronized (room), it's like Xiao Nan sleeping inside locking the door, and the smoke can't be sent in at all. If main doesn't add synchronized, it's like the main thread comes in through the window
  • The solution is to use the wait - notify mechanism

II

Think about the following implementation, okay? Why?

new Thread(() -> {
    synchronized (room) {
        log.debug("Any smoke?[{}]", hasCigarette);
        if (!hasCigarette) {
            log.debug("No smoke, take a break!");
            try {
                room.wait(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        log.debug("Any smoke?[{}]", hasCigarette);
        if (hasCigarette) {
            log.debug("You can start working");
        }
    }
}, "Xiaonan").start();
for (int i = 0; i < 5; i++) {
    new Thread(() -> {
        synchronized (room) {
            log.debug("You can start working");
        }
    }, "Other people").start();
}
sleep(1);
new Thread(() -> {
    synchronized (room) {
        hasCigarette = true;
        log.debug("Here comes the smoke!");
        room.notify();
    }
}, "Cigarette delivery").start();

output

20:51:42.489 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:51:42.493 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!
20:51:42.493 [Other people] c.TestCorrectPosture - You can start working
20:51:42.493 [Other people] c.TestCorrectPosture - You can start working
20:51:42.494 [Other people] c.TestCorrectPosture - You can start working
20:51:42.494 [Other people] c.TestCorrectPosture - You can start working
20:51:42.494 [Other people] c.TestCorrectPosture - You can start working
20:51:43.490 [Cigarette delivery] c.TestCorrectPosture - Here comes the smoke!
20:51:43.490 [Xiaonan] c.TestCorrectPosture - Any smoke?[true]
20:51:43.490 [Xiaonan] c.TestCorrectPosture - You can start working
  • It solves the problem of thread blocking of other working threads
  • But what if there are other threads waiting for conditions?

III

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!");
        room.notify();
    }
}, "Delivery").start();

output

20:53:12.173 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:53:12.176 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!
20:53:12.176 [my daughter] c.TestCorrectPosture - Did you deliver the takeout?[false]
20:53:12.176 [my daughter] c.TestCorrectPosture - No takeout, take a break!
20:53:13.174 [Delivery] c.TestCorrectPosture - Here's the takeout!
20:53:13.174 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:53:13.174 [Xiaonan] c.TestCorrectPosture - No dry survival...
  • 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

IV

new Thread(() -> {
    synchronized (room) {
        hasTakeout = true;
        log.debug("Here's the takeout!");
        room.notifyAll();
    }
}, "Delivery").start();

output

20:55:23.978 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:55:23.982 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!
20:55:23.982 [my daughter] c.TestCorrectPosture - Did you deliver the takeout?[false]
20:55:23.982 [my daughter] c.TestCorrectPosture - No takeout, take a break!
20:55:24.979 [Delivery] c.TestCorrectPosture - Here's the takeout!
20:55:24.979 [my daughter] c.TestCorrectPosture - Did you deliver the takeout?[true]
20:55:24.980 [my daughter] c.TestCorrectPosture - You can start working
20:55:24.980 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:55:24.980 [Xiaonan] c.TestCorrectPosture - No dry survival...
  • notifyAll only solves the wake-up problem of a thread, but there is only one chance to judge with if + wait. Once the condition is not tenable, there is no chance to judge again
  • The solution is to use while + wait. When the condition is not tenable, wait again

V

if (!hasCigarette) {
    log.debug("No smoke, take a break!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

After modification

while (!hasCigarette) {
    log.debug("No smoke, take a break!");
    try {
        room.wait();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

output

20:58:34.322 [Xiaonan] c.TestCorrectPosture - Any smoke?[false]
20:58:34.326 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!
20:58:34.326 [my daughter] c.TestCorrectPosture - Did you deliver the takeout?[false]
20:58:34.326 [my daughter] c.TestCorrectPosture - No takeout, take a break!
20:58:35.323 [Delivery] c.TestCorrectPosture - Here's the takeout!
20:58:35.324 [my daughter] c.TestCorrectPosture - Did you deliver the takeout?[true]
20:58:35.324 [my daughter] c.TestCorrectPosture - You can start working
20:58:35.324 [Xiaonan] c.TestCorrectPosture - No smoke, take a break!

VI template

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

3.8.6 protective pause of synchronization mode

I definition

That is, Guarded Suspension, which is used to wait for the execution result of another thread

main points

  • 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

II Implementation

class GuardedObject {
    private Object response;
    private final Object lock = new Object();
    public Object get() {
        synchronized (lock) {
            // Wait if conditions are not met
            while (response == null) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            return response;
        }
    }
    public void complete(Object response) {
        synchronized (lock) {
            // If the condition is met, notify the waiting thread
            this.response = response;
            lock.notifyAll();
        }
    }
}

III Application

One thread waits for the execution result of another thread

public static void main(String[] args) {
    GuardedObject guardedObject = new GuardedObject();
    new Thread(() -> {
        try {
            // The child thread performs the download
            List<String> response = download();
            log.debug("download complete...");
            guardedObject.complete(response);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }).start();
    log.debug("waiting...");
    // Main thread blocking wait
    Object response = guardedObject.get();
    log.debug("get response: [{}] lines", ((List<String>) response).size());
}

results of enforcement

08:42:18.568 [main] c.TestGuardedObject - waiting...
08:42:23.312 [Thread-0] c.TestGuardedObject - download complete...
08:42:23.312 [main] c.TestGuardedObject - get response: [3] lines

IV GuardedObject with timeout version

What if you want to control the timeout

class GuardedObjectV2 {
    private Object response;
    private final Object lock = new Object();
    public Object get(long millis) {
        synchronized (lock) {
            // 1) Record initial time
            long begin = System.currentTimeMillis();
            // 2) Time that has passed
            long timePassed = 0;
            while (response == null) {
                // 4) Assuming that the millis is 1000, and the result wakes up at 400, there is still 600 to wait
                long waitTime = millis - timePassed;
                log.debug("waitTime: {}", waitTime);
                if (waitTime <= 0) {
                    log.debug("break...");
                    break;
                }
                try {
                    lock.wait(waitTime);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 3) If you are awakened in advance, the elapsed time is assumed to be 400
                timePassed = System.currentTimeMillis() - begin;
                log.debug("timePassed: {}, object is null {}",
                          timePassed, response == null);
            }
            return response;
        }
    }
    public void complete(Object response) {
        synchronized (lock) {
            // If the condition is met, notify the waiting thread
            this.response = response;
            log.debug("notify...");
            lock.notifyAll();
        }
    }
}

Test, no timeout

public static void main(String[] args) {
    GuardedObjectV2 v2 = new GuardedObjectV2();
    new Thread(() -> {
        sleep(1);
        v2.complete(null);
        sleep(1);
        v2.complete(Arrays.asList("a", "b", "c"));
    }).start();
    Object response = v2.get(2500);
    if (response != null) {
        log.debug("get response: [{}] lines", ((List<String>) response).size());
    } else {
        log.debug("can't get response");
    }
}
08:49:39.917 [main] c.GuardedObjectV2 - waitTime: 2500
08:49:40.917 [Thread-0] c.GuardedObjectV2 - notify...
08:49:40.917 [main] c.GuardedObjectV2 - timePassed: 1003, object is null true
08:49:40.917 [main] c.GuardedObjectV2 - waitTime: 1497
08:49:41.918 [Thread-0] c.GuardedObjectV2 - notify...
08:49:41.918 [main] c.GuardedObjectV2 - timePassed: 2004, object is null false
08:49:41.918 [main] c.TestGuardedObjectV2 - get response: [3] lines

Test, timeout

// Insufficient waiting time
List<String> lines = v2.get(1500);

output

08:47:54.963 [main] c.GuardedObjectV2 - waitTime: 1500
08:47:55.963 [Thread-0] c.GuardedObjectV2 - notify...
08:47:55.963 [main] c.GuardedObjectV2 - timePassed: 1002, object is null true
08:47:55.963 [main] c.GuardedObjectV2 - waitTime: 498
08:47:56.461 [main] c.GuardedObjectV2 - timePassed: 1500, object is null true
08:47:56.461 [main] c.GuardedObjectV2 - waitTime: 0
08:47:56.461 [main] c.GuardedObjectV2 - break...
08:47:56.461 [main] c.TestGuardedObjectV2 - can't get response
08:47:56.963 [Thread-0] c.GuardedObjectV2 - notify...

V join principle

The bottom layer of join is the application of the principle of protective pause

public final synchronized void join(long millis)
    throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

VI multi tasking GuardedObject

In the picture, 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, and t1, t3 and t5 on the right are like postmen

If you need to use the GuardedObject object object between multiple classes, it is not very convenient to pass it as a parameter. Therefore, an intermediate class is designed to decouple, which can not only decouple the result wait and result producer, but also support the management of multiple tasks at the same time

The new id is used to identify the Guarded Object

class GuardedObject {
    // Identifies the Guarded Object
    private int id;
    public GuardedObject(int id) {
        this.id = id;
    }
    public int getId() {
        return id;
    }
    // result
    private Object response;
    // Get results
    // timeout indicates how long to wait
    public Object get(long timeout) {
        synchronized (this) {
            // Start time: 15:00:00
            long begin = System.currentTimeMillis();
            // Time experienced
            long passedTime = 0;
            while (response == null) {
                // How long should this cycle wait
                long waitTime = timeout - passedTime;
                // Exit the loop when the elapsed time exceeds the maximum waiting time
                if (timeout - passedTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime); // False wake-up 15:00:01
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // Find the experience time
                passedTime = System.currentTimeMillis() - begin; // 15:00:02 1s
            }
            return response;
        }
    }
    // Produce results
    public void complete(Object response) {
        synchronized (this) {
            // Assign a value to the result member variable
            this.response = response;
            this.notifyAll();
        }
    }
}

Intermediate decoupling class

class Mailboxes {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();
    private static int id = 1;
    // Generate unique id
    private static synchronized int generateId() {
        return id++;
    }
    public static GuardedObject getGuardedObject(int id) {
        return boxes.remove(id);
    }
    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }
    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

Business related

class People extends Thread{
    @Override
    public void run() {
        // Receive a letter
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.debug("Start receiving letters id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.debug("Received letter id:{}, content:{}", guardedObject.getId(), mail);
    }
}
class Postman extends Thread {
    private int id;
    private String mail;
    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }
    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
        log.debug("Deliver a letter id:{}, content:{}", id, mail);
        guardedObject.complete(mail);
    }
}

test

public static void main(String[] args) throws InterruptedException {
    for (int i = 0; i < 3; i++) {
        new People().start();
    }
    Sleeper.sleep(1);
    for (Integer id : Mailboxes.getIds()) {
        new Postman(id, "content" + id).start();
    }
}

Result of a run

10:35:05.689 c.People [Thread-1] - Start receiving letters id:3
10:35:05.689 c.People [Thread-2] - Start receiving letters id:1
10:35:05.689 c.People [Thread-0] - Start receiving letters id:2
10:35:06.688 c.Postman [Thread-4] - Deliver a letter id:2, content:Content 2
10:35:06.688 c.Postman [Thread-5] - Deliver a letter id:1, content:Content 1
10:35:06.688 c.People [Thread-0] - Received letter id:2, content:Content 2
10:35:06.688 c.People [Thread-2] - Received letter id:1, content:Content 1
10:35:06.688 c.Postman [Thread-3] - Deliver a letter id:3, content:Content 3
10:35:06.689 c.People [Thread-1] - Received letter id:3, content:Content 3

3.8.7 producer / consumer of asynchronous mode

I definition

main points

  • 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

II Implementation

class Message {
    private int id;
    private Object message;
    public Message(int id, Object message) {
        this.id = id;
        this.message = message;
    }
    public int getId() {
        return id;
    }
    public Object getMessage() {
        return message;
    }
}
class MessageQueue {
    private LinkedList<Message> queue;
    private int capacity;
    public MessageQueue(int capacity) {
        this.capacity = capacity;
        queue = new LinkedList<>();
    }
    public Message take() {
        synchronized (queue) {
            while (queue.isEmpty()) {
                log.debug("It's out of stock, wait");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            Message message = queue.removeFirst();
            queue.notifyAll();
            return message;
        }
    }
    public void put(Message message) {
        synchronized (queue) {
            while (queue.size() == capacity) {
                log.debug("Inventory has reached the upper limit, wait");
                try {
                    queue.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            queue.addLast(message);
            queue.notifyAll();
        }
    }
}

quote

MessageQueue messageQueue = new MessageQueue(2);
// 4 producer threads, download tasks
for (int i = 0; i < 4; i++) {
    int id = i;
    new Thread(() -> {
        try {
            log.debug("download...");
            List<String> response = Downloader.download();
            log.debug("try put message({})", id);
            messageQueue.put(new Message(id, response));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }, "producer" + i).start();
}
// 1 consumer thread, processing results
new Thread(() -> {
    while (true) {
        Message message = messageQueue.take();
        List<String> response = (List<String>) message.getMessage();
        log.debug("take message({}): [{}] lines", message.getId(), response.size());
    }
}, "consumer").start();

Result of a run

10:48:38.070 [Producer 3] c.TestProducerConsumer - download...
10:48:38.070 [Producer 0] c.TestProducerConsumer - download...
10:48:38.070 [consumer] c.MessageQueue - It's out of stock, wait
10:48:38.070 [Producer 1] c.TestProducerConsumer - download...
10:48:38.070 [Producer 2] c.TestProducerConsumer - download...
10:48:41.236 [Producer 1] c.TestProducerConsumer - try put message(1)
10:48:41.237 [Producer 2] c.TestProducerConsumer - try put message(2)
10:48:41.236 [Producer 0] c.TestProducerConsumer - try put message(0)
10:48:41.237 [Producer 3] c.TestProducerConsumer - try put message(3)
10:48:41.239 [Producer 2] c.MessageQueue - Inventory has reached the upper limit, wait
10:48:41.240 [Producer 1] c.MessageQueue - Inventory has reached the upper limit, wait
10:48:41.240 [consumer] c.TestProducerConsumer - take message(0): [3] lines
10:48:41.240 [Producer 2] c.MessageQueue - Inventory has reached the upper limit, wait
10:48:41.240 [consumer] c.TestProducerConsumer - take message(3): [3] lines
10:48:41.240 [consumer] c.TestProducerConsumer - take message(1): [3] lines
10:48:41.240 [consumer] c.TestProducerConsumer - take message(2): [3] lines
10:48:41.240 [consumer] c.MessageQueue - It's out of stock, wait

3.9 Park & UnPark

3.9.1 basic use

They are methods in the LockSupport class

// Pauses the current thread
LockSupport.park();
// Resume a thread
LockSupport.unpark(Pause thread object)
  1. park before unpark
Thread t1 = new Thread(() -> {
    log.debug("start...");
    sleep(1);
    log.debug("park...");
    LockSupport.park();
    log.debug("resume...");
},"t1");
t1.start();
sleep(2);
log.debug("unpark...");
LockSupport.unpark(t1);

output

18:42:52.585 c.TestParkUnpark [t1] - start...
18:42:53.589 c.TestParkUnpark [t1] - park...
18:42:54.583 c.TestParkUnpark [main] - unpark...
18:42:54.583 c.TestParkUnpark [t1] - resume...
  1. unpark first and then park
Thread t1 = new Thread(() -> {
    log.debug("start...");
    sleep(2);
    log.debug("park...");
    LockSupport.park();
    log.debug("resume...");
}, "t1");
t1.start();
sleep(1);
log.debug("unpark...");
LockSupport.unpark(t1);

output

18:43:50.765 c.TestParkUnpark [t1] - start...
18:43:51.764 c.TestParkUnpark [main] - unpark...
18:43:52.769 c.TestParkUnpark [t1] - park...
18:43:52.769 c.TestParkUnpark [t1] - resume...

3.9.2 features

Compared with wait & notify of Object

- wait,notify and notifyAll Must cooperate Object Monitor Used together, and park,unpark No
- park & unpark It is used to block and wake up threads in threads, and notify Only one waiting thread can be awakened randomly, notifyAll It is not so accurate to wake up all waiting threads
- park & unpark Can first unpark,and wait & notify Not first notify

3.9.3 principle

Each thread has its own Parker object, which is composed of three parts_ counter, _ cond and_ mutex

  • To make an analogy, thread is like a traveler, Parker is like his backpack, conditional variable_ cond is like a tent in a backpack_ counter is like the spare dry food in the backpack (0 is exhausted and 1 is sufficient)

  • Call park to see if you need to stop and rest

    • If the spare dry food runs out, get into the tent and rest
    • If there is enough spare dry food, don't stop and move on
  • Calling unpark is like making enough dry food

    • If the thread is still in the tent, wake up and let him move on
    • If the thread is still running at this time, the next time he calls park, he will only consume the spare dry food without staying and moving on
    • Because the backpack space is limited, calling unpark multiple times will only supplement one spare dry food
  • park

Disables the current thread for thread scheduling purposes unless the permit is available.
If the permit is available then it is consumed and the call returns immediately;

otherwise the current thread becomes disabled for thread scheduling purposes and lies dormant until one of three things happens:

  • Some other thread invokes unpark with the current thread as the target; or
  • Some other thread interrupts the current thread; or
  • The call spuriously (that is, for no reason) returns.
public static void park(Object blocker) {
    Thread t = Thread.currentThread();
    setBlocker(t, blocker);
    UNSAFE.park(false, 0L);
    setBlocker(t, null);
}
  • unpark

Makes available the permit for the given thread, if it was not already available.

If the thread was blocked on park then it will unblock.

Otherwise, its next call to park is guaranteed not to block.

This operation is not guaranteed to have any effect at all if the given thread has not been started.
Params:
thread – the thread to unpark, or null, in which case this operation has no effect

I the process of calling park first and then upark

  • Call park
    • The current thread calls unsafe Park() method
    • Check_ counter, this case is 0. In this case, the_ Mutex mutex (mutex object has a waiting queue _cond)
    • Thread entry_ cond conditional variable blocking
    • Set_ counter = 0 (no dry food)

  • Call unpark
    • Call unsafe Unpark (thread_0) method, setting_ counter is 1
    • Awaken_ Thread in cond condition variable_ 0
    • Thread_0 resume operation
    • Set_ counter is 0

II process of calling upark first and then park

  • Call unsafe Unpark (thread_0) method, setting_ counter is 1
  • The current thread calls unsafe Park() method
  • Check_ counter, this case is 1. At this time, the thread does not need to be blocked and continues to run
  • Set_ counter is 0

3.10 thread state transition

Suppose there is a thread Thread t

  1. NEW <–> RUNNABLE
  • t. When using the start() method, new -- > runnable
  1. RUNNABLE <–> WAITING

After the thread obtains the object lock with synchronized(obj)

  • Call obj When using the wait () method, the t thread enters the waitSet from runnable -- > waiting
  • Call obj notify(),obj. When notifyall(), t.interrupt(), the awakened threads go to the entrySet blocking queue and become BLOCKED. When blocking the queue, they compete with other threads for locks
    • The contention lock succeeds, and the t thread starts running -- > from waiting
    • Contention lock failed, t thread from waiting -- > blocked
public class TestWaitNotify {
    final static Object obj = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (obj) {
                log.debug("implement....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("Other codes...."); // breakpoint
            }
        },"t1").start();
        new Thread(() -> {
            synchronized (obj) {
                log.debug("implement....");
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("Other codes...."); // breakpoint
            }
        },"t2").start();
        sleep(0.5);
        log.debug("awaken obj Other threads on");
        synchronized (obj) {
            obj.notifyAll(); // Wake up all waiting thread breakpoints on obj
        }
    }
}
  1. RUNNABLE <–> WAITING
  • When the current thread calls the t.join() method, the current thread starts from runnable -- > waiting. Note that the current thread waits 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
  1. RUNNABLE <–> 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

  1. RUNNABLE <–> TIMED_ Waiting (wait with timeout)

After the t 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.notifyAll(), t.interrupt(); All the awakened threads go to the entrySet blocking queue and become BLOCKED. In the blocking queue, they compete with other threads for locks
    • Contention lock successful, t thread from timed_ WAITING --> RUNNABLE
    • Contention lock failed, t thread from timed_ WAITING --> BLOCKED
  1. RUNNABLE <–> TIMED_WAITING
  • When the current thread calls the t.join(long n) method, the current thread starts from runnable -- > timed_ Waiting notice that the current thread is waiting in the waitSet 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
  1. 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 exceeds n milliseconds or the interrupt() of the thread is called. The current thread starts from timed_ WAITING --> RUNNABLE
  1. 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
  1. RUNNABLE <–> BLOCKED
  • When a t thread obtains an object lock with synchronized(obj), if the contention fails, from RUNNABLE – > BLOCKED, the synchronization code block of the thread holding the obj lock will be executed, and all BLOCKED threads on the object will be awakened to compete again. If the contention of T threads is successful, from BLOCKED – > RUNNABLE, other failed threads will still be BLOCKED
  1. RUNNABLE <–> TERMINATED
  • After all the codes of the current thread have been run, enter TERMINATED

3.11 activity

  • For some reason, the code can not be executed completely. This phenomenon is called activity
  • A series of problems related to activity can be solved with ReentrantLock.

3.11.1 multiple locks

  • Little story

  • A big room has two functions: sleep and study.

  • Now Xiao Nan has to study and the little girl has to sleep, but if only one room (one object lock) is used, then

    Low concurrency
    
    • After Xiaonan got the lock, the little girl couldn't come in to sleep until she finished her study.
  • The solution is to prepare multiple rooms (multiple object locks)

@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> bigRoom.sleep(), "Xiaonan").start();
        new Thread(() -> bigRoom.study(), "my daughter").start();
    }
}

@Slf4j(topic = "guizy.BigRoom")
class BigRoom {
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 hour");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (this) {
            log.debug("study 1 hour");
            Sleeper.sleep(1);
        }
    }
}

// It is equivalent to serial execution. Because the lock object is the whole house, the concurrency is very low
12:16:15.952 guizy.BigRoom [Xiaonan] - sleeping 2 hour
12:16:17.954 guizy.BigRoom [my daughter] - study 1 hour
  • The improvement allows Xiaonan and Xiaonv to obtain different locks
@Slf4j(topic = "guizy.BigRoomTest")
public class BigRoomTest {
    private static final BigRoom sleepRoom = new BigRoom();
    private static final BigRoom studyRoom = new BigRoom();

    public static void main(String[] args) {
    	// Different object calls
        new Thread(() -> sleepRoom.sleep(), "Xiaonan").start();
        new Thread(() -> studyRoom.study(), "my daughter").start();
    }
}
// Because different lock objects are used
12:18:50.580 guizy.BigRoom [my daughter] - study 1 hour
12:18:50.580 guizy.BigRoom [Xiaonan] - sleeping 2 hour
  • 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

3.11.2 deadlock

I example

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

Thread t1 obtains the lock of object A, and then wants to obtain the lock of object B. thread t2 obtains the lock of object B, and then wants to obtain the lock of object A They all wait for the object to release the lock, which is called A deadlock

Example:

Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
    synchronized (A) {
        log.debug("lock A");
        sleep(1);
        synchronized (B) {
            log.debug("lock B");
            log.debug("operation...");
        }
    }
}, "t1");
Thread t2 = new Thread(() -> {
    synchronized (B) {
        log.debug("lock B");
        sleep(0.5);
        synchronized (A) {
            log.debug("lock A");
            log.debug("operation...");
        }
    }
}, "t2");
t1.start();
t2.start();

result

12:22:06.962 [t2] c.TestDeadLock - lock B
12:22:06.962 [t1] c.TestDeadLock - lock A

II necessary conditions for deadlock

  • mutual exclusion
    • A resource can only be used by one process for a period of time
  • Request and hold conditions
    • The process already has at least one resource and requests other resources at the same time. Because other resources are used by other processes, the process enters a blocking state and does not release its existing resources
  • Non preemptive condition
    • The resources obtained by the process cannot be forcibly occupied until they are used. They can only be released after the process is used
  • Cycle waiting condition
    • When a life and death lock occurs, there must be a process - the circular chain of resources.

III positioning deadlock

Method 1: JPS + JStack process ID

  • jps finds the JVM process first
  • jstack process ID
    • Enter the jps instruction in Terminal in the Java console to view the running process ID, and use the jstack process ID to view the process status.

Mode 2: jconsole detects deadlock

IV deadlock example - philosopher dining problem

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

When each philosopher thread holds a chopstick, they are waiting for another thread to release the lock, resulting in a deadlock.

/**
 * Description: Using synchronized locks leads to problems and deadlocks: the core reason is that synchronized locks are non interruptible and enter the blocking queue
 *                  You need to wait for another thread to release the lock
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.PhilosopherEat")
public class PhilosopherEat {
    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();
    }
}

@Slf4j(topic = "guizy.Philosopher")
class Philosopher extends Thread {
    final Chopstick left;
    final Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // Try to get the left chopsticks
            synchronized (left) {
                // Try to get the right chopsticks
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}

class Chopstick{
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

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

Methods to avoid deadlock
  • When a thread uses a lock object, the lock sequence is fixed. You can use the size of the Hash value to determine the lock sequence
  • Reduce the scope of locking as much as possible and wait until the shared variable is operated
  • Use a releasable timing lock (if you can't apply for lock permission for a period of time, you can release it directly)

3.11.3 movable lock

  • A livelock occurs when two threads change each other's end conditions, and no one can end.
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();
    public static void main(String[] args) {
        new Thread(() -> {
            // Expect to decrease to 0 and exit the loop
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // Expect more than 20 to exit the cycle
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

I. Methods to avoid loose locks

During the execution of a thread, different intervals are given in the middle of the thread to let a thread end first.

II difference between deadlock and livelock

Deadlock is the phenomenon that threads hold the desired locks of objects and do not release them. Finally, the threads block and stop running.
Livelock is the phenomenon that the code is running all the time but can't finish running because threads modify each other's end conditions.

3.11.4 hunger

  • Some threads are unable to obtain resources because their priority is too low.
  • Hunger may occur when sequential locking is used

3.12 ReentrantLock

3.12.1 features and grammar

Features of ReentrantLock * ***

  • Support lock reentry

    • Reentrant lock 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
  • Interruptible

    • lock. Lockinterrupt(): an interrupt lock that can be interrupted by other threads
  • You can set the timeout

    • lock. Trylock (time): try to obtain the lock object. If the set time is exceeded and the lock has not been obtained, exit the blocking queue and release the lock you own
  • Can be set to fair lock

    • (first come, first served) the default is unfair, and true is fair. new ReentrantLock(true)
  • Support multiple conditional variables (multiple waitsets)

    • (false wakeups can be avoided) - lock Newcondition() creates a condition variable object; Call the await/signal method through the condition variable object to wait / wake up

Basic grammar

//Get ReentrantLock object
private ReentrantLock lock = new ReentrantLock();
//Lock
lock.lock();
try {
	//Code to execute
}finally {
	//Release lock
	lock.unlock();
}

3.12.2 support lock reentry

  • Reentrant lock 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
/**
 * Description: ReentrantLock Reentrant lock. The same thread can obtain the lock object multiple times
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
    	// If there is competition, enter the "blocking queue" and wait all the time without being interrupted
        lock.lock();
        try {
            log.debug("entry main...");
            m1();
        } finally {
            lock.unlock();
        }
    }

    private static void m1() {
        lock.lock();
        try {
            log.debug("entry m1...");
            m2();
        } finally {
            lock.unlock();
        }
    }

    private static void m2() {
        log.debug("entry m2....");
    }
}

3.12.3 interruptible

synchronized and reentrantlock The lock of lock() cannot be broken; In other words, other threads have obtained locks, and my thread needs to wait all the time Cannot interrupt

  • Interruptible lock through lock The lock object obtained by lockinterrupt() can be blocked by calling the interrupt() method of the thread
  • If a thread is blocked, you can call its interrupt method to stop blocking
  • Interruptible locks can passively reduce the probability of deadlock to a certain extent. The reason for passivity is that we need to manually call the interrupt method of blocking threads;

The test uses lock Lockinterrupt() can interrupt from the blocking queue

/**
 * Description: ReentrantLock, Demonstrate the breakable lock method lock in RenntrantLock lockInterruptibly();
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            log.debug("t1 Thread start...");
            try {
                // Lockinterruptible () is a breakable lock. If there is lock competition, it can be interrupted through interrupt after entering the blocking queue
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("The process of waiting for the lock is interrupted"); //The exception of being interrupted and running out without obtaining the lock
                return;
            }
            try {
                log.debug("t1 The thread acquired the lock");
            } finally {
                lock.unlock();
            }
        }, "t1");

        // The main thread obtains a lock (this lock cannot be broken)
        lock.lock();
        log.debug("main The thread acquired the lock");
        // Start t1 thread
        t1.start();
        try {
            Sleeper.sleep(1);
            t1.interrupt();            //Interrupt t1 thread
            log.debug("Execution interrupt");
        } finally {
            lock.unlock();
        }
    }
}

14:18:09.145 guizy.ReentrantTest [main] - main The thread acquired the lock
14:18:09.148 guizy.ReentrantTest [t1] - t1 Thread start...
14:18:10.149 guizy.ReentrantTest [main] - Execution interrupt
14:18:10.149 guizy.ReentrantTest [t1] - The process of waiting for the lock is interrupted
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.guizy.reentrantlock.ReentrantTest.lambda$main$0(ReentrantTest.java:25)
	at java.lang.Thread.run(Thread.java:748)

The test uses lock Lock() cannot be interrupted from the blocking queue, waiting for another thread to release the lock

@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {

        Thread t1 = new Thread(() -> {
            log.debug("t1 Thread start...");
                lock.lock();
            try {
                log.debug("t1 The thread acquired the lock");
            } finally {
                lock.unlock();
            }
        }, "t1");

        // The main thread obtains a lock (this lock cannot be broken)
        lock.lock();
        log.debug("main The thread acquired the lock");
        // Start t1 thread
        t1.start();
        try {
            Sleeper.sleep(4);
            t1.interrupt();            //Interrupt t1 thread
            log.debug("main Thread execution interrupt");
        } finally {
            lock.unlock();
        }
    }
}

  • The lock() lock can not be interrupted, calling t1. in the main thread. Interrupt() is useless. After the main thread releases the lock, T1 obtains the lock
14:21:01.329 guizy.ReentrantTest [main] - main The thread acquired the lock
14:21:01.331 guizy.ReentrantTest [t1] - t1 Thread start...
14:21:05.333 guizy.ReentrantTest [main] - main Thread execution interrupt
14:21:05.333 guizy.ReentrantTest [t1] - t1 The thread acquired the lock

3.12.4 lock timeout

I concept

Lock timeout (lock.tryLock()) directly exits the blocking queue, and obtaining the lock fails

Prevent unlimited waiting and reduce deadlocks

  • Use lock The trylock() method returns whether the lock acquisition was successful. If successful, it returns true; otherwise, it returns false.
  • And the tryLock method can set the specified waiting time. The parameters are: tryLock(long timeout, TimeUnit unit), where timeout is the longest waiting time and TimeUnit is the time unit

In the process of obtaining the lock, if the waiting time is exceeded or interrupted, it will be directly removed from the blocking queue. At this time, obtaining the lock will fail and will not be blocked all the time! (can be used to implement deadlock problems)

  • Do not set the waiting time, fail immediately
/**
 * Description: ReentrantLock, Demonstrate tryLock() in RenntrantLock. Obtaining a lock immediately fails
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("Attempt to acquire lock");
            // At this point, the acquisition must fail because the main thread has obtained the lock object
            if (!lock.tryLock()) {
                log.debug("Get immediate failure, return");
                return;
            }
            try {
                log.debug("Get lock");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("Get lock");
        t1.start();
        // The main thread does not release the lock until 2s later
        Sleeper.sleep(2);
        log.debug("The lock was released");
        lock.unlock();
    }
}

14:52:19.726 guizy.WaitNotifyTest [main] - Get lock
14:52:19.728 guizy.WaitNotifyTest [t1] - Attempt to acquire lock
14:52:19.728 guizy.WaitNotifyTest [t1] - Get immediate failure, return
14:52:21.728 guizy.WaitNotifyTest [main] - The lock was released

  • Set the waiting time. If the waiting time is exceeded and the lock is not obtained, it fails. Remove the thread from the blocking queue
/**
 * Description: ReentrantLock, Demonstrate the tryLock(long mills) in RenntrantLock. If the waiting time of the lock setting is exceeded, it will be removed from the blocking queue
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.ReentrantTest")
public class ReentrantTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("Attempt to acquire lock");
            try {
                // Set the waiting time. If the waiting time is exceeded / interrupted, the lock acquisition will fail; Exit blocking queue
                if (!lock.tryLock(1, TimeUnit.SECONDS)) {
                    log.debug("Lock acquisition timeout, return");
                    return;
                }
            } catch (InterruptedException e) {
                log.debug("Interrupted, Failed to acquire lock, return");
                e.printStackTrace();
                return;
            }
            try {
                log.debug("Get lock");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("Get lock");
        t1.start();
//        t1.interrupt();
        // The main thread does not release the lock until 2s later
        Sleeper.sleep(2);
        log.debug("main The thread released the lock");
        lock.unlock();
    }
}

// Timed out printing
14:55:56.647 guizy.WaitNotifyTest [main] - Get lock
14:55:56.651 guizy.WaitNotifyTest [t1] - Attempt to acquire lock
14:55:57.652 guizy.WaitNotifyTest [t1] - Lock acquisition timeout, return
14:55:58.652 guizy.WaitNotifyTest [main] - main The thread released the lock

// Interrupted printing
14:56:41.258 guizy.WaitNotifyTest [main] - Get lock
14:56:41.260 guizy.WaitNotifyTest [main] - main The thread released the lock
14:56:41.261 guizy.WaitNotifyTest [t1] - Attempt to acquire lock
14:56:41.261 guizy.WaitNotifyTest [t1] - Interrupted, Failed to acquire lock, return
java.lang.InterruptedException

II solving philosopher's problems

lock. Trylock (time): try to obtain the lock object. If the set time is exceeded and the lock has not been obtained, exit the blocking queue and release the lock you own

/**
 * Description: ReentrantLock lock is used. There is a tryLock() method in this class. If the lock object cannot be obtained within the specified time, it will be removed from the blocking queue without waiting.
 *              After obtaining the left-hand chopsticks, try to obtain the right-hand chopsticks. If the chopsticks are occupied by other philosophers and the acquisition fails, put your left-hand chopsticks first,
 *              Let go This avoids the deadlock problem
 *
 * @author guizy1
 * @date 2020/12/23 13:50
 */
@Slf4j(topic = "guizy.PhilosopherEat")
public class PhilosopherEat {
    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();
    }
}

@Slf4j(topic = "guizy.Philosopher")
class Philosopher extends Thread {
    final Chopstick left;
    final Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // Got the left-hand chopsticks (for five philosophers, they must have got the left chopsticks at first)
            if (left.tryLock()) {
                try {
                	// At this time, it is found that its right chopsticks are occupied. Use tryLock(), 
                	// If the attempt fails, it will release its left chopsticks
                    // Critical zone code
                    if (right.tryLock()) { //Try to get the right-hand chopsticks. If the acquisition fails, the left chopsticks will be released
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(0.5);
    }
}

// Inherit ReentrantLock and make the chopsticks class called lock
class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

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

3.12.5 fair lock

I fair lock (new ReentrantLock(true))

  • Fair lock can put competing threads on a first in first out blocking queue
  • As long as the thread holding the lock finishes executing, wake up the next thread in the blocking queue to obtain the lock; At this time, the thread that enters the blocking queue first obtains the lock first

II. synchronized, new ReentrantLock())

  • For an unfair lock, when there is A waiting thread A in the blocking queue, the next thread B tries to see whether the lock object can be obtained If the access is successful, there is no need to enter the blocking queue at this time In this way, subsequent thread B obtains the lock first

Therefore, the difference between fair and unfair: when a thread executes a synchronous code block, whether to go back and try to obtain a lock is unfair. If it will try to obtain a lock, it is fair. If it will not try to obtain a lock, it will directly enter the blocking queue and wait to be awakened

  • What if you don't get into the queue? Just keep trying to get the lock?
    • I have been trying to acquire locks. When upgrading a synchronized lightweight lock to a heavyweight lock, an optimization is called spin lock. It generally consumes resources. The cpu keeps idling and finally fails to acquire locks, so it is not recommended. In jdk6, there is a mechanism for spin locks, such as failure when trying to obtain the lock a specified number of times, and so on

3.12.6 conditional variables

lock.newCondition() creates a condition variable object; Call the await/signal method through the condition variable object to wait / wake up

  • There are also conditional variables in Synchronized, that is, the waitSet waiting set in Monitor. When the conditions are not met, enter waitSet waiting
  • ReentrantLock's conditional variables are more powerful than synchronized in that it supports multiple conditional variables.
    • This is like synchronized, which means that those threads that do not meet the conditions are notified 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 (condition variable) 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
  • The signal method is used to wake up a waiting thread summarized by the condition variable (waiting room)
  • The signalAll method wakes up all threads in the condition variable (Lounge)
static ReentrantLock lock = new ReentrantLock();
static Condition waitCigaretteQueue = lock.newCondition();
static Condition waitbreakfastQueue = lock.newCondition();
static volatile boolean hasCigrette = false;
static volatile boolean hasBreakfast = false;
public static void main(String[] args) {
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasCigrette) {
                try {
                    waitCigaretteQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("Wait until its smoke");
        } finally {
            lock.unlock();
        }
    }).start();
    new Thread(() -> {
        try {
            lock.lock();
            while (!hasBreakfast) {
                try {
                    waitbreakfastQueue.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            log.debug("Wait until it's breakfast");
        } finally {
            lock.unlock();
        }
    }).start();
    sleep(1);
    sendBreakfast();
    sleep(1);
    sendCigarette();
}
private static void sendCigarette() {
    lock.lock();
    try {
        log.debug("Here comes the cigarette");
        hasCigrette = true;
        waitCigaretteQueue.signal();
    } finally {
        lock.unlock();
    }
}
private static void sendBreakfast() {
    lock.lock();
    try {
        log.debug("Breakfast is coming");
        hasBreakfast = true;
        waitbreakfastQueue.signal();
    } finally {
        lock.unlock();
    }
}

output

18:52:27.680 [main] c.TestCondition - Breakfast is coming
18:52:27.682 [Thread-1] c.TestCondition - Wait until it's breakfast
18:52:28.683 [main] c.TestCondition - Here comes the cigarette
18:52:28.683 [Thread-0] c.TestCondition - Wait until its smoke

3.12.7 fixed operation sequence

  • If there are two threads, thread A prints 1 and thread B prints 2
  • Requirements: the program prints 2 first and then 1

I Wait/Notify version implementation

/**
 * Description: Use wait/notify to realize sequential printing 2, 1
 *
 * @author guizy1
 * @date 2020/12/23 16:04
 */
@Slf4j(topic = "guizy.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static final Object lock = new Object();
    // t2 thread release executed
    public static boolean t2Runned = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lock) {
                while (!t2Runned) {
                    try {
                    	// Entering the waitset will release the lock
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lock) {
                log.debug("2");
                t2Runned = true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

II await/signal using ReentrantLock

/**
 * Description: Use await/sinal of ReentrantLock to realize sequential printing 2,1
 *
 * @author guizy1
 * @date 2020/12/23 16:04
 */
@Slf4j(topic = "guizy.SyncPrintWaitTest")
public class SyncPrintWaitTest {

    public static final ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    // t2 thread release executed
    public static boolean t2Runned = false;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                // Critical zone
                while (!t2Runned) {
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                log.debug("1");
            } finally {
                lock.unlock();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                log.debug("2");
                t2Runned = true;
                condition.signal();
            } finally {
                lock.unlock();
            }
        }, "t2");

        t1.start();
        t2.start();
    }
}

III. use park/unpart in LockSupport

/**
 * Description: Use Park and unpark in LockSupport to print 2 and 1 in sequence
 *
 * @author guizy1
 * @date 2020/12/23 16:04
 */
@Slf4j(topic = "guizy.SyncPrintWaitTest")
public class SyncPrintWaitTest {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();

        new Thread(() -> {
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
}

3.12.8 alternating operation sequence

demand

  • Thread 1 outputs a 5 times, thread 2 outputs b 5 times, and thread 3 outputs c 5 times. Now you need to output abcabcabcabcabcabc

I wait/notify version

/**
 * Description: Use wait/notify to print abcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcabcab
 *
 * @author guizy1
 * @date 2020/12/23 17:00
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestWaitNotify {
    public static void main(String[] args) {
        WaitNotify waitNotify = new WaitNotify(1, 5);

        new Thread(() -> {
            waitNotify.print("a", 1, 2);

        }, "a thread ").start();

        new Thread(() -> {
            waitNotify.print("b", 2, 3);

        }, "b thread ").start();

        new Thread(() -> {
            waitNotify.print("c", 3, 1);

        }, "c thread ").start();
    }
}

@Slf4j(topic = "guizy.WaitNotify")
@Data
@AllArgsConstructor
class WaitNotify {

    private int flag;
    
    // Number of cycles
    private int loopNumber;

    /*
        The output content waits for the next tag
        a           1          2
        b           2          3
        c           3          1
     */
    public void print(String str, int waitFlag, int nextFlag) {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this) {
                while (waitFlag != this.flag) {
                    try {
                        this.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.print(str);
                this.flag = nextFlag;
                this.notifyAll();
            }
        }
    }
}

II await/signal version

/**
 * Description: Use await/signal to print ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC
 *
 * @author guizy1
 * @date 2020/12/23 17:10
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestAwaitSignal {
    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a_condition = awaitSignal.newCondition();
        Condition b_condition = awaitSignal.newCondition();
        Condition c_condition = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a", a_condition, b_condition);
        }, "a").start();

        new Thread(() -> {
            awaitSignal.print("b", b_condition, c_condition);
        }, "b").start();

        new Thread(() -> {
            awaitSignal.print("c", c_condition, a_condition);
        }, "c").start();

        Thread.sleep(1000);
        System.out.println("==========start=========");
        awaitSignal.lock();
        try {
            a_condition.signal();  //Wake up thread a first
        } finally {
            awaitSignal.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock {
    private final int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Condition condition, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    condition.await();
                    //System.out.print("i:==="+i);
                    System.out.print(str);
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                unlock();
            }
        }
    }
}

III. park/unpark implementation of locksupport

/**
 * Description: Use park/unpark to realize the alternating printing of abcabcabcabcabc abcabcabcabc by three threads
 *
 * @author guizy1
 * @date 2020/12/23 17:12
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestParkUnpark {
    static Thread a;
    static Thread b;
    static Thread c;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        a = new Thread(() -> {
            parkUnpark.print("a", b);
        }, "a");

        b = new Thread(() -> {
            parkUnpark.print("b", c);
        }, "b");

        c = new Thread(() -> {
            parkUnpark.print("c", a);
        }, "c");

        a.start();
        b.start();
        c.start();

        LockSupport.unpark(a);

    }
}

class ParkUnpark {
    private final int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread);
        }
    }
}

3.13 summary of this chapter

/23 17:00
*/
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestWaitNotify {
public static void main(String[] args) {
WaitNotify waitNotify = new WaitNotify(1, 5);

    new Thread(() -> {
        waitNotify.print("a", 1, 2);

    }, "a thread ").start();

    new Thread(() -> {
        waitNotify.print("b", 2, 3);

    }, "b thread ").start();

    new Thread(() -> {
        waitNotify.print("c", 3, 1);

    }, "c thread ").start();
}

}

@Slf4j(topic = "guizy.WaitNotify")
@Data
@AllArgsConstructor
class WaitNotify {

private int flag;

// Number of cycles
private int loopNumber;

/*
    The output content waits for the next tag
    a           1          2
    b           2          3
    c           3          1
 */
public void print(String str, int waitFlag, int nextFlag) {
    for (int i = 0; i < loopNumber; i++) {
        synchronized (this) {
            while (waitFlag != this.flag) {
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.print(str);
            this.flag = nextFlag;
            this.notifyAll();
        }
    }
}

}

#### II **await/signal version**

```JAVA
/**
 * Description: Use await/signal to print ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC ABC
 *
 * @author guizy1
 * @date 2020/12/23 17:10
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestAwaitSignal {
    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a_condition = awaitSignal.newCondition();
        Condition b_condition = awaitSignal.newCondition();
        Condition c_condition = awaitSignal.newCondition();

        new Thread(() -> {
            awaitSignal.print("a", a_condition, b_condition);
        }, "a").start();

        new Thread(() -> {
            awaitSignal.print("b", b_condition, c_condition);
        }, "b").start();

        new Thread(() -> {
            awaitSignal.print("c", c_condition, a_condition);
        }, "c").start();

        Thread.sleep(1000);
        System.out.println("==========start=========");
        awaitSignal.lock();
        try {
            a_condition.signal();  //Wake up thread a first
        } finally {
            awaitSignal.unlock();
        }
    }
}

class AwaitSignal extends ReentrantLock {
    private final int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Condition condition, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                try {
                    condition.await();
                    //System.out.print("i:==="+i);
                    System.out.print(str);
                    next.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            } finally {
                unlock();
            }
        }
    }
}

III. park/unpark implementation of locksupport

/**
 * Description: Use park/unpark to realize the alternating printing of abcabcabcabcabc abcabcabcabc by three threads
 *
 * @author guizy1
 * @date 2020/12/23 17:12
 */
@Slf4j(topic = "guizy.TestWaitNotify")
public class TestParkUnpark {
    static Thread a;
    static Thread b;
    static Thread c;

    public static void main(String[] args) {
        ParkUnpark parkUnpark = new ParkUnpark(5);

        a = new Thread(() -> {
            parkUnpark.print("a", b);
        }, "a");

        b = new Thread(() -> {
            parkUnpark.print("b", c);
        }, "b");

        c = new Thread(() -> {
            parkUnpark.print("c", a);
        }, "c");

        a.start();
        b.start();
        c.start();

        LockSupport.unpark(a);

    }
}

class ParkUnpark {
    private final int loopNumber;

    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str, Thread nextThread) {
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(nextThread);
        }
    }
}

3.13 summary of this chapter

Keywords: Concurrent Programming synchronized monitor

Added by gottes_tod on Thu, 20 Jan 2022 21:31:27 +0200