我们都知道,在JDK1.5之前,java中要进行业务并发时,通常需要有程序员独立完成代码实现,当然也有一些开源的框架提供了这些功能,但是这些依然没有JDK自带的功能使用起来方便。而当针对高质量Java多线程并发程序设计时,为防止死蹦等现象的出现,比如使用java之前的wait()、notify()和synchronized等,每每需要考虑性能、死锁、公平性、资源管理以及如何避免线程安全性方面带来的危害等诸多因素,往往会采用一些较为复杂的安全策略,加重了程序员的开发负担.万幸的是,在JDK1.5出现之后,Sun大神(Doug Lea)终于为我们这些可怜的小程序员推出了java.util.concurrent工具包以简化并发完成。开发者们借助于此,将有效的减少竞争条件(race conditions)和死锁线程。concurrent包很好的解决了这些问题,为我们提供了更实用的并发程序模型。
Executor :具体Runnable任务的执行者。 ExecutorService :一个线程池管理者,其实现类有多种。我们能把Runnable,Callable提交到池中让其调度。 Semaphore :一个计数信号量 ReentrantLock :一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。 Future :是与Runnable,Callable进行交互的接口,比如一个线程执行结束后取返回的结果等等,还提供了cancel终止线程。 BlockingQueue :阻塞队列。 CompletionService : ExecutorService的扩展,可以获得线程执行结果的 CountDownLatch :一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 CyclicBarrier :一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 Future :Future 表示异步计算的结果。 ScheduledExecutorService :一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。
newFixedThreadPool(固定大小线程池):创建一个可重用固定线程集合的线程池,以共享的无界队列方式来运行这些线程(只有要请求的过来,就会在一个队列里等待执行)。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。
newCachedThreadPool(无界线程池,可以进行自动线程回收):创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能。调用 execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。注意,可以使用 ThreadPoolExecutor 构造方法创建具有类似属性但细节不同(例如超时参数)的线程池。
newSingleThreadExecutor(单个后台线程):创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool(1) 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(4); for (int i = 0 ; i < 5;i++){ service.execute(new WorkerExecutor(i)); } }}class WorkerExecutor extends Thread{ PRivate int mIndex; public WorkerExecutor(int index){ this.mIndex = index; } @Override public void run() { super.run(); try { System.out.println("Worker:["+this.mIndex+"]--->start...."); Thread.sleep((int)(Math.random()*10000)); System.out.println("Worker:["+this.mIndex+"]--->end...."); } catch (InterruptedException e) { e.printStackTrace(); } }}打印如下:
Worker:[1]--->start....Worker:[0]--->start....Worker:[2]--->start....Worker:[3]--->start....Worker:[1]--->end....Worker:[4]--->start....Worker:[0]--->end....Worker:[2]--->end....Worker:[4]--->end....Worker:[3]--->end....上面代码只能执行4个线程,如果4个线程都有任务那么之后的只能进入队列等待也就是说,我们将所有的线程提交后,线程池会等待执行完最后shutdown。我们也会发现,提交的线程被放到一个“无界队列里”。这是一个有序队列(BlockingQueue,这个下面会说到)。另外它使用了Executors的静态函数生成一个固定的线程池,顾名思义,线程池的线程是不会释放的,即使它是Idle。这就会产生性能问题,比如如果线程池的大小为200,当全部使用完毕后,所有的线程会继续留在池中,相应的内存和线程切换(while(true)+sleep循环)都会增加。如果要避免这个问题,就必须直接使用ThreadPoolExecutor()来构造。可以像通用的线程池一样设置“最大线程数”、“最小线程数”和“空闲线程keepAlive的时间”。
int corePoolSize = 4; int maximumPoolSize = 10; long keepAliveTime = 60; ArrayBlockingQueue<Runnable> blockingQueue = new ArrayBlockingQueue<>(maximumPoolSize); ExecutorService service = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.SECONDS, blockingQueue); for (int i = 0; i < 4; i++) { service.execute(new WorkerExecutor(i)); }(ThreadPoolExecutor 详细介绍:http://blog.csdn.net/wangwenhui11/article/details/6760474)
支持两个附加操作的 Queue,这两个操作是:检索元素时等待队列变为非空,以及存储元素时等待空间变得可用。BlockingQueue 不接受 null 元素。试图 add、put 或 offer 一个 null 元素时,某些实现会抛出 NullPointerException。null 被用作指示 poll 操作失败的警戒值。BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put 额外的元素。没有任何内部容量约束的 BlockingQueue 总是报告 Integer.MAX_VALUE 的剩余容量。BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持 Collection 接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常不会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁定或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection 操作(addAll、containsAll、retainAll 和 removeAll)没有必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了c中的一些元素后,addAll(c) 有可能失败(抛出一个异常)。BlockingQueue 实质上不支持使用任何一种“close”或“shutdown”操作来指示不再添加任何项。这种功能的需求和使用有依赖于实现的倾向。例如,一种常用的策略是:对于生产者,插入特殊的 end-of-stream 或 poison 对象,并根据使用者获取这些对象的时间来对它们进行解释。
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i)); } Thread thread = new Thread() { @Override public void run() { super.run(); try { while (true) { Thread.sleep((int) (Math.random() * 1000)); if (WorkerExecutor.mBlockingQueue.isEmpty()) break; String str = WorkerExecutor.mBlockingQueue.take(); System.out.println(str + " has take!"); } } catch (InterruptedException e) { e.printStackTrace(); } } }; service.submit(thread); service.shutdown(); }}class WorkerExecutor extends Thread { public static BlockingQueue<String> mBlockingQueue = new LinkedBlockingQueue<>(3); private int mIndex; public WorkerExecutor(int index) { this.mIndex = index; } @Override public void run() { super.run(); try { mBlockingQueue.put(String.valueOf(mIndex)); System.out.println("{" + this.mIndex + "} in queue!"); } catch (InterruptedException e) { e.printStackTrace(); } }}执行结果如下:
{1} in queue!{2} in queue!{0} in queue!0 has take!{3} in queue!1 has take!{4} in queue!2 has take!{5} in queue!3 has take!{6} in queue!4 has take!{7} in queue!5 has take!{8} in queue!6 has take!{9} in queue!7 has take!8 has take!9 has take!Semaphore是一个计数信号量,它的本质是一个”共享锁”。 信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可 Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); Semaphore semaphore = new Semaphore(2); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i+1,semaphore)); } service.shutdown(); semaphore.acquireUninterruptibly(2);//从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。 System.out.println("全部使用完毕"); semaphore.release(); }}class WorkerExecutor extends Thread { private Semaphore position; private int id; public WorkerExecutor(int id, Semaphore pos) { this.id = id; this.position = pos; } @Override public void run() { super.run(); try { if (position.availablePermits() > 0) {//返回此信号量中当前可用的许可数。 System.out.println("worker"+this.id + ": 有可用许可"); } else { System.out.println("worker"+this.id + ": 没有可用许可 等待"); } position.acquire();// 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。 System.out.println("worker"+this.id+":获得许可"); Thread.sleep((int)(Math.random()*1000)); System.out.println("worker"+this.id+":使用完毕 释放许可"); position.release();//释放一个许可,将其返回给信号量 } catch (Exception e) { e.printStackTrace(); } }}运行结果如下:
worker1: 有可用许可worker1:获得许可worker2: 有可用许可worker2:获得许可worker3: 没有可用许可 等待worker4: 没有可用许可 等待worker5: 没有可用许可 等待worker6: 没有可用许可 等待worker7: 没有可用许可 等待worker8: 没有可用许可 等待worker9: 没有可用许可 等待worker10: 没有可用许可 等待worker2:使用完毕 释放许可worker3:获得许可worker1:使用完毕 释放许可worker4:获得许可worker3:使用完毕 释放许可worker5:获得许可worker4:使用完毕 释放许可worker6:获得许可worker6:使用完毕 释放许可worker7:获得许可worker5:使用完毕 释放许可worker8:获得许可worker7:使用完毕 释放许可worker9:获得许可worker9:使用完毕 释放许可worker10:获得许可worker8:使用完毕 释放许可worker10:使用完毕 释放许可全部使用完毕ReentrantLock可以等同于synchronized使用、但是它比synchronized有更强的功能、可以提供更灵活的锁机制、同时减少死锁的发生概率。我们平时用用synchronized也就够了、但是要写好一个复杂的多线程系统、为了提供更灵活的同步机制、就需要用到ReentrantLock了。 ReentrantLock 将由最近成功获得锁定,并且还没有释放该锁定的线程所拥有。当锁定没有被另一个线程所拥有时,调用 lock 的线程将成功获取该锁定并返回。如果当前线程已经拥有该锁定,此方法将立即返回。可以使用 isHeldByCurrentThread() 和 getHoldCount() 方法来检查此情况是否发生。 此类的构造方法接受一个可选的公平参数。当设置为true时,在多个线程的争用下,这些锁定倾向于将访问权授予等待时间最长的线程。否则此锁定将无法保证任何特定访问顺序。与采用默认设置(使用不公平锁定)相比,使用公平锁定的程序在许多线程访问时表现为很低的总体吞吐量(即速度很慢,常常极其慢),但是在获得锁定和保证锁定分配的均衡性时差异较小。不过要注意的是,公平锁定不能保证线程调度的公平性。因此,使用公平锁定的众多线程中的一员可能获得多倍的成功机会,这种情况发生在其他活动线程没有被处理并且目前并未持有锁定时。还要注意的是,未定时的 tryLock 方法并没有使用公平设置。因为即使其他线程正在等待,只要该锁定是可用的,此方法就可以获得成功。
以上几个特点使ReentrantLock相比synchronized的锁粒度更细、使用起来更灵活、可以满足更多的功能。 示例如下
public class Test { public static void main(String[] args) { ExecutorService service = Executors.newCachedThreadPool(); TestReentrantLock lock = new TestReentrantLock(); for (int i = 0; i < 10; i++) { service.submit(new WorkerExecutor(i + 1, lock)); } service.shutdown(); }}class WorkerExecutor extends Thread { private TestReentrantLock mLock; private int id; public WorkerExecutor(int id, TestReentrantLock lock) { this.id = id; this.mLock = lock; } @Override public void run() { super.run(); mLock.print(id); }}class TestReentrantLock { private ReentrantLock mLock = new ReentrantLock(); public void print(int id) { try { mLock.lock(); System.out.println(id + "获得"); Thread.sleep((int) (Math.random() * 1000)); } catch (Exception e) { e.printStackTrace(); } finally { System.out.println(id + "释放"); mLock.unlock(); } }}当向Executor提交批处理任务时,并且希望在它们完成后获得结果,如果用FutureTask,你可以循环获取task,并用future.get()去获取结果,但是如果这个task没有完成,你就得阻塞在这里,这个实效性不高,其实在很多场合,其实你拿第一个任务结果时,此时结果并没有生成并阻塞,其实在阻塞在第一个任务时,第二个task的任务已经早就完成了,显然这种情况用future task不合适的,效率也不高。
自己维护list和CompletionService的区别:
从list中遍历的每个Future对象并不一定处于完成状态,这时调用get()方法就会被阻塞住,如果系统是设计成每个线程完成后就能根据其结果继续做后面的事,这样对于处于list后面的但是先完成的线程就会增加了额外的等待时间。CompletionService采取的是BlockingQueue无界队列来管理Future。则有一个线程执行完毕把返回结果放到BlockingQueue里面。就可以通过completionServcie.take().get()取出结果。方法区别:
take 方获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。<如果需要用到返回值建议用take>poll 获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回null。以下是jdk关于CompletionService的简介:
public interface CompletionService将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit 执行的任务。使用者 take 已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService 可以用来管理异步 IO ,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。通常,CompletionService 依赖于一个单独的 Executor 来实际执行任务,在这种情况下,CompletionService 只管理一个内部完成队列。ExecutorCompletionService 类提供了此方法的一个实现。 内存一致性效果:线程中向 CompletionService 提交任务之前的操作 happen-before 该任务执行的操作,后者依次 happen-before 紧跟在从对应 take() 成功返回的操作。 public class Test { public static class Task implements Callable<Integer> { private int i; public Task(int i) { this.i = i; } @Override public Integer call() throws Exception { Thread.sleep(new Random().nextInt(5000)); System.out.println(Thread.currentThread().getName() + " " + i); return i; } } public void run() { ExecutorService pool = Executors.newFixedThreadPool(10); CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(pool); try { for (int i = 0; i < 10; i++) completionService.submit(new Task(i)); for (int i = 0; i < 10; i++) System.out.println(completionService.take().get()); } catch (Exception e) { e.printStackTrace(); } finally { pool.shutdown(); } } public static void main(String[] args) throws Exception { new Test().run(); }}运行结果
pool-1-thread-6 55pool-1-thread-7 66pool-1-thread-3 22pool-1-thread-8 77pool-1-thread-1 00pool-1-thread-2 11pool-1-thread-9 88pool-1-thread-5 44pool-1-thread-10 99pool-1-thread-4 33从结果中不难看出。只要有一个线程执行完毕后,主程序就立马获取结果。
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用 await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N 个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await。
public class Test { public static void main(String[] args) throws Exception { VideoConference conference = new VideoConference(10); Thread threadConderence = new Thread(conference); threadConderence.start(); for (int i = 0; i < 10; i++) { Participant participant = new Participant(conference, "Participant " + i); Thread thread = new Thread(participant); thread.start(); } }}class VideoConference implements Runnable { private final CountDownLatch mCountDownLatch; public VideoConference(int number) { mCountDownLatch = new CountDownLatch(number); } public void arrive(String name) { System.out.println(name + " has arrived."); mCountDownLatch.countDown(); System.out.println("VideoConference:Waiting for " + mCountDownLatch.getCount()); } @Override public void run() { System.out.println("VideoConference:Initialization:" + mCountDownLatch.getCount()); try { mCountDownLatch.await(); System.out.printf("VideoConference: All the participants have come/n"); System.out.printf("VideoConference: Let's start.../n"); } catch (InterruptedException e) { e.printStackTrace(); } }}class Participant implements Runnable { private VideoConference mVideoConference; private String name; public Participant(VideoConference conference, String name) { this.mVideoConference = conference; this.name = name; } @Override public void run() { long duration = (long) (Math.random() * 10); try { TimeUnit.SECONDS.sleep(duration); } catch (InterruptedException e) { e.printStackTrace(); } mVideoConference.arrive(name); }}运行结果如下
VideoConference:Initialization:10Participant 0 has arrived.VideoConference:Waiting for 9Participant 9 has arrived.VideoConference:Waiting for 8Participant 3 has arrived.Participant 8 has arrived.VideoConference:Waiting for 7VideoConference:Waiting for 6Participant 7 has arrived.VideoConference:Waiting for 5Participant 4 has arrived.VideoConference:Waiting for 4Participant 5 has arrived.VideoConference:Waiting for 3Participant 2 has arrived.VideoConference:Waiting for 2Participant 6 has arrived.VideoConference:Waiting for 1Participant 1 has arrived.VideoConference:Waiting for 0VideoConference: All the participants have comeVideoConference: Let's start...CountDownLatch最重要的方法是countDown()和await(),前者主要是倒数一次,后者是等待倒数到0,如果没有到达0,就只有阻塞等待了。
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。 示例用法:下面是一个在并行分解设计中使用 barrier 的例子,很经典的旅行团例子:
public class Test { // 徒步需要的时间: Shenzhen, Guangzhou, Shaoguan, Changsha, Wuhan private static int[] timeWalk = {5, 8, 15, 15, 10}; // 自驾游 private static int[] timeSelf = {1, 3, 4, 4, 5}; // 旅游大巴 private static int[] timeBus = {2, 4, 6, 6, 7}; static String now() { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); return sdf.format(new Date()) + ": "; } static class Tour implements Runnable { private int[] times; private CyclicBarrier barrier; private String tourName; public Tour(CyclicBarrier barrier, String tourName, int[] times) { this.times = times; this.tourName = tourName; this.barrier = barrier; } @Override public void run() { try { Thread.sleep(times[0] * 1000); System.out.println(now() + tourName + " Reached Shenzhen"); barrier.await(); Thread.sleep(times[1] * 1000); System.out.println(now() + tourName + " Reached Guangzhou"); barrier.await(); Thread.sleep(times[2] * 1000); System.out.println(now() + tourName + " Reached Shaoguan"); barrier.await(); Thread.sleep(times[3] * 1000); System.out.println(now() + tourName + " Reached Changsha"); barrier.await(); Thread.sleep(times[4] * 1000); System.out.println(now() + tourName + " Reached Wuhan"); barrier.await(); } catch (Exception e) { e.printStackTrace(); } } } public static void main(String[] args) { // 三个旅行团 CyclicBarrier barrier = new CyclicBarrier(3); ExecutorService exec = Executors.newFixedThreadPool(3); exec.submit(new Tour(barrier, "WalkTour", timeWalk)); exec.submit(new Tour(barrier, "SelfTour", timeSelf)); //当我们把下面的这段代码注释后,会发现,程序阻塞了,无法继续运行下去。 exec.submit(new Tour(barrier, "BusTour", timeBus)); exec.shutdown(); }}运行结果如下:
15:29:38: SelfTour Reached Shenzhen15:29:38: BusTour Reached Shenzhen15:29:41: WalkTour Reached Shenzhen15:29:44: SelfTour Reached Guangzhou15:29:45: BusTour Reached Guangzhou15:29:49: WalkTour Reached Guangzhou15:29:53: SelfTour Reached Shaoguan15:29:55: BusTour Reached Shaoguan15:30:04: WalkTour Reached Shaoguan15:30:08: SelfTour Reached Changsha15:30:10: BusTour Reached Changsha15:30:19: WalkTour Reached Changsha15:30:24: SelfTour Reached Wuhan15:30:26: BusTour Reached Wuhan15:30:29: WalkTour Reached WuhanCyclicBarrier最重要的属性就是参与者个数,另外最重要方法是await()。当所有线程都调用了await()后,就表示这些线程都可以继续执行,否则就会等待。
Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。计算完成后只能使用 get 方法来检索结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel 方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future
一个 ExecutorService,可安排在给定的延迟后运行或定期执行的命令。schedule 方法使用各种延迟创建任务,并返回一个可用于取消或检查执行的任务对象。scheduleAtFixedRate 和 scheduleWithFixedDelay 方法创建并执行某些在取消前一直定期运行的任务。用Executor.execute(java.lang.Runnable) 和 ExecutorService 的 submit 方法所提交的命令,通过所请求的 0 延迟进行安排。schedule 方法中允许出现 0 和负数延迟(但不是周期),并将这些视为一种立即执行的请求。所有的 schedule 方法都接受相对 延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个时刻以后的日期运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)。但是要注意,由于网络时间同步协议、时钟漂移或其他因素的存在,因此相对延迟的期满日期不必与启用任务的当前 Date 相符。Executors 类为此包中所提供的 ScheduledExecutorService 实现提供了便捷的工厂方法。
public class Test { public static void main(String[] args) { final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(2); final Runnable beeper = new Runnable() { int count = 0; @Override public void run() { System.out.println(new Date() + " beep " + (++count)); } }; // 1秒钟后运行,并每隔2秒运行一次 final ScheduledFuture beeperHandle = scheduler.scheduleWithFixedDelay(beeper, 2, 5, TimeUnit.SECONDS); // 2秒钟后运行,并每次在上次任务运行完后等待5秒后重新运行 final ScheduledFuture beeperHandle2 = scheduler.scheduleWithFixedDelay(beeper, 2, 5, TimeUnit.SECONDS); // 30秒后结束关闭任务,并且关闭Scheduler scheduler.schedule(new Runnable() { @Override public void run() { beeperHandle.cancel(true); beeperHandle2.cancel(true); scheduler.shutdown(); } }, 30, TimeUnit.SECONDS); }}运行结果:
Sat Feb 04 16:02:20 CST 2017 beep 2Sat Feb 04 16:02:20 CST 2017 beep 1Sat Feb 04 16:02:25 CST 2017 beep 3Sat Feb 04 16:02:25 CST 2017 beep 4Sat Feb 04 16:02:30 CST 2017 beep 5Sat Feb 04 16:02:30 CST 2017 beep 6Sat Feb 04 16:02:35 CST 2017 beep 7Sat Feb 04 16:02:35 CST 2017 beep 8Sat Feb 04 16:02:40 CST 2017 beep 9Sat Feb 04 16:02:40 CST 2017 beep 10Sat Feb 04 16:02:45 CST 2017 beep 11Sat Feb 04 16:02:45 CST 2017 beep 12以上转自: (http://blog.csdn.net/wulei_longhe/article/details/30032031)(blog.csdn.net/tsyj810883979/article/details/6956290) (http://hyxw5890.VEvb.com/blog/1578597) (https://my.oschina.net/jielucky/blog/158839)
新闻热点
疑难解答