Executor框架下线程池的使用以及体验
背景:向redis中插入5000条数据,同时将这5000条数据保存到本地的.txt文件中,单线程以及多线程环境下的处理。
1.配置JedisPool,获得Jedis对象实例
项目本身是Spring-boot项目,但这里我们不使用Spring-boot封装的spring-data-starter-redis,而使用原生的Jedis来进行操作。
public static Jedis getJedis() {
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxWaitMillis(30000); //最大等待时间
jedisPoolConfig.setMaxTotal(50); //总连接数
jedisPoolConfig.setMaxIdle(5); //最大空闲连接数
JedisPool jedisPool = new JedisPool(jedisPoolConfig,"xx.xx.xx.xx",
6379,30000,"xxxxx",1);
Jedis jedis = jedisPool.getResource();
return jedis;
}
查看JedisPool类的源码,可以发现它有许多的构造方法。在这里我们使用其中的一个,传递的参数依此是:jedisPoolConfig,redis服务器地址,端口号,超时时间,redis登陆校验密码(如果有配置密码的话),redis中数据库的编号
单线程环境
public static void main(String[] args) throws Exception{
long start = System.currentTimeMillis();
User user = new User();
user.setNickname("777");
Jedis jedis = JedisFactory.getJedis();
File file = new File("D:\\图片\\token.txt");
FileWriter writer = new FileWriter(file,true);
for (int i=0; i < 5000 ; i++) {
System.out.println(i);
user.setId(i);
String token = UUIDUtil.uuid();
jedis.set("UserKey:"+token, JSON.toJSONString(user));
writer.write(token);
writer.write("\r\n");
}
jedis.close();
writer.close();
long time = System.currentTimeMillis() - start;
System.out.println("任务全部完成共耗费时间"+Long.toString(time));
}
public static String uuid() {
return UUID.randomUUID().toString().replace("-","");
}
在D盘创建了一个token.txt的文本文件,创建User对象,所有的user的nickname属性都相同。利用for循环5000次,每次都为user对象设置一个新的Id,随机生成一个UUID作为token。存入redis中的K=Userkey:+token,V=利用fastjson将user对象序列化成Json。同时将所有生成的token存入token.txt文件中。
经过多次测试,平均的完成时间在五分钟左右,这显然是很漫长且不可忍受的。所以我进行了多线程操作。
多线程环境
多线程环境下的业务逻辑代码,跟单线程环境类似,所以就不贴出来了。重点在如何利用Executor框架开辟线程池
public static void main(String[] args) throws InterruptedException {
long start = System.currentTimeMillis();
int count = 50;
ExecutorService executorService = Executors.newCachedThreadPool();
CountDownLatch countDownLatch = new CountDownLatch(count);
for(int i = 0 ; i < count ; i++){
int id = i*(5000/count)+1;
executorService.execute(()->{
try {
ConTest.write(id);
} catch (Exception e) {
e.printStackTrace();
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
long time = System.currentTimeMillis()-start;
System.out.println("任务全部完成共耗费时间"+Long.toString(time));
}
ExecutorService executorService = Executors.newCachedThreadPool();
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
CountDownLatch countDownLatch = new CountDownLatch(count)
countDownLatch是java.util.concurrent包下的一个类,它使用一个计数器来控制主线程与子线程的交互。每当一个子线程完成的时候,我们就调用 countDownLatch.countDown() 方法使计数器的值1。当计数器的值减为0,则表示所有的子线程都已完成,使用 countDownLatch.await方法主线程则可以继续运行。
在CountDownLatch 的构造方法中,我们传入的参数就是计数器的值,这里是50。所以我们的循环需要执行50次,分派50个线程。
经过多次测试,多线程环境下的耗时在7–20秒之间,效率可谓是大大提高。
但并不是说创建的线程越多,则执行的时间就越短。这取决于我们机器的硬件设备。当线程过多,CPU无法及时分配时间片,会造成多个线程的等待。这样一来执行的效率反而会大大降低。所以具体分配多少个线程,需要根据硬件的水平来决定。