文档章节

Java线程池扩展之关联线程池与业务

九州暮云
 九州暮云
发布于 2017/07/29 14:55
字数 1338
阅读 1092
收藏 51
点赞 0
评论 3

自定义线程池工厂

Java API针对不同需求,利用Executors类提供了4种不同的线程池:newCachedThreadPool, newFixedThreadPool, newScheduledThreadPool, newSingleThreadExecutor。我们以创建固定线程池为例,说明创建线程池的一般做法:

public class CustomExecutorService {

	// 用默认方式:创建固定大小为50的线程池
	private static ExecutorService DEFAULT_EXECUTORS = Executors.newFixedThreadPool(50);
	
	@Test
	public void testExecutorService() throws Exception {
		// 循环500次执行任务
		for (int i = 0; i < 500; i++) {
			Thread thread = new Thread(new Runnable() {

				@Override
				public void run() {
					// TODO Auto-generated method stub
					System.out.println(Thread.currentThread().getName());
				}
			}, "default_thread");
			DEFAULT_EXECUTORS.execute(thread);
		}
		// 加死循环是为了不至于主线程终止,便于用VisualVM查看线程池情况,正式环境中去掉
		while (true) {

		}
	}
}

在生产环境里,我们会开启Web服务器的JMX,用VisualVM来远程监控JVM的运行情况,这种方式监控到的线程池情况如下:

输入图片说明

用默认方法创建线程池生成的线程名称都以pool前缀开头。但是在高并发多线程的情况下,如果遇到内存溢出、死锁、线程阻塞等问题,要监控与具体业务相关的线程池的执行情况(睡眠、运行、等待等情况),可能就比较棘手。这时我们需要给线程池里的线程起个与具体业务相关的名字,来与其他线程池创建的线程区分开。完整测试用例代码如下:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;

import org.junit.Test;

public class CustomExecutorService {

	// 用默认方式:创建固定大小为50的线程池
	private static ExecutorService DEFAULT_EXECUTORS = Executors.newFixedThreadPool(50);
	// 用自定义方式:给线程起与业务相关的名字
	private static ExecutorService CUSTOM_EXECUTORS = Executors.newFixedThreadPool(50, new EventThreadFactory());

	@Test
	public void testExecutorService() throws Exception {
		// 循环500次执行任务
		for (int i = 0; i < 500; i++) {
			Thread thread = new Thread(new Runnable() {

				@Override
				public void run() {
					// TODO Auto-generated method stub
					System.out.println(Thread.currentThread().getName());
				}
			}, "default_thread");
			DEFAULT_EXECUTORS.execute(thread);
		}
		// 循环500次执行任务
		for (int i = 0; i < 500; i++) {
			Thread thread = new Thread(new Runnable() {

				@Override
				public void run() {
					// TODO Auto-generated method stub
					System.out.println(Thread.currentThread().getName());
				}
			}, "custom_thread");
			CUSTOM_EXECUTORS.execute(thread);
		}
		// 加死循环是为了不至于主线程终止,便于用VisualVM查看线程池情况,正式环境中去掉
		while (true) {

		}
	}
}

/**
 * 事件线程池,只是对标准的DefaultThreadFactory修改了线程名称,便于追踪。
 */
class EventThreadFactory implements ThreadFactory {
	// 用AtomicInteger来为线程计数,每次加1
	private static final AtomicInteger poolNumber = new AtomicInteger(1);
	private final AtomicInteger threadNumber = new AtomicInteger(1);
	// 线程所属的线程组
	private final ThreadGroup group;
	// 线程名称
	private final String namePrefix;

	EventThreadFactory() {
		SecurityManager s = System.getSecurityManager();
		group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();
		// 命名线程
		namePrefix = "push-pool-" + poolNumber.getAndIncrement() + "-thread-";
	}

	public Thread newThread(Runnable r) {
		Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);
		// 设置相同的线程优先级,避免线程池里的线程根据优先级争抢资源,保证任务的正常执行
		if (t.getPriority() != Thread.NORM_PRIORITY)
			t.setPriority(Thread.NORM_PRIORITY);
		return t;
	}
}

可以看到,我们创建了EventThreadFactory工厂类来生成与我们业务相关的线程,需要注意的是自定义的线程工程类需要实现ThreadFactory接口,才能被Executors类使用。这里我给线程起名为push-pool,表明这是一个处理push任务,发送push给客户端的线程池。再次运行以上测试用例,查看监控结果:

输入图片说明

默认线程工厂类

默认的线程工厂类在Executors类里面,是一个静态内部类,代码如下:

/**
 * The default thread factory
 */
static class DefaultThreadFactory implements ThreadFactory {
   private static final AtomicInteger poolNumber = new AtomicInteger(1);
   private final ThreadGroup group;
   private final AtomicInteger threadNumber = new AtomicInteger(1);
   private final String namePrefix;

   DefaultThreadFactory() {
       SecurityManager s = System.getSecurityManager();
       group = (s != null) ? s.getThreadGroup() :
                             Thread.currentThread().getThreadGroup();
       namePrefix = "pool-" +
                     poolNumber.getAndIncrement() +
                    "-thread-";
   }

   public Thread newThread(Runnable r) {
       Thread t = new Thread(group, r,
                             namePrefix + threadNumber.getAndIncrement(),
                             0);
       if (t.isDaemon())
           t.setDaemon(false);
       if (t.getPriority() != Thread.NORM_PRIORITY)
           t.setPriority(Thread.NORM_PRIORITY);
       return t;
   }
}

上面的EventThreadFactory线程工厂类的定义就源自这个类。在使用线程工厂的时候,Executors类使用defaultThreadFactory方法来获得线程工厂对象,这样避免了调用者与具体线程工厂的强耦合,提高了代码的可扩展性,该方法代码如下:

public static ThreadFactory defaultThreadFactory() {
    return new DefaultThreadFactory();
}

线程池创建规范

良好的开端是成功的一半。以下内容摘自阿里巴巴的Java开发手册,对我们排查线程池问题有一定帮助:

1、【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:使用线程池的好处是减少在创建和销毁线程上所花的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者 “过度切换” 的问题。

2、线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors 各个方法的弊端:

  • newFixedThreadPool 和 newSingleThreadExecutor: 主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至 OOM。

  • newCachedThreadPool 和 newScheduledThreadPool: 主要问题是线程数最大数是 Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至 OOM。

3、【强制】创建线程或线程池时请指定有意义的线程名称,方便出错时回溯。

正例:

public class TimerTaskThread extends Thread {
	public TimerTaskThread(){
		super.setName("TimerTaskThread"); ...
	}
}

© 著作权归作者所有

共有 人打赏支持
九州暮云
粉丝 47
博文 128
码字总数 65499
作品 0
海淀
高级程序员
加载中

评论(3)

ly6cyh
ly6cyh
过来学习学习~
九州暮云
九州暮云

引用来自“凡行”的评论

��前不久面试就问道了相关的问题

嗯,一起学习。我还会总结的,这是第一篇
Fanxme
Fanxme
��前不久面试就问道了相关的问题
JVM学习总结(一)运行时数据区

《深入Java虚拟机》这本书买了有一段时间了,当时看的时候就只是看,并没有边看边总结啥的,最后发现到脑子里面的根本所剩无几了。现在开始要好好归纳总结地再学习一遍。 运行时数据区域 JV...

hensemlee ⋅ 04/22 ⋅ 0

JVM(Thread/Stack)

JVM Thread/Stack Memory Size JVM Thread/Stack Object states (6 states) Dump OS Thread/Stack OS的线程运行状态 Iuput(top): Output: or input(ps): Dump Thread/Stack Analysis 注意thr......

赵-猛 ⋅ 2016/10/12 ⋅ 0

【JVM】 java内存区域与内存溢出异常

前言 此系列博客是读《深入理解java虚拟机》所做的笔记整理。 No1. JVM内存管理这堵墙? 对C和C++的开发人员来说,在内存管理领域,他们既拥有每一个对象的“所有权”,也担负着每一个对象生...

binggetong ⋅ 05/07 ⋅ 0

连接池详解,c3p0与dbcp的区别!

连接池: 连接池是创建和管理一个连接的缓冲池的技术,这些连接准备好被任何需要它们的线程使用。这项技术能明显提高对数据库操作的性能。 连接池的好处: (1)对于大多数应用程序,当它们正...

IT_laobai ⋅ 昨天 ⋅ 0

ThreadLocal可能引起的内存泄露

  threadlocal里面使用了一个存在弱引用的map,当释放掉threadlocal的强引用以后,map里面的value却没有被回收.而这块value永远不会被访问到了. 所以存在着内存泄露. 最好的做法是将调用thr...

天天顺利 ⋅ 06/15 ⋅ 0

JVM学习之——Java内存区域

为了加深对Java语言的理解,加深对Java虚拟机工作机制、底层特性的了解和掌握,准备在闲暇时间,抽空对《深入理解Java虚拟机 JVM高级特性与最佳实践》一书进行学习。本文是学习此书第2章时的...

你想要怎样的未来 ⋅ 05/27 ⋅ 0

java基础thread——java5之后的多线程(浅尝辄止)

承上启下 虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象L...

潇潇漓燃 ⋅ 06/03 ⋅ 0

编写高性能 Java 代码的最佳实践

摘要:本文首先介绍了负载测试、基于APM工具的应用程序和服务器监控,随后介绍了编写高性能Java代码的一些最佳实践。最后研究了JVM特定的调优技巧、数据库端的优化和架构方面的调整。以下是译...

这篇文章 ⋅ 昨天 ⋅ 0

Java多线程学习(二)synchronized关键字(2)

系列文章传送门: Java多线程学习(一)Java多线程入门 Java多线程学习(二)synchronized关键字(1) java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Ja...

一只蜗牛呀 ⋅ 04/16 ⋅ 0

Java多线程之线程池(ThreadPoolExecutor)实现原理分析(一)

在上一篇文章Java中实现多线程的3种方法介绍和比较中,我们讲解了Java中实现多线程的3种方法。使用多线程,就必须要考虑使用线程池,今天我们来聊聊线程池的那些事。 注:源码都是基于JDK1....

小怪聊职场 ⋅ 05/14 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

用SQL命令查看Mysql数据库大小

要想知道每个数据库的大小的话,步骤如下: 1、进入information_schema 数据库(存放了其他的数据库的信息) use information_schema; 2、查询所有数据的大小: select concat(round(sum(da...

源哥L ⋅ 15分钟前 ⋅ 0

两个小实验简单介绍@Scope("prototype")

实验一 首先有如下代码(其中@RestController的作用相当于@Controller+@Responsebody,可忽略) @RestController//@Scope("prototype")public class TestController { @RequestMap...

kalnkaya ⋅ 20分钟前 ⋅ 0

php-fpm的pool&php-fpm慢执行日志&open_basedir&php-fpm进程管理

12.21 php-fpm的pool pool是PHP-fpm的资源池,如果多个站点共用一个pool,则可能造成资源池中的资源耗尽,最终访问网站时出现502。 为了解决上述问题,我们可以配置多个pool,不同的站点使用...

影夜Linux ⋅ 29分钟前 ⋅ 0

微服务 WildFly Swarm 管理

Expose Application Metrics and Information 要公开关于我们的微服务的有用信息,我们需要做的就是将监视器模块添加到我们的pom.xml中: 这将使在管理和监视功能得到实现。从监控角度来看,...

woshixin ⋅ 30分钟前 ⋅ 0

java连接 mongo伪集群部署遇到的坑

部署mongo伪集群 #创建mongo数据存放文件地址mkdir -p /usr/local/config1/datamkdir -p /usr/local/config2/data mkdir -p /usr/local/config3/data mkdir -p /usr/local/config1/l......

努力爬坑人 ⋅ 31分钟前 ⋅ 0

React Native & Weex 区别

JS引擎 Weex使用V8, React native使用JSCore JS开发框架 ( Js Framework ) Weex基于vue.js(2W+ star)。小巧轻量的前端开发框架,组件化,数据绑定,2.0引入virtual dom。 ReactNative使用...

东东笔记 ⋅ 39分钟前 ⋅ 1

UIkit 分页组件动态加载简单实现

1. 问题描述 使用过UIkit分页组件的都清楚,UIkit的分页不能动态刷新数据,也就是不能在点击下一页的时候,动态从后台加载数据,并且刷新页数以及该页数上的数据,下面是一个简单实现,没有做...

影狼 ⋅ 40分钟前 ⋅ 0

Mobx入门之三:Provider && inject

上一节中<App/>组件传递状态temperatures给children -- <TemperatureInput />,如果组建是一个tree, 那么属性的传递则会非常繁琐。redux使用Provider给子组件提供store, connect将子组件和s...

pengqinmm ⋅ 42分钟前 ⋅ 0

魔兽世界 7.0版本 S23/S24/S25全职业普通+精锐套

  死亡骑士   (联盟)   (部落)   (精锐)   恶魔猎手   (联盟)   (部落)   (精锐)   德鲁伊   (联盟)   (部落)   (精锐)   猎人   (联盟) ...

wangchen1999 ⋅ 49分钟前 ⋅ 0

maven顶级pom和子pom的版本号批量修改

当一个版本发布,新起一个版本时,我们只需要手动修改一下项目中pom.xml的版本号就可以了。但是如果这个maven项目有很多的子模块项目,那么一个个手动的去改就显得费时费力又繁琐了。还好,m...

ArlenXu ⋅ 58分钟前 ⋅ 0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部