目录
3.2.3) ScheduledExecutorService
1) 线程组
1.1)简述
线程组是用来管理线程的,类似到文件夹管理文件。如果线程在创建时没有指定线程组则该线程就属于父线程所在的线程组,jvm在创建main线程时会为它指定一个线程组。线程组是有缺陷的。在新系统中已经不常用线程组了。
基本操作:
//1)返回main线程的线程组
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
System.out.println(threadGroup); //java.lang.ThreadGroup[name=main,maxpri=10] 线程组名,优先级
//2)定义线程组,如果不指定线程组自动属于父线程组
ThreadGroup group1 = new ThreadGroup("group1");
System.out.println(group1); //java.lang.ThreadGroup[name=group1,maxpri=10] 线程组名,优先级
//3)定义线程组,指定父线程组
ThreadGroup group2 = new ThreadGroup(group1, "group2");
System.out.println(group2);//java.lang.ThreadGroup[name=group2,maxpri=10]
// 4)判断group2的父线程组是否为group1
System.out.println(group1 == group2.getParent()); //true
Runnable r = new Runnable() {
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread());
}
};
//5) 创建线程时,如果没有指定父线程组,则默认归属到父线程的线程组中
Thread th1 = new Thread(r,"thread1");
Thread th2 = new Thread(group1,r,"thread2");
th1.start();
th2.start();
1.2)常用方法:
案例模板:
ThreadGroup threadGroup = Thread.currentThread().getThreadGroup();
ThreadGroup group1 = new ThreadGroup("group1");
Runnable r = new Runnable() {
@Override
public void run() {
while(true){}
System.out.println(Thread.currentThread());
}
};
//5) 创建线程时,如果没有指定父线程组,则默认归属到父线程的线程组中
Thread th1 = new Thread(r,"thread1");
Thread th2 = new Thread(group1,r,"thread2");
th1.start();
th2.start();
ThreadGroup mainGroup = Thread.currentThread().getThreadGroup();
activeCount() | 返回当前线程组及子线程组中活动线程的数量(近似值) | |
activeGroupCount() | 返回当前线程组及子线程组中活动线程组的数量 (近似值) | |
int enumerate(Thread[] list,boolean) | 将当前线程组中的活动线程复制到参数数组中,如果boolean为false则不复制子线程组的内容 |
|
enumerate(ThreadGroup[] list) | 将当前线程组中的活动线程组复制到 参数数组中 | |
getMaxPriority() | 返回线程组的最大优先级,默认是 10 | |
getName() | 返回线程组的名称 | |
getParent() | 返回父线程组 | |
interrupt() | 中断线程组中所有的线程 | |
isDaemon() | 判断当前线程组是否为守护线程组 | |
list() | 将当前线程组中的活动线程打印出来 | |
parentOf(ThreadGroup g) | 判断当前线程组是否为参数线程组的父线程组 | |
setDaemon(boolean daemon) | 设置线程组为守护线程组 | 守护线程是为其他线程提供服务的,当 JVM 中只有守护线程时,守护线程会自动销毁,JVM 会退出. 守护线程是当没有活动线程时,自动销毁。而守护线程组是当线程组内没有线程线程是自动销毁。 |
1.2.1)例:interrupt中断线程组内线程
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("当前线程--"+Thread.currentThread().getName()+"--开始");
while (!Thread.currentThread().isInterrupted()){
new StringBuffer();
}
System.out.println("线程结束----"+Thread.currentThread().getName());
}
};
ThreadGroup threadGroup = new ThreadGroup("group1");
Thread t1 = new Thread(threadGroup,r);
Thread t2 = new Thread(threadGroup,r);
t1.start();t2.start();
//中断线程组的活动线程
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("中断线程组的活动线程");
threadGroup.interrupt();
===
当前线程--Thread-0--开始
当前线程--Thread-1--开始
中断线程组的活动线程
线程结束----Thread-1
线程结束----Thread-0
1.2.2 捕获线程的执行异常
在线程运行中,如果出现了异常,jvm会先调用线程本身的uncaughtExceptionHandler方法,如果为null,调用Thread静态的defaultUncaughtException(全局的回调接口)方法,如果为null,则调用当前线程所在线程组 UncaughtExceptionHandler 回调接口uncaughtException 方法,如果线程组也没有设置回调接口,则直接把异常的栈信息定向到 System.err 中
public static void main(String[] args) {
//1,设置线程全局的回调接口
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
//t参数接收发生异常的线程,e就是该线程中的异常
System.out.println(thread.getName()+"线程发生了异常:"+ throwable.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();
}
int i = 10/0; //会产生算术异常
}
});
t1.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
System.out.println("t1线程出错了");
}
});
t1.start();
new Thread(new Runnable() {
@Override
public void run() {
String text = null;
System.out.println(text.length()); //会产生空指针异常
}
}).start();
}
=======
Thread-0开始运行
Thread-1线程发生了异常:null //因为没有设置thread-1的uncaughtException方法,所以调用的是DefaultUncaughtExceptionHandler方法
t1线程出错了 //t1线程设置了uncaughtException方法,所以调用自身的就不在调用了
2) 注入Hook钩子线程
在jvm结束时,会自动运行hook钩子线程
public static void main(String[] args) {
//Hook钩子函数,当jvm结束时运行
Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() {
@Override
public void run() {
System.out.println("JVM结束了,可以进行资源释放");
}
}));
}
======
JVM结束了,可以进行资源释放
3)线程池
3.1)概述
可以以 new Thread( () -> { 线程执行的任务 }).start(); 这种形式开启一个线程. 当 run()方法运行结束,线程对象会被 GC 释放.在真实的生产环境中,可能需要很多线程来支撑整个应用,当线程数量非常多时 ,反而会耗尽 CPU 资源. 如果不对线程进行控制与管理,反而会影响程序的性能. 线程开销主要包括: 创建与启动线程的开销;线程销毁开销; 线程调度的开销; 线程数量受限 CPU 处理器数量.
线程池就是有效使用线程的一种常用方式. 线程池内部可以预先创建一定数量的工作线程,客户端代码直接将任务作为一个对象提交给线程池, 线程池将这些任务缓存在工作队列中, 线程池中的工作线程不断地从队列中取出任务并执行.
3.2 )jdk对线程池的支持
3.2.1)总体架构
3.2.2) ExecutorService的使用
ExecutorService接口没有太大作用
//创建有5个线程大小的线程池(固定5个)
ExecutorService executorService = Executors.newFixedThreadPool(5);
for (int i = 0; i < 18; i++) {
//向线程池提交18个线程,会先缓存到对列中,由5个活动线程 轮流执行
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+" 编号的任务在执行,开始时间:"+System.currentTimeMillis());
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
3.2.3) ScheduledExecutorService
此接口创建一个有调度功能的线程池,可以对Runnable进行时间上的调试
- schedult(Runnable任务,延迟时长,时间单位),在延迟2秒后执行任务
//创建一个有调度功能的线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
//在延迟2秒后执行任务,schedult(Runnable任务,延迟时长,时间单位)
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"正在执行:时间"+System.currentTimeMillis());
}
},2, TimeUnit.SECONDS);
- scheduleAtFixedRate(Runnable任务,n时间后开始,x时间执行一次,时间单位)
//以固定的频率执行任务,开启任务的时间是固定的,在3秒后执行任务,以后每隔5秒执行一次
//scheduleAtFixedRate(Runnable任务,n时间后开始,n时间执行一次,时间单位)
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"...在固定频率开启任务..."+System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(3); //如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);
- scheduleWithFixedDelay(Runnable(),开始执行时间,任务结束后n秒后再执行,时间单位)
//scheduleWithFixedDelay(Runnable(),开始执行时间,任务结束后n秒后再执行,时间单位)
//在上次任务结束后,在固定延迟后再次执行此任务,不管执行任务耗时多长,总是在任务结束后的2秒再次开启新的任务
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId()+"...在固定频率开启任务..."+System.currentTimeMillis());
try {
TimeUnit.SECONDS.sleep(3); //如果任务执行时长超过了时间间隔,则任务完成后立即开启下个任务
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},3,2,TimeUnit.SECONDS);