2

1 source

  • Source: "Detailed Multithreading and Architecture Design of Java High Concurrency Programming", written by Wang Wenjun
  • Chapters: Chapters 4 and 6

This article is a compilation of notes from two chapters.

2 Overview

This article mainly describes the basic usage of synchronized and ThreadGroup .

3 synchronized

3.1 Introduction

synchronized can prevent thread interference and memory consistency errors, as follows:

  • synchronized provides a locking mechanism that ensures mutually exclusive access to shared variables, thereby preventing data inconsistency
  • synchronized includes monitor enter and monitor exit two JVM instructions, which can guarantee that any thread must obtain data from main memory before monitor enter succeeds at any time, not from the cache. After monitor exit runs successfully, the value of the shared variable must Flush into main memory instead of just in cache
  • synchronized instruction strictly follows the Happens-Beofre rule, and a monitor exit instruction must be preceded by a monitor enter

3.2 Basic usage

The basic usage of synchronized can be used to decorate code blocks or methods, such as:

private final Object MUTEX = new Object();
    
public void sync1(){
    synchronized (MUTEX){
    }
}

public synchronized void sync2(){
}

3.3 Simple Analysis of Bytecode

A simple example is as follows:

public class Main {
    private static final Object MUTEX = new Object();

    public static void main(String[] args) throws InterruptedException {
        final Main m = new Main();
        for (int i = 0; i < 5; i++) {
            new Thread(m::access).start();
        }
    }

    public void access(){
        synchronized (MUTEX){
            try{
                TimeUnit.SECONDS.sleep(20);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}

View the bytecode after compilation:

javap -v -c -s -l Main.class

access() bytecode is intercepted as follows:

stack=3, locals=4, args_size=1
 0: getstatic     #9                  // Field MUTEX:Ljava/lang/Object;  获取MUTEX
 3: dup
 4: astore_1
 5: monitorenter                      // 执行monitor enter指令
 6: getstatic     #10                 // Field java/util/concurrent/TimeUnit.SECONDS:Ljava/util/concurrent/TimeUnit;
 9: ldc2_w        #11                 // long 20l
12: invokevirtual #13                 // Method java/util/concurrent/TimeUnit.sleep:(J)V
15: goto          23                  // 正常退出,跳转到字节码偏移量23的地方
18: astore_2
19: aload_2
20: invokevirtual #15                 // Method java/lang/InterruptedException.printStackTrace:()V
23: aload_1
24: monitorexit                          // monitor exit指令
25: goto          33
28: astore_3
29: aload_1
30: monitorexit
31: aload_3
32: athrow
33: return

The description of monitorenter and monitorexit is as follows:

  • monitorenter : Each object corresponds to a monitor . When a thread tries to acquire the monitor associated with the object, if the counter of monitor is 0, the counter will be incremented by 1 immediately after monitor . Will cause the counter to accumulate again, and if other threads try to acquire, it will block until the counter of monitor becomes 0, and can try to acquire ownership of monitor again
  • monitorexit : release ownership of monitor , decrement the counter of monitor by 1, if the counter is 0, it means that the thread no longer has ownership of monitor

3.4 Notes

3.4.1 Non-null objects

The object associated with monitor cannot be null:

private Object MUTEX = null;
private void sync(){
    synchronized (MUTEX){

    }
}

A null pointer exception will be thrown directly.

3.4.2 Improper scope

Due to the exclusivity of the synchronized keyword, the larger the scope, the lower the efficiency and the loss of concurrency advantages, such as:

private synchronized void sync(){
    method1();
    syncMethod();
    method2();
}

Only the second method is a concurrent operation, then it can be modified as

private Object MUTEX = new Object();
private void sync(){
    method1();
    synchronized (MUTEX){
        syncMethod();
    }
    method2();
}

3.4.3 Using different objects

Because an object is associated with a monitor , if a different object is used, it will lose the meaning of synchronization, for example:

public class Main {
    public static class Task implements Runnable{
        private final Object MUTEX = new Object();

        @Override
        public void run(){
            synchronized (MUTEX){
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 20; i++) {
            new Thread(new Task()).start();
        }
    }
}

The monitor that each thread contends for is independent of each other, which loses the meaning of synchronization and does not play a role in mutual exclusion.

3.5 Deadlock

In addition, the use of synchronized also needs to pay attention to the problem that may cause deadlock. Let's first look at the possible causes of deadlock.

3.5.1 Causes of deadlock

  • Cross locks lead to program deadlock: for example, thread A holds the lock of R1 and waits for the lock of R2, and thread B holds the lock of R2 and waits for the lock of R1.
  • Insufficient memory: For example, two threads T1 and T2, T1 has acquired 10MB of memory, T2 has acquired 15MB of memory, both T1 and T2 need to acquire 30MB of memory to work, but the remaining available memory is 10MB, so both threads are waiting for each other free memory resources
  • Question-and-answer data exchange: The server opens a port and waits for the client to access. After the client sends a request, the server misses the client request for some reason, causing the client to wait for the server to respond, and the server to wait for the client to send ask
  • Deadlock caused by an infinite loop: It is relatively common. You cannot see the deadlock by using tools such as jstack , but the program does not work, and the CPU a high occupation rate. This kind of deadlock is also called system suspended animation, which is difficult to troubleshoot and reproduce.

3.5.2 Examples

public class Main {
    private final Object MUTEX_READ = new Object();
    private final Object MUTEX_WRITE = new Object();

    public void read(){
        synchronized (MUTEX_READ){
            synchronized (MUTEX_WRITE){
            }
        }
    }

    public void write(){
        synchronized (MUTEX_WRITE){
            synchronized (MUTEX_READ){
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();
        new Thread(()->{
            while (true){
                m.read();
            }
        }).start();
        new Thread(()->{
            while (true){
                m.write();
            }
        }).start();
    }
}

Two threads occupy MUTEX_READ / MUTEX_WRITE respectively, while waiting for another thread to release MUTEX_WRITE / MUTEX_READ , which is a deadlock caused by cross-locking.

3.5.3 Troubleshooting

After finding the process with jps , view it through jstack :

在这里插入图片描述

You can see a clear hint that a deadlock has been found, Thread-0 is waiting for monitor , which is occupied by Thread-1 , and Thread-1 is waiting for monitor , which is occupied by Thread-0 .

3.6 Two special monitor

Two special monitor are introduced here:

  • this monitor
  • class monitor

3.6.1 this monitor

First the previous piece of code:

public class Main {
    public synchronized void method1(){
        System.out.println(Thread.currentThread().getName()+" method1");
        try{
            TimeUnit.MINUTES.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public synchronized void method2(){
        System.out.println(Thread.currentThread().getName()+" method2");
        try{
            TimeUnit.MINUTES.sleep(5);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Main m = new Main();
        new Thread(m::method1).start();
        new Thread(m::method2).start();
    }
}

After running, it can be found that there is only one line of output, that is to say, only one of the methods is run, and the other method is not executed at all. Use jstack to find:

在这里插入图片描述

One thread is sleeping while the other thread is blocked. And if method2() modified as follows:

public void method2(){
    synchronized (this) {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

The effect is the same. That is, using synchronized on the method is equivalent to synchronized(this) .

3.6.2 class monitor

Change the method in the above code to a static method:

public class Main {
    public static synchronized void method1() {
        System.out.println(Thread.currentThread().getName() + " method1");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static synchronized void method2() {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(Main::method1).start();
        new Thread(Main::method2).start();
    }
}

After running, you can find that the output is still only one line, that is to say, only one of the methods is run, and the analysis of jstack is similar:

在这里插入图片描述

And if method2() modified as follows:

public static void method2() {
    synchronized (Main.class) {
        System.out.println(Thread.currentThread().getName() + " method2");
        try {
            TimeUnit.MINUTES.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

It can be found that the output is still the same, that is, synchronized on the static method is equivalent to synchronized(XXX.class) .

3.6.3 Summary

  • this monitor : 0622c9dd179a2a on the member method is synchronized , which is equivalent to using this monitor in the synchronized(this)
  • class monitor : 0622c9dd179a68 on a static method is synchronized , which is equivalent to using class monitor in a static synchronized(XXX.class)

4 ThreadGroup

4.1 Introduction

In any case, a newly created thread will be added to a ThreadGroup :

  • If the new thread does not specify ThreadGroup , the default is main where the ThreadGroup thread is located
  • If ThreadGroup is specified, then add the ThreadGroup

There is a parent-child relationship in ThreadGroup , and a ThreadGroup can have a child ThreadGroup .

4.2 Create

Creating ThreadGroup can be created directly through the constructor. There are two constructors, one is to directly specify the name ( ThreadGroup is ThreadGroup of the main thread), and the other is the constructor with the parent ThreadGroup and name:

ThreadGroup group1 = new ThreadGroup("name");
ThreadGroup group2 = new ThreadGroup(group1,"name2");

Complete example:

public static void main(String[] args) throws InterruptedException {
    ThreadGroup group1 = new ThreadGroup("name");
    ThreadGroup group2 = new ThreadGroup(group1,"name2");
    System.out.println(group2.getParent() == group1);
    System.out.println(group1.getParent().getName());
}

Output result:

true
main

4.3 enumerate()

enumerate() can be used for the replication of Thread and ThreadGroup , because one ThreadGroup can join several Thread and several sub ThreadGroup , which can be easily replicated using this method. The method is described as follows:

  • public int enumerate(Thread [] list)
  • public int enumerate(Thread [] list, boolean recurse)
  • public int enumerate(ThreadGroup [] list)
  • public int enumerate(ThreadGroup [] list, boolean recurse)

The above method will copy the active thread/ ThreadGroup in ThreadGroup to the Thread / ThreadGroup array. The boolean parameter indicates whether to enable recursive copying.

Examples are as follows:

public static void main(String[] args) throws InterruptedException {
    ThreadGroup myGroup = new ThreadGroup("MyGroup");
    Thread thread = new Thread(myGroup,()->{
        while (true){
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    },"MyThread");
    thread.start();
    TimeUnit.MILLISECONDS.sleep(1);
    ThreadGroup mainGroup = currentThread().getThreadGroup();
    Thread[] list = new Thread[mainGroup.activeCount()];
    int recurseSize = mainGroup.enumerate(list);
    System.out.println(recurseSize);
    recurseSize = mainGroup.enumerate(list,false);
    System.out.println(recurseSize);
}

The latter output is 1 less than the former because threads in myGroup are not included (recursively set to false ). It should be noted that the thread obtained by enumerate() is only an estimated value, and cannot guarantee 100% the active thread of group . Data is inaccurate. In addition, the returned value of int is more realistic than the length of Thread[] , because enumerate only puts the currently active threads into the array, and the returned value of int represents the actual number rather than the length of the array.

4.4 Other API

  • activeCount() : Get active threads in group , estimated
  • activeGroupCount() : Get the active sub- group in group , which is also an approximation, it will recursively acquire all sub- group
  • getMaxPriority() : used to get the priority of group . By default, the priority of group is 10, and the priority of all threads must not be greater than the priority of group where the thread is located.
  • getName() : get group name
  • getParent() : get the parent group , if it does not exist, return null
  • list() : an output method that recursively outputs all active thread information to the console
  • parentOf(ThreadGroup g) : Determine whether the current group is the parent group of the given group , if the given group is itself, it will also return true
  • setMaxPriority(int pri) : Specify the maximum priority of group . After setting, it will also change the maximum priority of all sub- group . In addition, after the priority is modified, the thread priority will be greater than the priority of group . For example, if the thread priority is 10, set group After the priority is 5, the thread priority is greater than group priority, but the newly added thread priority must not be greater than group priority
  • interrupt() : causes all active threads to be interrupted, recursively calling thread's interrupt()
  • destroy() : remove itself in parent group after calling if there are no active threads
  • setDaemon(boolean daemon) : After setting to guard ThreadGroup , if the ThreadGroup does not have any active threads, it will be automatically destroyed

氷泠
420 声望647 粉丝