线程管理
线程组(选择性看)
线程组开始是出于安全的考虑设计用来区分不同的 Applet,然而ThreadGroup 并未实现这一目标,在新开发的系统中,已经不常用线程组, 现在一般会将一组相关的线程存入一个数组或一个集合中,如果仅仅是用来区分线程时,可以使用线程名称来区分, 多数情况下,可以忽略线程组。
线程组管理线程就类似于在计算机中使用文件夹管理文件, 在线程组中定义一组相似(相关)的线程,在线程组中也可以定义子线程组。
Thread 类有几个构造方法允许在创建线程时指定线程组,如果在创建线程时没有指定线程组则该线程就属于父线程所在的线程组。JVM 在创建 main 线程时会为它指定一个线程组,因此每个 Java 线程都有一个线程组与之关联,可以调用线程的 getThreadGroup()方法返回线程组。
创建线程组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| public class ThreadGroupTest {
public static void main(String[] args) { ThreadGroup mainGroup = Thread.currentThread().getThreadGroup(); System.out.println(mainGroup); ThreadGroup group1 = new ThreadGroup("group1"); System.out.println(group1); ThreadGroup group2 = new ThreadGroup(mainGroup, "group2"); System.out.println(group1.getParent() == mainGroup); System.out.println(group2.getParent() == mainGroup); Runnable r = () -> System.out.println(Thread.currentThread()); Thread t1 = new Thread(r, "t1"); System.out.println(t1); Thread t2 = new Thread(group1, r, "t2"); Thread t3 = new Thread(group2, r, "t3"); System.out.println(t2); System.out.println(t3); } }
|
线程组的基本操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
|
public class ThreadGroupTest02 {
public static void main(String[] args) { ThreadGroup mainGroup = Thread.currentThread().getThreadGroup(); ThreadGroup group = new ThreadGroup("group"); Runnable r = () -> { while (true) { System.out.println("-----------当前线程: " + Thread.currentThread()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t1 = new Thread(r, "t1"); Thread t2 = new Thread(group, r, "t2"); t1.start(); t2.start(); System.out.println("main线程组中活动线程数量: " + mainGroup.activeCount()); System.out.println("group子线程组中活动线程数量: " + group.activeCount()); System.out.println("main线程组中子线程组数量: " + mainGroup.activeGroupCount()); System.out.println("group子线程组中子线程组数量: " + group.activeGroupCount()); System.out.println("main线程组的父线程组: " + mainGroup.getParent()); System.out.println("group线程组的父线程组: " + group.getParent()); System.out.println(mainGroup.parentOf(mainGroup)); System.out.println(mainGroup.parentOf(group)); mainGroup.list(); } }
|
复制线程组中的线程及子线程组
enumerate(Thread[] list) 把当前线程组和子线程组中所有的线程复制到参数数组中
enumerate(Thread[] list, boolean recursive) ,如果第二个参数设置为false,则只复制当前线程组中所有的线程,不复制子线程组中的线程 。
enumerate(ThreadGroup[] list) 把当前线程组和子线程组中所有的线程组复制到参数数组中。
enumerate(ThreadGroup[] list, boolean recurse) 第二个参数设置false,则只复制当前线程组的子线程组。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58
| public class ThreadGroupTest03 { public static void main(String[] args) { ThreadGroup mainGroup = Thread.currentThread().getThreadGroup(); ThreadGroup group1 = new ThreadGroup("group1"); ThreadGroup group2 = new ThreadGroup(mainGroup, "group2"); Runnable r = () -> { while (true) { System.out.println("----当前线程: " + Thread.currentThread()); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }; Thread t1 = new Thread(r, "t1"); Thread t2 = new Thread(group1, r, "t2"); Thread t3 = new Thread(group2, r, "t3"); t1.start(); t2.start(); t3.start(); Thread[] threadList = new Thread[mainGroup.activeCount()];
mainGroup.enumerate(threadList, false); for (Thread thread : threadList) { System.out.println(thread); } System.out.println("----------------------------"); ThreadGroup[] threadGroups = new ThreadGroup[mainGroup.activeGroupCount()]; mainGroup.enumerate(threadGroups); System.out.println("============================"); for (ThreadGroup threadGroup : threadGroups) { System.out.println(threadGroup); } } }
|
捕获线程的执行异常
在线程的run方法中,如果有受检异常必须进行捕获处理,如果想要获得run()方法中出现的运行时异常信息,可以通过回调UncaughtExceptionHandler
接口获得哪个线程出现了运行时异常。在Thread 类中有关处理运行异常的方法有:
getDefaultUncaughtExceptionHandler()
获得全局的 ( 默认的)UncaughtExceptionHandler
getUncaughtExceptionHandler()
获得当前线程的UncaughtExceptionHandler
setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
设置全局的 UncaughtExceptionHandler
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
设置当前线程的 UncaughtExceptionHandler
当线程运行过程中出现异常,JVM会调用Thread类的dispatchUncaughtException(Throwable e)
方法 , 该方法会调用getUncaughtExceptionHandler().uncaughtException(this, e)
如果想要获得线程中出现异常的信息,就需要设置线程的UncaughtExceptionHandler
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class UnCaughtExceptionHandlerTest {
public static void main(String[] args) { Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException(Thread t, Throwable e) { System.out.println(t.getName() + "线程产生了异常: " + e.getMessage()); } }); Thread t1 = new Thread(() -> { System.out.println(Thread.currentThread().getName() + "开始运行"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(12 / 0); }); t1.start(); new Thread(() -> { String txt = null; System.out.println(txt.length()); }).start();
} }
|
注入Hook钩子线程
现在很多软件包括 MySQL, Zookeeper, kafka 等都存在 Hook 线程的校验机制,目的是校验进程是否已启动,防止重复启动程序。Hook 线程也称为钩子线程,当 JVM退出的时候会执行Hook线程。经常在程序启动时创建一个.lock 文件, 用.lock 文件校验程序是否启动,在程序退出(JVM 退出)时删除该.lock 文件,在 Hook 线程中除了防止重新启动进程外,还可以做资源释放,尽量避免在 Hook 线程中进行复杂的操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| public class HookTest { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { System.out.println("JVM 退出,会启动当前Hook线程,在Hook线程中删除.lock文件"); getLockFile().toFile().delete(); } }); if (getLockFile().toFile().exists()) { throw new RuntimeException("程序已启动"); } else { try { getLockFile().toFile().createNewFile(); System.out.println("程序在启动时创建了 lock 文件"); } catch (IOException e) { e.printStackTrace(); } } for (int i = 0; i < 10; i++) { System.out.println("程序正在运行"); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } } }
private static Path getLockFile() { return Paths.get("", "tmp.lock"); } }
|