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 inconsistencysynchronized
includesmonitor enter
andmonitor exit
twoJVM
instructions, which can guarantee that any thread must obtain data from main memory beforemonitor enter
succeeds at any time, not from the cache. Aftermonitor exit
runs successfully, the value of the shared variable must Flush into main memory instead of just in cachesynchronized
instruction strictly follows theHappens-Beofre
rule, and amonitor exit
instruction must be preceded by amonitor 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 amonitor
. When a thread tries to acquire themonitor
associated with the object, if the counter ofmonitor
is 0, the counter will be incremented by 1 immediately aftermonitor
. Will cause the counter to accumulate again, and if other threads try to acquire, it will block until the counter ofmonitor
becomes 0, and can try to acquire ownership ofmonitor
againmonitorexit
: release ownership ofmonitor
, decrement the counter ofmonitor
by 1, if the counter is 0, it means that the thread no longer has ownership ofmonitor
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 theCPU
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 issynchronized
, which is equivalent to usingthis monitor
in thesynchronized(this)
class monitor
: 0622c9dd179a68 on a static method issynchronized
, which is equivalent to usingclass monitor
in a staticsynchronized(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 ismain
where theThreadGroup
thread is located - If
ThreadGroup
is specified, then add theThreadGroup
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 ingroup
, estimatedactiveGroupCount()
: Get the active sub-group
ingroup
, which is also an approximation, it will recursively acquire all sub-group
getMaxPriority()
: used to get the priority ofgroup
. By default, the priority ofgroup
is 10, and the priority of all threads must not be greater than the priority ofgroup
where the thread is located.getName()
: getgroup
namegetParent()
: get the parentgroup
, if it does not exist, returnnull
list()
: an output method that recursively outputs all active thread information to the consoleparentOf(ThreadGroup g)
: Determine whether the currentgroup
is the parentgroup
of the givengroup
, if the givengroup
is itself, it will also returntrue
setMaxPriority(int pri)
: Specify the maximum priority ofgroup
. 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 ofgroup
. For example, if the thread priority is 10, setgroup
After the priority is 5, the thread priority is greater thangroup
priority, but the newly added thread priority must not be greater thangroup
priorityinterrupt()
: causes all active threads to be interrupted, recursively calling thread'sinterrupt()
destroy()
: remove itself in parentgroup
after calling if there are no active threadssetDaemon(boolean daemon)
: After setting to guardThreadGroup
, if theThreadGroup
does not have any active threads, it will be automatically destroyed
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。