Base multiproceso (9): subproceso daemon, rendimiento, unión y grupo de subprocesos

¡Acostúmbrate a escribir juntos! Este es el décimo día de mi participación en el "Nuggets Daily New Plan · April Update Challenge", haz clic para ver los detalles del evento .

@[toc] Sin darme cuenta, hablé sobre ReentrantLock en el artículo anterior, pero mirando hacia atrás, todavía hay mucho contenido sobre los conceptos básicos de subprocesamiento múltiple que no se ha cubierto, y ReentrantLock es una aplicación de subprocesamiento relativamente avanzada. Hoy, revisaré estos puntos básicos de conocimiento de manera unificada.

Hilo de demonio

En el anterior "Conceptos básicos de subprocesos múltiples (2): Análisis de código fuente de subprocesos" , mencionamos conceptos como subproceso de daemon, unión, etc. Ahora veamos qué es un subproceso de daemon. En Java, hay dos tipos de hilos, uno es el hilo del usuario y el otro es el hilo del demonio. El llamado subproceso daemon es establecerlo en verdadero a través de setDaemon después de que se crea el subproceso y antes de que se inicie.

t2.setDaemon(true);
复制代码

Esto convierte a un hilo en un hilo daemon. Entonces, ¿cuál es el papel del subproceso daemon? Su propósito principal es que cuando un subproceso se establece como un subproceso daemon, el subproceso principal jvm ya no se preocupará por el estado de ejecución del subproceso, a diferencia del subproceso del usuario, si el subproceso del usuario no se completa, entonces el subproceso principal no se cerrará, por lo que mientras se establezca el subproceso del daemon, el subproceso principal se cerrará después de que se ejecute toda la lógica del subproceso del usuario.

package com.dhb.reentrantlocktest;

import java.util.concurrent.TimeUnit;

public class DeamonTest {

	public static  int i = 0;
	public static void main(String[] args) throws InterruptedException{

		Thread t1 = new Thread(() -> {
			try {
				while (true) {
					System.out.println(i++);
					TimeUnit.SECONDS.sleep(1);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});
		t1.setDaemon(true);

		Thread t2 = new Thread(() -> {
			try {
				TimeUnit.SECONDS.sleep(10);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});
		t1.setDaemon(true);

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

复制代码

Mire el siguiente código: T1 está configurado como un subproceso daemon e imprime un número cada segundo, pero T2 es un subproceso de usuario, T2 duerme durante 10 segundos, después de 10 segundos, T2 finaliza y t1 se detiene:

0
1
2
3
4
5
6
7
8
9

Process finished with exit code 0
复制代码

De hecho, esta función se entiende bien, algo similar al daemon en el sistema operativo. Entonces, ¿qué puede hacer este hilo? Esto me recuerda que cuando escribí la cola de bloqueo antes, agregué un subproceso de monitoreo para mostrar regularmente el tamaño de la cola. Después de que la cola sale, el subproceso de monitoreo se detendrá automáticamente.

package com.dhb.reentrantlocktest;

import java.util.concurrent.TimeUnit;

public class DeamonTest1 {

	private static final int MAX = 10;

	private static int count = 0;

	public static void main(String[] args) {

		Thread t1 = new Thread(() -> {
			while (count < MAX){
				 count ++;
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

		Thread t2 = new Thread(() -> {
			while (true) {
				System.out.println(count);
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});

		t2.setDaemon(true);
		t1.start();
		t2.start();

	}
}

复制代码

Como en el código anterior, t2 es el hilo de seguimiento que imprime los números periódicamente. Y t1 saldrá después de acumular el número a MAX.

1
1
3
4
4
6
7
7
9
9
10

Process finished with exit code 0
复制代码

Entonces, el subproceso daemon es muy adecuado para que lo usemos como subproceso de monitoreo en varios grupos.

2.rendimiento

Thread的yield方法是一个可以将当前线程的执行权限让出的方法。调用yield之后,当前执行的线程就会从RUNNING状态变为RUNNABLE状态。我们来看如下例子:

package com.dhb.reentrantlocktest;

import java.util.concurrent.atomic.AtomicInteger;

public class YieldTest {

	private static final int MAX = 20;

	private static volatile AtomicInteger i = new AtomicInteger(0);

	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(() -> {
				while (i.get()<MAX) {
					System.out.println(Thread.currentThread().getName() + " "+(i.getAndIncrement()));
//					Thread.yield();
				}
		},"T1");

		Thread t2 = new Thread(() -> {
			while (i.get()<MAX) {
				System.out.println(Thread.currentThread().getName() + " "+(i.getAndIncrement()));
			}
		},"T2");

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


}

复制代码

首先这两个线程启动,由于T1先执行,那么T1print的数据肯定会比T2多。

T1 0
T2 1
T1 2
T1 4
T1 5
T1 6
T1 7
T2 3
T1 8
T1 10
T1 11
T1 12
T1 13
T2 9
T1 14
T1 16
T1 17
T1 18
T1 19
T2 15
复制代码

我们将yield打开,再看看执行结果:

T1 0
T2 1
T2 3
T1 2
T2 4
T2 6
T2 7
T1 5
T2 8
T2 10
T2 11
T2 12
T2 13
T2 14
T2 15
T2 16
T2 17
T2 18
T2 19
T1 9
复制代码

可以看到调用yield之后,可能会让T2的次数增多。但是需要注意的是,这个情况不是绝对的。当线程从RUNNABLE状态变为RUNNING状态的时候,这个过程并不是类似公平锁那样先进先出,于synchronized导致的从BLOCK到RUNNABLE状态一样。

3. join

join方法是指将运行join方法的线程使其处于WAIT状态,待被运行的线程执行完之后再通知他进入RUNNABLE状态。如下所示:

package com.dhb.reentrantlocktest;

import java.util.concurrent.TimeUnit;

public class JoinTest {

	public static void main(String[] args) throws InterruptedException{
		Thread t1 = new Thread(() -> {
			try {
				System.out.println(Thread.currentThread().getName()+" begin sleep!");
				TimeUnit.SECONDS.sleep(10);
				System.out.println(Thread.currentThread().getName()+" weak up!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});
		Thread t2 = new Thread(() -> {
			try {
				System.out.println(Thread.currentThread().getName()+" begin sleep!");
				TimeUnit.SECONDS.sleep(10);
				System.out.println(Thread.currentThread().getName()+" weak up!");
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		});

		t1.start();
		t2.start();
//		t1.join();
//		t2.join();
		System.out.println("main exit");
	}
}

复制代码

上述代码在没开启join的时候:

main exit
Thread-1 begin sleep!
Thread-0 begin sleep!
Thread-1 weak up!
Thread-0 weak up!
复制代码

可以看到,main线程已经退出了,但是线程0和1都还在运行。 当我们打开join之后:

Thread-0 begin sleep!
Thread-1 begin sleep!
Thread-1 weak up!
Thread-0 weak up!
main exit
复制代码

如果想让两个线程串行运行:

t1.start();
t1.join();
t2.start();
t2.join();
复制代码

这样的执行结果:

Thread-0 begin sleep!
Thread-0 weak up!
Thread-1 begin sleep!
Thread-1 weak up!
main exit
复制代码

4. 线程组

线程组是java中的一个已经不怎么被使用的概念,线程组ThreadGroup对象,可以在new Thread的时候,将线程组传入,之后能实现对线程组的统一interrupt和stop等。但是实际上我们在工作中已经不怎么使用。因为线程组只是提供了一个比较弱的管理机制,类似于在线程中打上标记,这种控制手段比较弱。而我们实际的工作中,大多数情况下是使用的线程池。

package com.dhb.reentrantlocktest;

import java.util.concurrent.TimeUnit;

public class ThreadGroupTest {

	public static void main(String[] args) throws InterruptedException {
		ThreadGroup g1 = new ThreadGroup("G1");
		Thread t1 = new Thread(g1,() -> {
			while (true) {
				System.out.println("*");
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName()+" Interrupt .");
					e.printStackTrace();
				}
			}
		},"T1");

		Thread t2 = new Thread(g1,() -> {
			while (true) {
				System.out.println("-");
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					System.out.println(Thread.currentThread().getName()+" Interrupt .");
					e.printStackTrace();
				}
			}
		},"T2");
		t1.start();
		t2.start();
		System.out.println(g1.getName());
		g1.interrupt();
		TimeUnit.SECONDS.sleep(5);
		System.out.println(g1.activeCount());
	}
}

复制代码

上述代码执行如下:

G1
-
*
T2 Interrupt .
T1 Interrupt .
*
-
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.dhb.reentrantlocktest.ThreadGroupTest.lambda$main$0(ThreadGroupTest.java:13)
	at java.lang.Thread.run(Thread.java:748)
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.dhb.reentrantlocktest.ThreadGroupTest.lambda$main$1(ThreadGroupTest.java:25)
	at java.lang.Thread.run(Thread.java:748)
*
-
-
*
-
*
*
-
2
复制代码

可以看到可以对线程组的线程,统一实现打断方法,本来旧版本还可以实现stop方法。但是这个方法已经在新版本中被废弃。 此外默认情况下,Thread是使用的父类的线程组。 我们可以看Thread中的init方法:

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }

        this.name = name;

        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

... ...
复制代码

可以看到g为null的时候,使用的是parent.getThreadGroup()。默认情况下缺省的就是父线程的Group。

package com.dhb.reentrantlocktest;

import java.util.concurrent.TimeUnit;

public class GroupTest {

	public static void main(String[] args) {
		Thread t1 = new Thread(() -> {
			while (true) {
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"T1");
		t1.start();
		Thread t2 = new Thread(() -> {
			while (true) {
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},"T2");
		t2.start();
		System.out.println("T1 :"+t1.getThreadGroup());
		System.out.println("T2 :"+t2.getThreadGroup());
	}
}

复制代码

De forma predeterminada, se utiliza el grupo del subproceso principal predeterminado. Y todos los subprocesos son creados por main, entonces en realidad es el grupo de subprocesos donde se encuentra main. Personalmente, los grupos de subprocesos son menos importantes que los grupos de subprocesos. Rara vez nos las arreglamos con grupos de subprocesos. En su lugar, utilice un grupo de subprocesos. El grupo de subprocesos se presentará por separado más adelante.

Supongo que te gusta

Origin juejin.im/post/7084996521896181774
Recomendado
Clasificación