Java定时器相关Timer和TimerTask类
每个Timer对象相对应的是单个后台线程,用于顺序地执行所有计时器任务TimerTask对象。
Timer有两种执行任务的模式,最常用的是schedule,它可以以两种方式执行任务:1:在某个时间(Data),2:在某个固定的时间之后(long delay),都可以指定任务执行的固定延迟(long period)。
另一种是scheduleAtFixedRate,它可以在1:在某个时间(Data),2:在某个固定的时间之后(long delay),以固定的频率(long period)执行任务。
在固定延迟执行中,根据前一次执行的实际执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则后续执行也将被延迟。
在固定速率执行中,相对于已安排的初始执行时间来安排每次执行。如果由于任何原因(如垃圾回收或其他后台活动)而延迟了某次执行,则将快速连续地出现两次或更多次执行,从而使后续执行能够赶上来。
区别在于,如果指定开始执行的时间在当前系统运行时间之前,scheduleAtFixedRate会把已经过去的时间也作为周期执行,而schedule不会把过去的时间算上。
TimerTask为抽象类,由子类覆写run方法实现计时器任务要执行的操作。
实例:
1 import java.util.*; 2 public class TimerTest { 3 4 int count =0; 5 6 public static void main(String[] args){ 7 8 new Timer().schedule(new TimerTest().new MyTimerTask(), 2000); 9 10 while(true){ 11 //System.out.println(new Date().getSeconds()); 12 System.out.println(new GregorianCalendar().get(Calendar.SECOND)); 13 try { 14 Thread.sleep(1000); 15 }catch (InterruptedException e){ 16 e.printStackTrace(); 17 } 18 } 19 } 20 class MyTimerTask extends TimerTask{ 21 22 public void run(){ 23 count =(count+1)%2; 24 System.out.println("Attention!bomb!"); 25 new Timer().schedule(new MyTimerTask(), 2000+2000*count); 26 } 27 } 28 }
线程范围内共享变量——ThreadLocal
API描述:ThreadLocal 实例通常是类中的 private static 字段,它们希望将状态与某一个线程(例如,用户 ID 或事务 ID)相关联。
ThreadLocal用来隔离线程间的变量访问和修改。
Java提供的synchronized关键字使用了“同步锁”的机制来阻止线程的竞争访问,即“以时间换空间”。ThreadLocal则使用了“拷贝副本”的方式,人人有份,你用你的,我用我的,大家互不影响,是“以空间换时间”。每个线程修改变量时,实际上修改的是变量的副本,不怕影响到其它线程。
原理:将该ThreadLocal实例作为key,要保持的对象的引用作为value,通过ThreadLocal.set()设置到当前线程的ThreadLocalMap中,执行 ThreadLocal.get()时,各线程从ThreadLocalMap中取出放进去的对象,因此取出来的是各自自己线程中的对象。
1 import java.util.*; 2 public class ThreadLocalDemo implements Runnable { 3 //创建线程局部变量studentLocal 4 private final static ThreadLocal<Student> studentLocal = new ThreadLocal<Student>(); 5 6 public static void main(String[] agrs) { 7 8 ThreadLocalDemo td = new ThreadLocalDemo(); 9 new Thread(td).start(); 10 new Thread(td).start(); 11 } 12 13 public void run() { 14 15 System.out.println(Thread.currentThread().getName() + " is running!"); 16 int age = new Random().nextInt(100); 17 18 System.out.println(Thread.currentThread().getName() + " set age to:" + age); 19 20 Student student = getStudent(); 21 student.setAge(age); 22 System.out.println(Thread.currentThread().getName() + " first read age is:" + student.getAge()); 23 24 try { 25 Thread.sleep(2000); 26 } 27 catch (InterruptedException ex) { 28 ex.printStackTrace(); 29 } 30 System.out.println(Thread.currentThread().getName() + " second read age is:" + student.getAge()); 31 32 } 33 34 protected Student getStudent() { 35 //获取本地线程变量并强制转换为Student类型 36 Student student = (Student) studentLocal.get(); 37 //线程首次执行此方法的时候,studentLocal.get()肯定为null 38 if (student == null) { 39 //创建一个Student对象,并保存到本地线程变量studentLocal中 40 student = new Student(); 41 studentLocal.set(student); 42 } 43 return student; 44 } 45 } 46 //被多线程操纵的javabean 47 public class Student { 48 private int age = 0; 49 50 public int getAge() { 51 return this.age; 52 } 53 54 public void setAge(int age) { 55 this.age = age; 56 } 57 } 58 ---------- 运行 ---------- 59 Thread-0 is running! 60 Thread-1 is running! 61 Thread-1 set age to:57 62 Thread-1 first read age is:57 63 Thread-0 set age to:44 64 Thread-0 first read age is:44 65 Thread-1 second read age is:57 66 Thread-0 second read age is:44 67 68 输出完成 (耗时 2 秒) - 正常终止
线程池
线程池是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。作用就是限制系统中执行线程的数量。线程池根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果。
合理利用线程池能够带来三个好处:
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
第三:提高线程的可管理性。如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
newSingleThreadExecutor
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。
例如:ExecutorService pool = Executors.newSingleThreadExecutor();
newFixedThreadPool
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。
例如:ExecutorService pool = Executors.newFixedThreadPool(2);
newCachedThreadPool
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。
例如:ExecutorService pool = Executors.newCachedThreadPool();
newScheduledThreadPool
创建一个延迟连接的线程池。此线程池可安排在给定延迟后运行命令或者定期地执行。
例如:
ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
pool.scheduleAtFixedRate(new Runnable() {
public void run() {
System.out.println(System.currentTimeMillis());
}}
, 1000
, 2000
, TimeUnit.MILLISECONDS);
Callable和Future接口
Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可被其它线程执行的任务。
Callable和Runnable有几点不同:
- Callable规定的方法是call(),而Runnable规定的方法是run()。
- Callable的任务执行后可返回值,而Runnable的任务是不能返回值的。
- call()方法可抛出异常,而run()方法是不能抛出异常的。
- 运行Callable任务可拿到一个Future对象 。
Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可了解任务执行情况isCancelled和isDone,可使用cancel方法取消任务的执行,还可使用get方法获取任务执行的结果。
1 import java.util.concurrent.*; 2 import java.util.*; 3 public class Test { 4 5 public static void main(String[] args) throws Exception{ 6 7 int taskSize = 5; 8 //创建一个线程池 9 ExecutorService pool = Executors.newFixedThreadPool(taskSize); 10 //创建多个有返回值的任务 11 List<Future> list = new ArrayList<Future>(); 12 for (int i = 0; i < taskSize; i++) { 13 Callable<Object> c = new MyCallable(i); 14 //执行任务并获取Future对象 15 Future<Object> f = pool.submit(c); 16 list.add(f); 17 } 18 // 关闭线程池 19 pool.shutdown(); 20 21 // 获取所有并发任务的运行结果 22 for (Future f : list) { 23 //从Future对象上获取任务的返回值,并输出到控制台 24 System.out.println(f.get()); 25 } 26 } 27 } 28 29 class MyCallable implements Callable<Object> { 30 private int taskNum; 31 32 MyCallable(int taskNum) { 33 this.taskNum = taskNum; 34 } 35 36 public Object call() throws Exception { 37 System.out.println(">>>" + taskNum + "任务启动"); 38 long start =System.currentTimeMillis(); 39 40 Thread.sleep(1000); 41 42 long end = System.currentTimeMillis(); 43 44 System.out.println(">>>>>>" + taskNum + "任务终止"); 45 return taskNum + "任务返回运行结果,耗时【" + (end - start) + "毫秒】"; 46 } 47 } 48 49 ---------- 运行 ---------- 50 >>>0任务启动 51 >>>2任务启动 52 >>>1任务启动 53 >>>3任务启动 54 >>>4任务启动 55 >>>>>>0任务终止 56 0任务返回运行结果,耗时【1001毫秒】 57 >>>>>>2任务终止 58 >>>>>>3任务终止 59 >>>>>>1任务终止 60 1任务返回运行结果,耗时【1001毫秒】 61 2任务返回运行结果,耗时【1001毫秒】 62 3任务返回运行结果,耗时【1001毫秒】 63 >>>>>>4任务终止 64 4任务返回运行结果,耗时【1001毫秒】 65 66 输出完成 (耗时 1 秒) - 正常终止
读写锁
分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥。
1 import java.util.Random; 2 import java.util.concurrent.locks.*; 3 4 public class ReadWriteLockTest { 5 6 public static void main(String[] args) { 7 final TheData myData=new TheData(); 8 for(int i=0;i<3;i++){//分别开启3个线程操作读写 9 new Thread(new Runnable(){ 10 public void run() { 11 while(true){ 12 myData.get(); 13 } 14 } 15 }).start(); 16 17 new Thread(new Runnable(){ 18 public void run() { 19 while(true){ 20 myData.put(new Random().nextInt(1000)); 21 } 22 } 23 }).start(); 24 } 25 } 26 } 27 class TheData{ 28 private Object data=null; 29 private ReadWriteLock rwl=new ReentrantReadWriteLock(); 30 public void get(){ 31 rwl.readLock().lock(); //读锁开启,读线程均可进入 32 try { 33 System.out.println(Thread.currentThread().getName()+"准备读取---------"); 34 Thread.sleep(1000); 35 System.out.println(Thread.currentThread().getName()+"已经取到---------"+data); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } finally{ 39 rwl.readLock().unlock(); 40 } 41 } 42 43 public void put(Object data){ 44 rwl.writeLock().lock(); //写锁开启,这时只有一个写线程进入 45 try { 46 System.out.println(Thread.currentThread().getName()+"准备写入>>>>>>>>>"); 47 Thread.sleep(1000); 48 this.data=data; 49 System.out.println(Thread.currentThread().getName()+"已经写入>>>>>>>>>"+data); 50 } catch (InterruptedException e) { 51 e.printStackTrace(); 52 } finally{ 53 rwl.writeLock().unlock(); 54 } 55 } 56 } 57 58 ---------- 运行 ---------- 59 Thread-0准备读取--------- 60 Thread-2准备读取--------- 61 Thread-0已经取到---------null 62 Thread-2已经取到---------null 63 Thread-1准备写入>>>>>>>>> 64 Thread-1已经写入>>>>>>>>>796 65 Thread-3准备写入>>>>>>>>> 66 Thread-3已经写入>>>>>>>>>237 67 Thread-3准备写入>>>>>>>>> 68 Thread-3已经写入>>>>>>>>>353 69 Thread-5准备写入>>>>>>>>> 70 Thread-5已经写入>>>>>>>>>646 71 Thread-5准备写入>>>>>>>>> 72 Thread-5已经写入>>>>>>>>>84 73 Thread-4准备读取--------- 74 Thread-0准备读取--------- 75 Thread-2准备读取--------- 76 Thread-0已经取到---------84 77 Thread-2已经取到---------84 78 Thread-4已经取到---------84 79 Thread-1准备写入>>>>>>>>> 80 Thread-1已经写入>>>>>>>>>898 81 Thread-1准备写入>>>>>>>>> 82 83 输出完成 (耗时 9 秒) - 已被用户取消。
Semaphore
使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许的并发访问数。
Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项。
1 import java.util.concurrent.*; 2 public class SemaphoreTest { 3 public static void main(String[] args) { 4 ExecutorService service= Executors.newCachedThreadPool(); 5 final Semaphore sp = new Semaphore(5); 6 for(int i=0;i<10;i++){ 7 Runnable r = new Runnable(){ 8 public void run(){ 9 try { 10 sp.acquire(); 11 } catch (InterruptedException e1) { 12 e1.printStackTrace(); 13 } 14 System.out.println("线程" + Thread.currentThread().getName() + 15 "进入,当前已有" + (5-sp.availablePermits()) + "个并发"); 16 try { 17 Thread.sleep(2000); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 System.out.println("线程" + Thread.currentThread().getName() + 22 "即将离开"); 23 sp.release(); 24 //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元 25 System.out.println("线程" + Thread.currentThread().getName() + 26 "已离开,当前已有" + (5-sp.availablePermits()) + "个并发"); 27 } 28 }; 29 service.execute(r); 30 } 31 service.shutdown(); 32 } 33 } 34 ---------- 运行 ---------- 35 线程pool-1-thread-1进入,当前已有1个并发 36 线程pool-1-thread-4进入,当前已有3个并发 37 线程pool-1-thread-2进入,当前已有2个并发 38 线程pool-1-thread-3进入,当前已有4个并发 39 线程pool-1-thread-6进入,当前已有5个并发 40 线程pool-1-thread-4即将离开 41 线程pool-1-thread-8进入,当前已有5个并发 42 线程pool-1-thread-4已离开,当前已有5个并发 43 线程pool-1-thread-2即将离开 44 线程pool-1-thread-10进入,当前已有5个并发 45 线程pool-1-thread-2已离开,当前已有5个并发 46 线程pool-1-thread-6即将离开 47 线程pool-1-thread-6已离开,当前已有4个并发 48 线程pool-1-thread-3即将离开 49 线程pool-1-thread-3已离开,当前已有3个并发 50 线程pool-1-thread-5进入,当前已有4个并发 51 线程pool-1-thread-1即将离开 52 线程pool-1-thread-1已离开,当前已有3个并发 53 线程pool-1-thread-7进入,当前已有4个并发 54 线程pool-1-thread-9进入,当前已有5个并发 55 线程pool-1-thread-5即将离开 56 线程pool-1-thread-10即将离开 57 线程pool-1-thread-10已离开,当前已有4个并发 58 线程pool-1-thread-8即将离开 59 线程pool-1-thread-8已离开,当前已有3个并发 60 线程pool-1-thread-5已离开,当前已有2个并发 61 线程pool-1-thread-7即将离开 62 线程pool-1-thread-7已离开,当前已有1个并发 63 线程pool-1-thread-9即将离开 64 线程pool-1-thread-9已离开,当前已有0个并发 65 66 输出完成 (耗时 4 秒) - 正常终止
同步工具类
CyclicBarrier
一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时CyclicBarrier很有用。因为该barrier 在释放等待线程后可以重用,所以称它为循环的barrier。比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候就可以选择CyclicBarrier了。
int |
await() 在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。 |
int |
await(long timeout,
TimeUnit unit)
在所有参与者都已经在此屏障上调用 await 方法之前将一直等待,或者超出了指定的等待时间。 |
1 import java.util.concurrent.*; 2 3 public class CyclicBarrierTest { 4 5 public static void main(String[] args) { 6 ExecutorService service = Executors.newCachedThreadPool(); 7 final CyclicBarrier cb = new CyclicBarrier(3); 8 for(int i=0;i<3;i++){ 9 Runnable runnable = new Runnable(){ 10 public void run(){ 11 try { 12 Thread.sleep((long)(Math.random()*10000)); 13 System.out.println("线程" + Thread.currentThread().getName() + 14 "即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); 15 cb.await(); 16 17 Thread.sleep((long)(Math.random()*10000)); 18 System.out.println("线程" + Thread.currentThread().getName() + 19 "即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); 20 cb.await(); 21 Thread.sleep((long)(Math.random()*10000)); 22 System.out.println("线程" + Thread.currentThread().getName() + 23 "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候")); 24 cb.await(); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 } 29 }; 30 service.execute(runnable); 31 } 32 service.shutdown(); 33 } 34 } 35 ---------- 运行 ---------- 36 线程pool-1-thread-1即将到达集合地点1,当前已有1个已经到达,正在等候 37 线程pool-1-thread-2即将到达集合地点1,当前已有2个已经到达,正在等候 38 线程pool-1-thread-3即将到达集合地点1,当前已有3个已经到达,都到齐了,继续走啊 39 线程pool-1-thread-1即将到达集合地点2,当前已有1个已经到达,正在等候 40 线程pool-1-thread-2即将到达集合地点2,当前已有2个已经到达,正在等候 41 线程pool-1-thread-3即将到达集合地点2,当前已有3个已经到达,都到齐了,继续走啊 42 线程pool-1-thread-2即将到达集合地点3,当前已有1个已经到达,正在等候 43 线程pool-1-thread-3即将到达集合地点3,当前已有2个已经到达,正在等候 44 线程pool-1-thread-1即将到达集合地点3,当前已有3个已经到达,都到齐了,继续走啊 45 46 输出完成 (耗时 17 秒) - 正常终止
CountDownLatch
一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。
方法摘要 | |
---|---|
void |
await() 使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断。 |
boolean |
await(long timeout,
TimeUnit unit)
使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。 |
void |
countDown()
递减锁存器的计数,如果计数到达零,则释放所有等待的线程。 |
long |
getCount()
返回当前计数。 |
1 import java.util.concurrent.CountDownLatch; 2 import java.util.concurrent.CyclicBarrier; 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 6 public class CountdownLatchTest { 7 8 public static void main(String[] args) { 9 ExecutorService service = Executors.newCachedThreadPool(); 10 final CountDownLatch cdOrder = new CountDownLatch(1); 11 final CountDownLatch cdAnswer = new CountDownLatch(3); 12 for(int i=0;i<3;i++){ 13 Runnable runnable = new Runnable(){ 14 public void run(){ 15 try { 16 System.out.println("线程" + Thread.currentThread().getName() + 17 "正准备接受命令"); 18 cdOrder.await(); 19 System.out.println("线程" + Thread.currentThread().getName() + 20 "已接受命令"); 21 Thread.sleep((long)(Math.random()*10000)); 22 System.out.println("线程" + Thread.currentThread().getName() + 23 "回应命令处理结果"); 24 cdAnswer.countDown(); 25 } catch (Exception e) { 26 e.printStackTrace(); 27 } 28 } 29 }; 30 service.execute(runnable); 31 } 32 try { 33 Thread.sleep((long)(Math.random()*10000)); 34 35 System.out.println("线程" + Thread.currentThread().getName() + 36 "即将发布命令"); 37 cdOrder.countDown(); 38 System.out.println("线程" + Thread.currentThread().getName() + 39 "已发送命令,正在等待结果"); 40 cdAnswer.await(); 41 System.out.println("线程" + Thread.currentThread().getName() + 42 "已收到所有响应结果"); 43 } catch (Exception e) { 44 e.printStackTrace(); 45 } 46 service.shutdown(); 47 48 } 49 } 50 ---------- 运行 ---------- 51 线程pool-1-thread-1正准备接受命令 52 线程pool-1-thread-3正准备接受命令 53 线程pool-1-thread-2正准备接受命令 54 线程main即将发布命令 55 线程pool-1-thread-1已接受命令 56 线程pool-1-thread-3已接受命令 57 线程main已发送命令,正在等待结果 58 线程pool-1-thread-2已接受命令 59 线程pool-1-thread-1回应命令处理结果 60 线程pool-1-thread-2回应命令处理结果 61 线程pool-1-thread-3回应命令处理结果 62 线程main已收到所有响应结果 63 64 输出完成 (耗时 10 秒) - 正常终止
Exchanger
提供了一个可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给 exchange
方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。
1 import java.util.concurrent.Exchanger; 2 import java.util.concurrent.ExecutorService; 3 import java.util.concurrent.Executors; 4 5 public class ExchangerTest { 6 7 public static void main(String[] args) { 8 ExecutorService service = Executors.newCachedThreadPool(); 9 final Exchanger exchanger = new Exchanger(); 10 service.execute(new Runnable(){ 11 public void run() { 12 try { 13 14 String data1 = "111"; 15 System.out.println("线程" + Thread.currentThread().getName() + 16 "正在把数据" + data1 +"换出去"); 17 Thread.sleep((long)(Math.random()*10000)); 18 String data2 = (String)exchanger.exchange(data1); 19 System.out.println("线程" + Thread.currentThread().getName() + 20 "换回的数据为" + data2); 21 }catch(Exception e){ 22 23 } 24 } 25 }); 26 service.execute(new Runnable(){ 27 public void run() { 28 try { 29 30 String data1 = "222"; 31 System.out.println("线程" + Thread.currentThread().getName() + 32 "正在把数据" + data1 +"换出去"); 33 Thread.sleep((long)(Math.random()*10000)); 34 String data2 = (String)exchanger.exchange(data1); 35 System.out.println("线程" + Thread.currentThread().getName() + 36 "换回的数据为" + data2); 37 }catch(Exception e){ 38 39 } 40 } 41 }); 42 service.shutdown(); 43 } 44 } 45 ---------- 运行 ---------- 46 线程pool-1-thread-1正在把数据111换出去 47 线程pool-1-thread-2正在把数据222换出去 48 线程pool-1-thread-2换回的数据为111 49 线程pool-1-thread-1换回的数据为222 50 51 输出完成 (耗时 5 秒) - 正常终止