首页 > 编程语言 >Java线程-02

Java线程-02

时间:2022-09-23 13:00:18浏览次数:85  
标签:02 Java name secs 线程 end main public

Java 8

--

 

0、前言

一年前写了一篇“Java线程-01”,只是没学透彻。现在继续。ben发布于博客园

比如,怎么配置 线程池的线程名称、设置拒绝策略、使用ScheduledThreadPoolExecutor 等内容。

 

1、ThreadPoolExecutor 概述

java.util.concurrent.ThreadPoolExecutor 

四个构造函数:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue);
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              RejectedExecutionHandler handler);
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory);
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler);

其中,corePoolSize、maximumPoolSize、keepAliveTime+unit 很好理解,自己之前也主要用这些参数建立线程池(Executors工具类)。

workQueue 表示 等待执行的任务,BlockingQueue 对象;ben发布于博客园

threadFactory 表示 线程工厂,之前未使用过,也就没有自动配置过线程池中线程的名称;

handler 表示 拒绝策略,排队满了,线程池无法接收更多任务时根据配置的拒绝策略来执行任务——比如,直接丢弃(拒绝)。

 

BlockingQueue 类型层次结构(Eclipse查看):

RejectedExecutionHandler 类型层次结构:常用的是 4个标记为 s 的静态类

 

2、ThreadPoolExecutor 中的线程名称配置

需要实现 ThreadFactory接口 才可以定制线程名称,ThreadFactory接口 只有1个函数:ben发布于博客园

Thread newThread(Runnable r);

需要使用Thread类的构造函数基于 入参r 构造Thread对象:其中有的参数就可以 配置线程名称

 

建立线程池:使用 Thread(Runnable target) 函数

	public static ExecutorService es = new ThreadPoolExecutor(5, 10, 30L, TimeUnit.SECONDS, 
			new ArrayBlockingQueue<Runnable>(10), new ThreadFactory() {
				
				@Override
				public Thread newThread(Runnable r) {
					return new Thread(r);
				}
				
			});

Thread(Runnable target) 函数:

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }

    /* For autonumbering anonymous threads. */
    private static int threadInitNumber;
    private static synchronized int nextThreadNum() {
        return threadInitNumber++;
    }

测试程序:使用线程池建立5个线程执行

	public static void main(String[] args) {
		IntStream.range(0, 5).forEach(i->{
			es.execute(()->{
				String tname = Thread.currentThread().getName();
				int secs = ThreadLocalRandom.current().nextInt(10);
				System.out.println("i=" + i + ", name=" + tname + ", secs=" + secs);
				
				try {
					TimeUnit.SECONDS.sleep(secs);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(i + " end");
			});
		});
		
		System.out.println("main end");
	}

测试结果:

main end
i=3, name=Thread-3, secs=7
i=4, name=Thread-4, secs=7
i=2, name=Thread-2, secs=9
i=1, name=Thread-1, secs=9
i=0, name=Thread-0, secs=0
0 end
4 end
3 end
2 end
1 end

线程名为默认的,“Thread-” 开头,再加上数字。

 

根据 参考资料#1 建立 AppThreadFactory 类进行测试:ben发布于博客园

public class AppThreadFactory implements ThreadFactory {

	private String prefix = "app-";
	private Integer count = 0;

	public AppThreadFactory() {
	}
	
	public AppThreadFactory(String prefix) {
		if (StringUtils.hasText(prefix)) {
			prefix.trim();
			this.prefix = prefix;
		}
	}
	
	@Override
	public Thread newThread(Runnable r) {
		return new Thread(r, prefix + (count++));
	}
}

使用了 下面的Thead构造函数:

public Thread(Runnable target, String name);

 

使用 AppThreadFactory 建立线程池进行测试:

	public static ExecutorService es = new ThreadPoolExecutor(5, 10, 30L, TimeUnit.SECONDS, 
			new ArrayBlockingQueue<Runnable>(10), 
			new AppThreadFactory()); // 默认前缀
	public static ExecutorService es = new ThreadPoolExecutor(5, 10, 30L, TimeUnit.SECONDS, 
			new ArrayBlockingQueue<Runnable>(10), 
			new AppThreadFactory("Tom-")); // 自定义线程名前缀

继续使用上面的 main 函数测试,可以得到不同的线程名前缀。ben发布于博客园

 

3、ThreadPoolExecutor 中的拒绝策略测试

使用前面没有配置 拒绝策略 的线程池,修改main函数,提交 超过线程池可以支持的最大数量的线程:

	public static void main(String[] args) {
		// 5个线程
//		IntStream.range(0, 5).forEach(i->{
		// 25个线程:超过线程池的 队列长度+最大线程数量(20)
		IntStream.range(0, 25).forEach(i->{
			es.execute(()->{
				String tname = Thread.currentThread().getName();
				int secs = ThreadLocalRandom.current().nextInt(10);
				System.out.println("i=" + i + ", name=" + tname + ", secs=" + secs);
				
				try {
					TimeUnit.SECONDS.sleep(secs);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				
				System.out.println(i + " end");
			});
		});
		
		System.out.println("main end");
	}

执行后发生了异常:java.util.concurrent.RejectedExecutionException

从上图可以看到,默认使用了 AbortPolicy。

 

可以配置为其它的:使用 handler 参数

比如,配置为 new ThreadPoolExecutor.CallerRunsPolicy():ben发布于博客园

此时,超过线程池承载量 的线程由调用方线程main线程执行:可以看到,i=20时 由main线程执行了。

当然,不同情况,输出结果不同。不过,调用方执行会导致调用方卡住。

CallerRunsPolicy测试结果
 i=1, name=Tom-1, secs=9
i=19, name=Tom-9, secs=2
i=0, name=Tom-0, secs=8
i=20, name=main, secs=9
i=2, name=Tom-2, secs=0
i=3, name=Tom-3, secs=4
i=18, name=Tom-8, secs=0
18 end
i=17, name=Tom-7, secs=8
i=16, name=Tom-6, secs=6
i=4, name=Tom-4, secs=6
i=15, name=Tom-5, secs=6
i=5, name=Tom-8, secs=9
2 end
i=6, name=Tom-2, secs=6
19 end
i=7, name=Tom-9, secs=8
3 end
i=8, name=Tom-3, secs=2
4 end
16 end
i=10, name=Tom-6, secs=8
15 end
i=11, name=Tom-5, secs=3
6 end
i=9, name=Tom-4, secs=9
i=12, name=Tom-2, secs=6
8 end
i=13, name=Tom-3, secs=7
17 end
i=14, name=Tom-7, secs=0
14 end
0 end
1 end
20 end
5 end
11 end
i=22, name=Tom-5, secs=3
i=23, name=Tom-7, secs=7
i=21, name=Tom-8, secs=3
main end
i=24, name=Tom-0, secs=3
7 end
21 end
24 end
22 end
12 end
13 end
10 end
9 end
23 end

 

其它的 DiscardOldestPolicy、DiscardPolicy 会导致任务不被执行,被Discard——丢弃,前者丢弃已经提交的,后者丢弃正在提交的。

可以检查各个拒绝策略类的源码。

 

当然,也可以自己实现 RejectedExecutionHandler接口 ,开发自己的拒绝策略类。

怎么开发?有必要吗?什么时候需要?没经历过,不太明白。ben发布于博客园

 

4、使用 ScheduledThreadPoolExecutor

java.util.concurrent.ScheduledThreadPoolExecutor

这是一个 定时调度线程池,提交到这个线程池的任务都会 按照配置的时间策略执行,可以执行一次,可以反复执行。

有4个构造函数:

public ScheduledThreadPoolExecutor(int corePoolSize);
public ScheduledThreadPoolExecutor(int corePoolSize,
                                       RejectedExecutionHandler handler);
public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory);
public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory,
                                       RejectedExecutionHandler handler);

可以看到,只设置了核心线程池,,没有最大线程池、排队的阻塞队列设置。

默认情况下,阻塞队列的最大长度可以认为是无限的——Integer.MAX_VALUE,这也是存在风险的地方,可能导致资源耗尽。

 

调度功能的实现,需要调用以下函数:

public ScheduledFuture<?> schedule(Runnable command,
                                       long delay,
                                       TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
                                           long delay,
                                           TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay,
                                                  long period,
                                                  TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                     long initialDelay,
                                                     long delay,
                                                     TimeUnit unit);

前面2个schedule函数都只会把任务执行1次,后面2个则是周期执行。

 

用法也很简单:

package com.lib.webdemo.config;

import java.util.Date;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class AppScheduledThreadPoolExecutor {

	public static ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5, new AppThreadFactory("stpe-"));
	
	public static void main(String[] args) throws InterruptedException {
		System.out.println("main 1 " + new Date());
		
		IntStream.range(0, 10).forEach(i->{
			executor.schedule(()->{
				System.out.println(i + ", schedule, tname=" + Thread.currentThread().getName());
			}, 10, TimeUnit.SECONDS);
		});
		
		System.out.println("main 2 " + new Date());
		
		int slp = 0;
		BlockingQueue<Runnable> bq = executor.getQueue();
//		while (executor.getActiveCount() > 1) {
		while (! bq.isEmpty()) {
			System.out.println("main bq.size=" + bq.size() + ", remainingCapacity=" + bq.remainingCapacity());
			TimeUnit.SECONDS.sleep(1L);
			slp++;
		}
		
		System.out.println("main END bq.size=" + bq.size() + ", remainingCapacity=" + bq.remainingCapacity());
		
		System.out.println("main 3 slp=" + slp + ", " + new Date());
		
		executor.shutdown();
		
		System.out.println("main END " + new Date());
	}
	
}

测试结果:ben发布于博客园

测试ScheduledThreadPoolExecutor
 main 1 Fri Sep 23 12:43:59 CST 2022
main 2 Fri Sep 23 12:43:59 CST 2022
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
main bq.size=10, remainingCapacity=2147483647
0, schedule, tname=stpe-0
1, schedule, tname=stpe-1
3, schedule, tname=stpe-3
2, schedule, tname=stpe-2
4, schedule, tname=stpe-4
5, schedule, tname=stpe-3
6, schedule, tname=stpe-1
9, schedule, tname=stpe-0
7, schedule, tname=stpe-2
8, schedule, tname=stpe-3
main END bq.size=0, remainingCapacity=2147483647
main 3 slp=10, Fri Sep 23 12:44:10 CST 2022
main END Fri Sep 23 12:44:10 CST 2022

 

从线程池的队列的 remainingCapacity 属性可以看到,其值几乎是无限的。ben发布于博客园

是否可以修改 队列的排队长度呢?

没有找到。

 

缘起:

之前使用 spring 的 @Schedule 注解 执行调度时出现了一些问题(修改服务器时间后,调度失效),看博文后才知道是属于 此类。

不过,这个类 存在一些问题。

还有更好的方式执行调度任务吗?不会导致OOM那种。ben发布于博客园

 

更进一步:

线程池的队列中存在任务时,此时停止程序(断电、kill等),队列中的任务是不是就不能被执行了?

 

参考资料

1、使用ThreadFactory

https://www.jianshu.com/p/7040054b069b

2、JUC之ScheduledThreadPoolExecutor

https://blog.csdn.net/weixin_50518271/article/details/119116334

ben发布于博客园

标签:02,Java,name,secs,线程,end,main,public
From: https://www.cnblogs.com/luo630/p/16722212.html

相关文章

  • 计算机毕业设计 SSM+Vue农家乐管理系统 农家院住宿管理系统 民宿旅游预约管理系统 Jav
    ......
  • JavaScript HTML DOM 事件
    对事件做出反应:当用户点击鼠标时当网页已加载时当图像已加载时当鼠标移动到元素上时当输入字段被改变时当提交HTML表单时当用户触发按键时用户点击改变内容:......
  • Java8中stream新特性
    参考总结:https://www.bilibili.com/video/BV1ut411g7E9?p=7&vd_source=c85b4a015a69e82ad4f202bd9b87697f了解Streamjava8中有两大最为重要的改变。第一个是Lambda表达式......
  • 今日热门表情包精选2022-09-23-985
    今日热门表情包精选款鸡涌,把心都给你我什么都看见了不行我受不了这委屈(橘猫)不说了要去搬砖了孤寡之王七夕蛤蟆之寡王点我叫到你朋友自闭表情包沙雕熊猫头哎!我.哎.呵呵......
  • 2022.9.21———HZOI【CSP-S模拟8】游寄
    \(Preface\)评价:最近网络流学魔怔了感觉学长很喜欢\(AtCoder\)。。。其实题的质量似乎确实很好\(Rank34/43\)\(5pts+10pts+5pts+0pts=20pts\)话说只要水到55p......
  • Java基础(学习笔记非本人纯原创)
    Java学习Java基础语法一、注释:注释的说明:1、注释是在程序指定位置添加的说明信息(例如衣服的吊牌等)2、注释不参与程序运行,仅起到说明作用注释的分类://......
  • JavaScript HTML DOM - 改变CSS
    改变HTML样式:语法:document.getElementById(id).style.property=新样式 使用事件:HTMLDOM允许通过触发事件来执行代码:比如:元素被点击。页面加载完成。输入框被修......
  • JavaScript HTML DOM
    DOM(文档对象模型)      文档        -文档表示的就是整个的html网页文档      对象        -对象表示将......
  • Java 简介:第 1 条
    Java简介:第1条最安全、最便携的Java是的..Java是所有面向对象语言中最安全和最可移植的语言。但是,如何?我们应该问一下Java的构思者JamesGosling吗?当然,他可......
  • Java I/O流(四)I/O流原理和分类 [节点流,处理流]
    节点流可以从一个特定的数据源读写数据,如FileReader,FileWriter等等处理流(也叫包装流)是“连接”在已存在的流(节点流或处理流)之上,为程序提供更强大的读写功能,如BufferedRea......