xscn

博客园 首页 新随笔 联系 订阅 管理

 

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 秒) - 正常终止

 

 

 




 

 

 

 

 

 

 

 

 

 

posted on 2013-08-28 17:56  xscn  阅读(294)  评论(0编辑  收藏  举报