jdk1.5之后自带了线程池功能,妈妈再也不用担心线程使用中遇到的各种坑了。
先来一个简单的例子来体验下线程池:
-
比如有个需求,我需要读取一个目录下的文件,将所有的文件进行解析。那么如果依次读取文件肯定很慢,所以用多线程的方式来处理:
-
代码:
public class ThreadTest { private Logger logger = LoggerFactory.getLogger(ThreadTest.class); public static void main(String[] args){ new ThreadTest().test(); } public void test() { String dirName = "/home/public/test/"; File dir = new File(dirName); File[] files = dir.listFiles(new FileFilter() { @Override public boolean accept(File file) { if(file.getName().endsWith(".txt")){ return true; } return false; } }); ExecutorService executor = Executors.newFixedThreadPool(5); for(final File file:files){ executor.execute(new Runnable() { @Override public void run() { try { String fileName= file.getName(); List<String> lines = Files.readLines(file, Charset.defaultCharset()); for(int i=0;i<lines.size();i++) { logger.info("读取文件: {} 的第 {} 行的内容为:{}" , fileName, i, lines.get(i)); } } catch (IOException e) { e.printStackTrace(); } } }); } executor.shutdown(); } }
java多线程组成
- jdk1.5以后提供了很方面的原生的线程池用法。顶级的线程池接口是Executor。
- 先来看下线程池中几个类的继承关系:
- 然后jdk还提供了一个Executors类,该类可以很方面的创建线程池的方法。常见的创建线程池方法有以下几种:
- newCachedThreadPool
- 创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。
- 对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。
- 调用 execute 将重用以前构造的线程(如果线程可用)。
- 如果现有线程没有可用的,则创建一个新线程并添加到池中。
- 线程默认有60秒的缓存期,如果60秒内未被使用,就会移除这个线程。
- 长时间保持空闲的线程池不会使用任何资源。因为都被移除了嘛
- newFixedThreadPool
- newFixedThreadPool与cacheThreadPool差不多,也是能reuse就用,但不能随时建新的线程
- 其独特之处:任意时间点,最多只能有固定数目的活动线程存在,此时如果有新的线程要建立,只能放在另外的队列中等待,直到当前的线程中某个线程终止直接被移出池子
- 和cacheThreadPool不同,FixedThreadPool没有IDLE机制(可能也有,但既然文档没提,肯定非常长,类似依赖上层的TCP或UDP IDLE机制之类的),所以FixedThreadPool多数针对一些很稳定很固定的正规并发线程,多用于服务器
- 从方法的源代码看,cache池和fixed 池调用的是同一个底层池,只不过参数不同:fixed池线程数固定,并且是0秒IDLE(无IDLE);cache池线程数支持0至Integer.MAX_VALUE(显然完全没考虑主机的资源承受能力),60秒IDLE
- newScheduledThreadPool
- 调度型线程池
- 这个池子里的线程可以按schedule依次delay执行,或周期执行
- newSingleThreadExecutor -单例线程,任意时间池中只能有一个线程。 -用的是和cache池和fixed池相同的底层池,但线程数目是1,0秒IDLE(无IDLE)
- newCachedThreadPool
使用注意事项:
-
线程池本身用法很简单,但是如果想用好,需要考虑的东西很多。比如每个任务执行结果如何。线程池有没有正常关闭等。比如仍然是上面的例子,还有完善的余地:
public class ThreadTest { private Logger logger = LoggerFactory.getLogger(ThreadTest.class); public static void main(String[] args){ new ThreadTest().test(); } public void test() { String dirName = "/home/public/test/"; File dir = new File(dirName); File[] files = dir.listFiles(new FileFilter() { @Override public boolean accept(File file) { if(file.getName().endsWith(".txt")){ return true; } return false; } }); ExecutorService executor = Executors.newFixedThreadPool(5); final List<String> badFiles = new ArrayList<String>(); for(final File file:files){ executor.execute(new Runnable() { @Override public void run() { try { String fileName= file.getName(); List<String> lines = Files.readLines(file, Charset.defaultCharset()); for(int i=0;i<lines.size();i++) { logger.info("读取文件: {} 的第 {} 行的内容为:{}" , fileName, i, lines.get(i)); } } catch (IOException e) { logger.error("处理文件:{}失败,",file.getName(),e); badFiles.add(file.getName()); } } }); } executor.shutdown(); try { executor.awaitTermination(1, TimeUnit.DAYS); } catch (InterruptedException e) { logger.error("等待线程池结束时被中断", e); } if (!badFiles.isEmpty()) { logger.info("处理数据时有错误出现,详情请检查日志. 出错的文件如下: {}", Joiner.on("\n").join(badFiles)); } } }
- 例子中代码使用了google的guava库。如果你不知到这个库。那么…刚快试试吧,做java开发不用这个库,简直是浪费了。
- 下次分享一些日常开发常用的库。