线程同步的三种方法(Java 并发编程 concurrent包复习)
来源:互联网 发布:php reflection的作用 编辑:程序博客网 时间:2024/06/01 07:59
最近在项目里用到了多线程,包括线程池的创建,多个线程同步等,所以对executor框架简单复习一下。因为是简单复习,所以不会介绍太多概念,只是对一些基础知识点列举,并给出几个实际问题及其解决方法。
一、executor框架在java5引入,为并发编程提供了一堆新的启动、调度和管理线程的API。它在java.util.cocurrent包下,其内部使用了线程池机制,通过该框架来控制线程的启动、执行和关闭,可以简化并发编程的操作。因此,在Java 5之后,通过Executor来启动线程比使用Thread的start方法更好,更易管理,效率更好(用线程池实现,节约开销),它的主要内容包括:threadPool,Executor,Executors,ExecutorService,CompletionService,Future,Callable,以及CountDownLatch 等工具类。下面是一些基础概念:
1. Executor接口定义了一个execute(Runnable command)方法,接收一个Runnable实例。
2. ExecutorService接口继承自Executor接口,它提供了更丰富的实现多线程的方法,比如,ExecutorService提供了关闭自己的方法,以及可为跟踪一个或多个异步任务执行状况而生成 Future 的方法。 可以调用ExecutorService的shutdown()方法来平滑地关闭 ExecutorService,调用该方法后,将导致ExecutorService停止接受任何新的任务且等待已经提交的任务执行完成(已经提交的任务会分两类:一类是已经在执行的,另一类是还没有开始执行的),当所有已经提交的任务执行完毕后将会关闭ExecutorService。因此我们一般用该接口来实现和管理多线程。
3. Executors提供了一系列工厂方法用于创先线程池,返回的线程池都实现了ExecutorService接口。 其中两种是:
public static ExecutorService newFixedThreadPool(int nThreads)创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()创建一个可缓存的线程池,调用execute将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线 程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
关于使用哪一种,stackOverFlow上有很多问题回答。不解释了。
https://stackoverflow.com/questions/17957382/fixedthreadpool-vs-cachedthreadpool-the-lesser-of-two-evils
4. Java 5之后,任务分两类:一类是实现了Runnable接口的类,一类是实现了Callable接口的类。两者都可以被ExecutorService执行,两者的区别如下:
a. Runnable任务没有返回值,而Callable任务有返回值。并且Callable的call()方法只能通过ExecutorService的submit(Callable<T> task) 方法来执行,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。
b.Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable 不会返回结果,并且无法抛出经过检查的异常而Callable又返回结果,而且当获取返回结果时可能会抛出异常。Callable中的call()方法类似Runnable的run()方法,区别同样是有返回值,后者没有。
c.当将一个Callable的对象传递给ExecutorService的submit方法,则该call方法自动在一个线程上执行,并且会返回执行结果Future对象。同样,将Runnable的对象传递给ExecutorService的submit方法,则该run方法自动在一个线程上执行,并且会返回执行结果Future对象,但是在该Future对象上调用get方法,将返回null。
二。实际问题
问题1. 我们需要某件事准备好之后,开始执行一组任务。而且要这组任务都结束后,才进行后续动作。
解决方法: 使用CountDownLatch,它是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 用给定的计数初始化 CountDownLatch。每个被等待的工作线程完成后,调用了 countDown() 方法,计数器减1。在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回,不在阻塞。代码如下:
public String test1() { final int N = 3; CountDownLatch doneSignal = new CountDownLatch(N); CountDownLatch startSignal = new CountDownLatch(1);//开始执行信号 for (int i = 1; i <= N; i++) { new Thread(new Worker(i, doneSignal, startSignal)).start();//线程启动了 } System.out.println("begin------------"); startSignal.countDown();//开始执行啦 try { doneSignal.await();//这句使得主线程等待所有的线程执行完毕,才会继续往下走,输出OK } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Ok"); return "done"; }在test1里,当startSignal信号变为0时,for循环里的N个工作线程才开始执行。并且等这N个线程都执行结束后,主线程才能输出OK;worker代码如下:
class Worker implements Runnable { private final CountDownLatch doneSignal; private final CountDownLatch startSignal; private int beginIndex; Worker(int beginIndex, CountDownLatch doneSignal, CountDownLatch startSignal) { this.startSignal = startSignal; this.beginIndex = beginIndex; this.doneSignal = doneSignal; } public void run() { try { startSignal.await(); //等待开始执行信号的发布 beginIndex = (beginIndex - 1) * 2+ 1; for (int i = beginIndex; i <= beginIndex + 2; i++) { System.out.println(i); } } catch (InterruptedException e) { e.printStackTrace(); } finally { doneSignal.countDown();//调用countDown表示自己执行结束。共享计数减1 } }}问题2. 文件批量下载,每个文件通过一个线程下载,需要将所有文件都下载后,打包成zip压缩文件返回(文件上传下载会在后面介绍)。
解决方法: 此问题使用上面的countDownLatch同样可以解决,但是这次我们给出另外一种方法,使用Future.回顾概念:ExecutorService的submit(Callable<T> task) 方法执行一个Callbale实例,并且返回一个 <T>Future<T>,是表示任务等待完成的 Future。通过跟踪future,可以判断任务是否完成。关键代码如下:
List<File>fileAll=Lists.new ArrayList();...初始化fileAllList<Future<String>> futures=Lists.newArrayList();//跟踪每个任务的执行结果for(int i=0;i<fileAll.size();i++){ File e=fileAll.get( i ); String filename=e.getFilename(); String url = MessageFormat.format( downloadUrl, e.getUrl() ); FileDownloadTask fs=new FileDownloadTask( file,filename,url );//下载文件的任务 futures.add( scheduledExecutorComponent.submit(fs));//将任务返回加入列表。进行跟踪 } for (Future<String> fs : futures){//此循环跟踪每个任务的执行结果 try{ while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成 LOG.debug( "文件下载结果:"+documentid+":"+fs.get() ); }catch(Exception e){ e.printStackTrace(); } }
问题3 同问题1以及问题2的场景类似。只不过我们给出另外一种解决方法:invokeAll.关键代码如下:
public String test2() { List<Callable<Integer>> tasks = new ArrayList<Callable<Integer>>(); Callable<Integer> task = null; for (int i = 0; i < 5; i++) { task = new Callable<Integer>() { @Override public Integer call() throws Exception { int ran = new Random().nextInt(1000); Thread.sleep(ran); System.out.println(Thread.currentThread().getName()+" 执行了 " + ran ); return ran; } }; tasks.add(task); } long s = System.currentTimeMillis(); List<Future<Integer>> results = null; try { results = this.scheduledExecutorComponent.invokeAll(tasks); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("执行任务消耗了 :" + (System.currentTimeMillis() - s) +"毫秒"); for (int i = 0; i < results.size(); i++) { try { System.out.println(results.get(i).get());//3 } catch (Exception e) { e.printStackTrace(); } } return "ok";}
如果有一个任务执行失败,3初会报异常,所以,invokeAll 还可以结合ExecutorCompletionService来使用,通过一个blockingQueue来管理,一旦有线程执行失败,可以立即获得结果。具体请参考:https://stackoverflow.com/questions/18202388/how-to-use-invokeall-to-let-all-thread-pool-do-their-taski
invokeAll是阻塞方法,它必须等待所有的任务执行完成后统一返回,一方面内存持有的时间长;另一方面响应性也有一定的影响、所以对于问题场景,我们更倾向于使用前面两种方法。
- 线程同步的三种方法(Java 并发编程 concurrent包复习)
- java线程并发包util.concurrent的研究(三)
- java并发编程(三)----线程的同步
- JAVA并发编程 - concurrent包的使用
- java并发编程concurrent包
- java线程并发包util.concurrent的研究(一)
- java线程并发包util.concurrent的研究(二)
- java线程并发包util.concurrent的研究(四)
- java线程并发包util.concurrent的研究(五)
- java线程并发包util.concurrent的研究(六)
- java线程并发包util.concurrent的研究(七)
- Java并发编程三:并发(Concurrent)与并行(Parallel)的区别(一)
- Java.util.concurrent包学习(三)同步集合
- Java.util.concurrent包学习(二)线程同步控制相关的类
- Java.util.concurrent包学习(二)线程同步控制相关的类 (
- Java.util.concurrent包学习(二)线程同步控制相关的类
- java并发包concurrent
- java 并发 concurrent 包
- java 正则去除中文标点符号
- LeetCode 26: Remove Duplicates from Sorted Array
- HDU2063--过山车(二分匹配,二分图)
- ccpc预选赛-1005CaoHaha's staff
- typedef 和 define 的用法
- 线程同步的三种方法(Java 并发编程 concurrent包复习)
- Asp.Net core上传文件代码
- GIT和SVN比较
- Android中的子线程和服务的使用
- 线段树(一)
- VS2013下glew库链接失败问题OpenGL.obj : error LINK2001: 无法解析的外部符号 __imp____glewFramebufferTexture2DEXT
- [leedcode]-- 27. Remove Element
- 【链家笔试题】部队分组
- Struts2 拦截器 默认Action 一些常量的设置 Result常用的结果类型