每天学习一点点20211210/20211211--线程管理

线程组

  类似于在计算机中使用文件夹管理文件,也可以使用线程组来管理线程。在线程组中定义一组相似(相关)的线程,在线程组中也可以定义子线程组。
  Thread 类有几个构造方法允许在创建线程时指定线程组,如果在创建线程时没有指定线程组则该线程就属于父线程所在的线程组。JVM 在创建 main 线程时会为它指定一个线程组,因此每个 Java 线程都有一个线程组与之关联,可以调用线程的 getThreadGroup() 返回线程组。
  线程组开始是处于安全的考虑设计用来区分不同的 Applet,然而 ThreadGroup 并未实现这一目标,在新开发的系统中,已经不常用线程组,现在已办会将一组相关的线程存入一个数组或一个集合中,如果仅仅是用来区分线程时,可以使用线程名称来区分,多数情况下,可以忽略线程组。

1.创建线程组

package com.jason.java.duoxiancheng.threadgroup;

/**
 * @author Jason
 */
public class Test01 {
    public static void main(String[] args) {
        //1 返回当前main线程的线程组
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
        System.out.println(mainGroup);

        //2 定义线程组,如果不指定所属线程组,则自动归属当前线程所属的线程组中
        ThreadGroup group1 = new ThreadGroup("group1");
        System.out.println(group1);

        //3 定义线程组,同时指定父线程组
        ThreadGroup group2 = new ThreadGroup(mainGroup, "group2");
        //现在 group1 与 group2 都是 mainGroup 线程组中的子线程组,调用线程组的 getParent()返回父线程组
        System.out.println(group1.getParent() == mainGroup);//true
        System.out.println(group2.getParent() == mainGroup);//true

        //4 在创建线程时指定所属线程组
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread());
            }
        };
        //在创建线程时,如果没有指定线程组,则默认线程归属到父线程的线程组中
        //  在 main 线程中创建了 t1 线程,称 main 线程为父线程,t1线程为子线程,t1 没有指定线程组,则 t1
        //线程就归属到父线程 main 线程的线程组中
        Thread t1 = new Thread(r, "t1");
        System.out.println(t1);//Thread[t1,5,main], t1 的线程组是 main 线程组

        //创建线程时,可以指定线程所属线程组
        Thread t2 = new Thread(group1, r, "t2");
        Thread t3 = new Thread(group2, r, "t3");
        System.out.println(t2);
        System.out.println(t3);
    }
}

2.线程组的基本操作

  • int activeCount() 返回当前线程组及子线程组中活动线程的数量(近似值)
  • int activeGroupCount() 返回当前线程组及子线程组中活动线程组的数量
  • int enumerate(Thread[] list) 将当前线程组中的活动线程复制到参数数组中
  • int getMaxPriority() 返回线程组的最大优先级,默认是 10
  • String getName() 返回线程组的名称
  • ThreadGroup getParent() 返回父线程组
  • void interrupt() 中断线程组中所有的线程
  • boolean isDaemon() 判断当前线程组是否为守护线程组
  • void list() 将当前线程组中的活动线程打印出来
  • boolean parentOf(ThreadGroup g) 判断当前线程组是否为参数线程组的父线程
  • void setDaemon(boolean daemon) 设置线程组为守护线程组
package com.jason.java.duoxiancheng.threadgroup;

/**
 * @author Jason
 */
public class Test02 {
    public static void main(String[] args) {
        //返回当前线程组
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();

        //再定义线程组
        ThreadGroup group = new ThreadGroup("group");//默认 group 的父线程组是 main 线程组
        Runnable r = new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("------当前线程:" + Thread.currentThread());
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };

        Thread t1 = new Thread(r, "t1");//默认在 main 线程组中创建
        Thread t2 = new Thread(group, "t2");//在指定的 group 线程组中创建线程
        t1.start();
        t2.start();

        //打印线程的相关属性
        System.out.println("main 线程组中活动线程数量:" + mainGroup.activeCount());//4,main线程组中活动线程:main,t1,t2,垃圾回收器
        System.out.println("group 子线程组中活动线程数量:" + group.activeCount());//1,t2
        System.out.println("main 线程组中子线程组数量:" + mainGroup.activeGroupCount());//1,group
        System.out.println("group 子线程组中子线程组数量:" + group.activeGroupCount());//0
        System.out.println("main 线程组的父线程组:" + mainGroup.getParent());//system
        System.out.println("group 线程组的父线程组:" + group.getParent());//main
        System.out.println(mainGroup.parentOf(mainGroup));//true,线程组也是它自己的父线程组
        System.out.println(mainGroup.parentOf(group));//true
        mainGroup.list();//把main 线程组中多有的线程打印输出
    }
}

3.复制线程组中的线程及子线程组

  enumerate(Thread[] list) 把当前线程组和子线程组中所有的线程复制到参数数组中
  enumerate(Thread[] list, boolean recurse) ,如果第二个参数设置为 false,则只复制当前线程组中所有的线程,不复制子线程组中的线程
  enumerate(ThreadGroup[] list) 把当前线程组和子线程组中所有的线程组复制到参数数组中
  enumerate(ThreadGroup[] list, boolean recurse) 第二个参数设置为false,则只复制当前线程组的子线程组。

package com.jason.java.duoxiancheng.threadgroup;

/**
 * @author Jason
 */
public class Test03 {
    public static void main(String[] args) {
        ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();

        //main 线程组中定义了两个子线程组
        ThreadGroup group1 = new ThreadGroup("group1");//默认 group1 的父线程组 就是当前线程组 main
        ThreadGroup group2 = new ThreadGroup(mainGroup, "group2");

        Runnable r = new Runnable() {
            @Override
            public void run() {
                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"); //在 group1 线程组中创建线程
        Thread t3 = new Thread(group2, r, "t3"); //在 group2 线程组中创建线程
        t1.start();
        t2.start();
        t3.start();

        //1 把 main 线程组中的线程复制到数组中
        //先定义存储线程的数组,数组的长度为 main 线程组中活动线程的数量
        Thread[] threadList = new Thread[mainGroup.activeCount()];
        /*//把 main 线程组包括子线程组中的所有的线程复制到数组中
        mainGroup.enumerate(threadList);
        //遍历 treadList 数组
        for (Thread thread : threadList) {
            System.out.println(thread);
        }
        System.out.println("---------------------");*/

        //只把 main 线程组中的线程复制到数组中,不包含子线程组的线程
        mainGroup.enumerate(threadList, false);
        //遍历 treadList 数组
        for (Thread thread : threadList) {
            System.out.println(thread);
        }
        System.out.println("---------------------");

        //2 把 main 线程组中的子线程组复制到数组中
        //定义数组存储线程组
        ThreadGroup[] threadGroups = new ThreadGroup[mainGroup.activeGroupCount()];
        //把 main 线程组中的子线程复制到数组中
        mainGroup.enumerate(threadGroups);
        System.out.println("================");
        for (ThreadGroup threadGroup : threadGroups) {
            System.out.println(threadGroup);
        }
    }
}

4.线程组的批量中断

  线程组的 interrupt() 可以给该线程组中所有的活动线程添加中断标志

package com.jason.java.duoxiancheng.threadgroup;

/**
 * 线程组的批量中断
 * @author Jason
 */
public class Test04 {
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println("当前线程--" + Thread.currentThread() + "--开始循环");

                //当前线程没有被中断就一直循环
                while (!Thread.currentThread().isInterrupted()) {
                    System.out.println(Thread.currentThread().getName() + "---------");
//                    try {
//                        Thread.sleep(500);
//                    } catch (InterruptedException e) {
//                        //如果中断睡眠中的线程,产生中断异常,同时会清除中断标志
//                        e.printStackTrace();
//                    }
                }
                System.out.println(Thread.currentThread() + "循环结束");
            }
        };

        //创建线程组
        ThreadGroup group = new ThreadGroup("group");
        //在 group 线程组中创建5个线程
        for (int i = 0; i < 5; i++) {
            new Thread(group,r).start();
        }

        Thread.sleep(2000);
        //中断线程组,会中断线程组中所有的线程
        group.interrupt();
    }
}

5.设置守护线程组

  守护线程是为其他线程提供服务的,当 JVM 中只有守护线程时,守护线程会自动销毁,JVM 会退出。
  调用线程组的 setDaemon(true) 可以把线程组设置为守护线程组,当守护线程组中没有任何活动线程时,守护线程组会自动销毁。
  线程组的守护属性,不影响线程组中线程的守护属性,或者说守护线程组中的线程可以是非守护线程。

package com.jason.java.duoxiancheng.threadgroup;

/**
 * @author Jason
 */
public class Test05 {
    public static void main(String[] args) throws InterruptedException {
        //先定义线程组
        ThreadGroup group = new ThreadGroup("group");

        //设置线程组为守护线程组
        group.setDaemon(true);

        //向组中添加3个线程
        for (int i = 0; i < 3; i++) {
            new Thread(group, new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 20; j++) {
                        System.out.println(Thread.currentThread().getName() + "--" + j);
                        try {
                            Thread.sleep(500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }).start();
        }
        Thread.sleep(5000);
        System.out.println("main...end...");
    }
}

捕获线程的执行异常

  在线程的 run() 方法中,如果有受检异常必须进行捕获处理,如果想要获得 run() 方法中出现的运行时异常信息,可以通过回调 UncaughtExceptionHandler 接口获得哪个线程出现了运行时异常。在 Thread 类中有关处理异常的方法有:

  • static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 获得全局的(默认的) UncaughtExceptionHandler
  • UncaughtExceptionHandler getUncaughtExceptionHandler() 获得当前线程的 UncaughtExceptionHandler
  • static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) 设置全局的 UncaughtExceptionHandler
  • void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) 设置当前线程的 UncaughtExceptionHandler
      当线程运行过程中出现异常,JVM 会调用 Thread 类的 dispatchUncaughtException(Throwable e) 方法,该方法会调用 getUncaughtExceptionHandler().uncaughtException(this, e);如果想要获得线程中出现的异常的信息,就需要设置线程的 UncaughtExceptionHandler。
package com.jason.java.duoxiancheng.threadexception;

/**
 * @author Jason
 */
public class Test {
    public static void main(String[] args) {
        //设置线程全局的回调接口
        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread t, Throwable e) {
                //t 参数接收发生异常的线程,e 就是该线程中的异常
                System.out.println(t.getName()+"线程产生了异常:"+e.getMessage());
            }
        });

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "开始运行");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    //线程中的受检异常必须捕获处理
                    e.printStackTrace();
                }
                System.out.println(12 / 0);
            }
        });
        t1.start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                String txt = null;
                System.out.println(txt.length());//会产生空指针异常
            }
        }).start();
    }
}

  在实际开发中,这种设计异常处理的方式还是比较常用的,尤其是异常执行的方法。
  如果线程产生了异常,JVM 会调用 dispatchUncaughtException() 方法,在该方法中调用了 getUncaughtExceptionHandler().uncaughtException(this, e);如果当前线程设置了 UncaughtExceptionHandler 回调接口就直接调用它自己的 uncaughtException 方法,如果没有设置则调用当前线程所在线程组 UncaughtExceptionHandler 回调接口的 uncaughtException 方法,如果线程组也没有设置回调接口,则直接把异常的栈信息定向到 System.err 中。

注入 Hook 钩子线程

  现在很多软件包括 MySQL,Zookeeper,kafka 等都存在 Hook线程的校验机制,目的是校验进程是否已启动,防止重复启动程序。
  Hook 线程也称为钩子线程,当 JVM 退出的时候会执行 Hook 线程。经常在程序启动的时候创建一个 .locl 文件,用 .lock 文件校验程序是否启动,在程序退出(JVM退出)时删除该 .lock 文件,在 Hook 线程中除了防止重新启动进程外,还可以做资源释放,尽量避免在 Hook 线程中进行复杂的操作。

package com.jason.java.duoxiancheng.hook;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.TimeUnit;

/**
 * 通过 Hook 线程防止程序重复启动
 * @author Jason
 */
public class Test {
    public static void main(String[] args) {
        //1注入 Hook 线程,在程序退出时删除 .lock 文件
        Runtime.getRuntime().addShutdownHook(new Thread(){
            @Override
            public void run() {
                System.out.println("JVM 退出,会启动当前 Hook 线程,在 Hook 线程中删除 .lock 文件");
                getLockFile().toFile().delete();
            }
        });

        //2程序运行时,检查 lock 文件是否存在,如果 lock 文件存在,则抛出异常
        if (getLockFile().toFile().exists()) {
            throw new RuntimeException("程序已启动");
        }else {
            //文件不存在,说明程序时第一次启动,创建lock文件
            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");
    }
}

线程池

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值