Java 线程执行器 详解

来源:互联网 发布:紫川软件 编辑:程序博客网 时间:2024/05/17 06:29
线程执行器 分离任务的创建和执行,通过食用执行器,仅需要使用 Runnable接口对象然后将这些对象发送给执行器即可。执行器通过创建所需线程,来负责这些 Runnable对象的创建、实例化以及运行。当一个任务发送给执行器,执行器会尝试使用线程池中的线程来执行这个任务,避免不断地创建、销毁线程而导致系统性能下降

执行框架,有 Callable接口功能类似于 Runnable 接口,但是功能更强大

使用执行框架(Executor Framework) 的第一步是创建 ThreadPoolExecutor对象,该类提供四个构造器或使用 Executors 工厂类(推荐工厂类创建) 来创建 ThreadPoolExecutor对象,一旦有执行器就可以将 Runnable或Callable对象发送其去执行

Java 通过 Executors 提供四种线程池,分别为 :
1> newCachedThreadPool : 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程,例如如下代码 :
ExecutorServicecachedThreadPool = Executors.newCachedThreadPool();
for(inti = 0; i < 10; i++) {
    final int index = i;
    try {
        Thread.sleep(50);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    cachedThreadPool.execute(newRunnable() {
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " => "+ index);
        }
    });
}

2> newFixedThreadPool : 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
ExecutorServicefixedThreadPool = Executors.newFixedThreadPool(3);
for(inti = 0; i < 10; i++) {
    final int index = i;
    fixedThreadPool.execute(newRunnable() {
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " => "+ index);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}
因为线程池大小为3,每个任务输出index后sleep 1秒,所以每一秒打印3个数字。定长线程池的大小最好根据系统资源进行设置。如Runtime.getRuntime().availableProcessors()

3> newScheduledThreadPool : 创建一个定长线程池,支持定时及周期性任务执行
延迟执行示例代码如下 :
ScheduledExecutorServicescheduledThreadPool = Executors.newScheduledThreadPool(3);
for(inti = 0; i < 10; i++) {
    final int index = i;
    scheduledThreadPool.schedule(newRunnable() {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => "+ index);
        }
    }, 3, TimeUnit.SECONDS);
}

定期执行示例代码如下 :
ScheduledExecutorServicescheduledThreadPool = Executors.newScheduledThreadPool(3);
for(inti = 0; i < 10; i++) {
    final int index = i;
    scheduledThreadPool.scheduleAtFixedRate(newRunnable() {
        public void run() {
            System.out.println(Thread.currentThread().getName() + " => "+ index);
        }
    }, 1, 3, TimeUnit.SECONDS);
}

4> newSingleThreadExecutor : 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
ExecutorServicesingleThreadExecutor = Executors.newSingleThreadExecutor();
for(inti = 0; i < 10; i++) {
    final int index = i;
    singleThreadExecutor.execute(newRunnable() {
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + " => "+ index);
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
}


ThreadPoolExecutor 类
如果要执行新任务,缓存线程池就会创建新线程;如果线程所运行的任务执行完成后并且这个线程可用,那么缓存线程池将会重用这些线程。线程重用将减少创建线程花费的时间。然而,缓存线程池的缺点是如果发送过多的任务给执行器系统的负荷将会过载
注 : 仅当线程数量是合理或者线程只会运行很短时间,适合采用 Executor工厂类 newCacheThreadPool() 方法来创建执行器

一旦创建执行器就可以使用执行器的 execute() 方法来发送 Runnable 或 Callable类型的任务

执行器以及 ThreadPoolExecutor 类的一个重要特性是需要显示地去结束,否则其一直运行不会结束,可以使用 ThreadPoolExecutor类的 shutdown()方法来结束,调用该方法之后再尝试发送任务将会被拒绝并抛出 RejectedExecutionException

public void execute(Runnable command) : 在将来某个时间执行给定任务,任务会在一个新线程或者线程池中执行。如果任务不能提交执行,可能是由于这个执行器关闭或者因为线程处理量到达器容量

public void shutdown() : 表示执行执行器应当结束,但执行器只有等待所有的执行任务结束后才算正真结束,在这个过程中不会接受新任务。如果已经关闭,唤醒将不会有额外影响。这个方法不会等待之前提交的任务完全执行

public List<Runnable> shutdownNow() : 这个方法会立即关闭执行器,执行器将不再执行那些正在等待执行的任务,这个方法将返回等待执行的任务列表。调用时,正在运行的任务继续运行,但是不会等待这些任务完成

public void setRejectedExecutionHandler(RejectedExecutionHandler handler) : 设置用于被拒绝任务处理器,这样当出现被拒任务,将调用该方法 RejectedExecutionHandler 去处理,否则抛出 RejectedExecutionException

public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException : 阻塞所调用的线程直到执行器完成所有任务或者到达超时时间

public boolean isShutdown() : 是否调用 shutdown() 相关方法终止 ThreadPoolExecutor 运行

public int getPoolSize() : 返回执行器线程池中实际的线程数

public int getActiveCount() : 返回执行器中正在执行任务的线程数

public long getCompletedTaskCount() : 返回执行器已完成的任务数

public int getLargestPoolSize() : 返回曾经在线程池中执行的最大线程数

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException : 指定给定任务集合,当所有计算完成时(在完成所有任务过程中线程会在该地阻塞) 返回持有状态和结果的Future 列表,返回的结果顺序和插入的任务顺序相同。注意任务完成可以正常终止或者通过抛出异常。如果在操作过程中给定集合被修改则方法结果就无法确定

<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException : 当所有任务完成,或者超时的时候(无论哪个首先发生),这个方法返回保持任务状态和结果的 Future 列表

例如如下基本案例 :
ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
for(inti = 0; i < 5; i++) {
    executor.execute(newRunnable() {
        @Override
       public voidrun() {
        }
    });
}
executor.shutdown();

ExecutorService接口
ThreadPoolExecutor 继承 AbstractExecutorService 抽象类,AbstractExecutorService 抽象类 实现 ExecutorService接口

<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException : 指定给定的Task集合,返回其中一个成功完成的结果

<T> Future<T> submit(Callable<T> task) : 提交一个执行返回值的任务,返回一个代表还未执行完成的结果 Future,Future 可通过使用 get方法返回一个成功操作的结果

Future 接口
boolean isDone() : 如果任务完成返回 true

boolean isCancelled() : 在这个任务完成前任务取消被,返回 true

V get() throws InterruptedException, ExecutionException : 如果有必要等待计算完成之后获取结果

V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException : 如果必要等待到最大给定时间完成计算并返回可得到的结果

boolean cancel(boolean mayInterruptIfRunning) : 尝试取消执行的任务。如果任务已经完成,已经取消或者由于其它原因不能取消 那么尝试取消将失败。当 cancel 方法被调用这个任务还为启动,如果操作成功,这个任务将不会运行。当这个任务已经启动,尝试去停止这个任务,此时 mayInterruptIfRunning 参数决定是否线程执行这个任务应该被阻断

Executors.newCachedThreadPool() 如遇线程多且耗时长的任务,此时会创建很多新线程将影响整体性能,为避免此问题可以使用固定大小的线程执行器,如果处理任务超过执行器最大值将不再创建额外线程,剩下任务阻塞知道有空闲的线程可用
如下,剩下过程和 Executors.newCachedThreadPool() 一样
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);

执行器框架(Executor Framework) 可以运行并发任务并返回结果,并提供如下两接口实现该功能 :
1> Callable : 该接口声明 call() 方法,该方法中实现具体业务逻辑,由于该接口是泛型必须声明返回数据类型
2> Future : 该接口声明一些方法来获取 Callable 对象产生的结果,并管理其状态

例如如下代码 :
public classFactorialCalculator implements Callable<Integer> {
    private Integernumber;
    public FactorialCalculator(Integer number) {
        this.number= number;
    }
    @Override
   publicInteger call() throws Exception {
        int result =1;
        if (number== 0 || number ==1) {
            result = 1;
        } else {
            for (inti = 2; i <= number; i++) {
                result *= i;
                TimeUnit.MILLISECONDS.sleep(20);
            }
        }
        System.out.printf("%s: %d\n", Thread.currentThread().getName(), result);
        return result;
    }
    public static void main(String[] args) {
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);
        List<Future<Integer>> resultList =newArrayList<>();
        Random random =newRandom();
        for (inti = 0; i < 10; i++) {
            Integer number = random.nextInt(10);
            FactorialCalculator calculator =newFactorialCalculator(number);
            Future<Integer> result = executor.submit(calculator);// 发送一个 Callable 对象去执行,相当于 Runnable
           resultList.add(result);
        }
        do {
            // getCompletedTaskCount() 获取完成的任务数量
           System.out.printf("Main: Number of Completed Tasks: %d\n", executor.getCompletedTaskCount());
            for (inti = 0; i < resultList.size(); i++) {
                Future<Integer> result = resultList.get(i);
                System.out.printf("Main: Task %d: %s\n", i, result.isDone()); // isDone() 任务是否完成
           }
            try {
                TimeUnit.MILLISECONDS.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        } while (executor.getCompletedTaskCount() < resultList.size());
        System.out.printf("Main: Results\n");
        for (inti = 0; i < resultList.size(); i++) {
            Future<Integer> result = resultList.get(i);
            Integer number =null;
            try {
                number = result.get();// 获取最终结果,如果有必要需等待计算完成
           } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.printf("Main: Task %d: %d\n", i, number);
        }
        executor.shutdown();
    }
}

运行多个任务并处理第一个结果
public classUserValidator {
    private Stringname;
    public UserValidator(String name) {
        this.name= name;
    }
    public boolean validate(String name, String password) {
        Random random =newRandom();
        try {
            long duration = (long) (Math.random() * 10);
            System.out.printf("Validator %s: Validating a user during %d seconds\n",this.name, duration);
            TimeUnit.SECONDS.sleep(duration);
        } catch (InterruptedException e) {
            e.printStackTrace();
            return false;
        }
        return random.nextBoolean();
    }
    public String getName() {
        return name;
    }
}

public classTaskValidator implements Callable<String> {
    private UserValidatorvalidator;
    private Stringuser;
    private Stringpassword;
    public TaskValidator(UserValidator validator, String user, String password) {
        this.validator= validator;
        this.user= user;
        this.password= password;
    }
    @Override
   publicString call() throws Exception {
        if (!validator.validate(user,password)) {
            System.out.printf("%s: The user has not been found\n",validator.getName());
            throw new Exception("Error validating user");
        }
        System.out.printf("%s: The user has not been found\n",validator.getName());
        return validator.getName();
    }
    public static void main(String[] args) {
        String username ="test";
        String password ="test";
        TaskValidator ldapTask =newTaskValidator(newUserValidator("LDAP"), username, password);
        TaskValidator dbTask =newTaskValidator(newUserValidator("DataBase"), username, password);
        List<TaskValidator> taskList =newArrayList<>();
        taskList.add(ldapTask);
        taskList.add(dbTask);
        ExecutorService executorService = Executors.newCachedThreadPool();
        try {
            // invokeAny : 指定给定任务返回其中一个执行成功
           String result = executorService.invokeAny(taskList);
            System.out.printf("Main: Result: %s\n", result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        System.out.printf("Main: End of the Execution\n");
    }
}

ScheduledExecutorService 接口
用于延迟或者周期性的执行任务,默认情况下无论执行器是否结束,待处理任务仍将被执行
注 : ScheduledThreadPoolExecutor类 实现 ScheduledExecutorService接口,推荐仅在开发定时任务程序时采用 ScheduledThreadPoolExecutor 类

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit)
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) : 在指定延迟时间后执行任务,执行方法可以是 Runnable 或 Callable

public void setExecuteExistingDelayedTasksAfterShutdownPolicy(boolean value) : 设置为 false 执行 shutdown() 方法后,待处理任务不会执行,true会执行

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) : 在指定延迟后周期性运行任务,如果任何一个任务抛出异常随后的任务将被抑制。否则,任务将仅通过执行程序的取消或终止而终止。如果执行的任务比周期花费更长时间,那么随后的执行可能会延迟,但不会同时执行

public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) : 创建和执行一个周期性行动,其能够让第一个任务延迟后执行,随后的周期性延迟会在上一个结束到下一个任务开始。如果任何一个任务抛出异常随后的任务将被抑制。否则,任务将仅通过执行程序的取消或终止而终止

ScheduledFuture 接口
ScheduledFuture 接口 是继承 Delayed接口 和 Future接口

long getDelay(TimeUnit unit) : 使用给定的时间单位返回关联这个对象剩余延迟

在执行器中延迟执行任务
public classTask implements Callable<String> {
    private Stringname;
    public Task(String name) {
        this.name= name;
    }
    @Override
   publicString call() throws Exception {
        System.out.printf("%s: Starting at : %s\n",name,newDate());
        return "Hello, world";
    }
    public static void main(String[] args) {
        ScheduledThreadPoolExecutor executor = (ScheduledThreadPoolExecutor) Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n",newDate());
        for (inti = 0; i < 5; i++) {
            Task task =newTask("Task "+ i);
            executor.schedule(task, i +1, TimeUnit.SECONDS);
        }
        executor.shutdown();
        try {
            executor.awaitTermination(1, TimeUnit.DAYS);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Main: Ends at: %s\n",newDate());
    }
}

执行器周期性执行任务
public classTask implements Runnable {
    private Stringname;
    public Task(String name) {
        this.name= name;
    }
    @Override
   public voidrun() {
        System.out.printf("%s: Start at : %s\n",name,newDate());
        try {
            TimeUnit.MILLISECONDS.sleep(2500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("%s: End at : %s\n",name,newDate());
    }
    public static void main(String[] args) {
        ScheduledExecutorService executor = Executors.newScheduledThreadPool(1);
        System.out.printf("Main: Starting at: %s\n",newDate());
        Task task = new Task("Task");
        ScheduledFuture<?> result = executor.scheduleAtFixedRate(task,1,2, TimeUnit.SECONDS);
        for (inti = 0; i < 20; i++) {
            System.out.printf("Main: Delay: %d\n", result.getDelay(TimeUnit.MILLISECONDS));
            try {
                TimeUnit.MILLISECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        executor.shutdown();

        try {
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Main: Finished at: %s\n",newDate());
    }
}

在执行器中取消任务
public classTask implements Callable<String> {
   @Override
   publicString call() throws Exception {
       while(true) {
            System.out.printf("Task: Test\n");
            Thread.sleep(100);
        }
    }
   public static voidmain(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Task task =newTask();
        System.out.printf("Main: Executing the Task\n");
        Future<String> result = executorService.submit(task);
       try{
            TimeUnit.SECONDS.sleep(2);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Main: Canceling the Task\n");
        result.cancel(true);
        System.out.printf("Main: Cancelled: %s\n", result.isCancelled());
        System.out.printf("Main: Done: %s\n", result.isDone());
        executorService.shutdown();
        System.out.printf("Main: The executor has finish\n");
    }
}

FutureTask 类
用于在当任务执行完成之后,还可以执行一些代码。当任务执行完成是受 FutureTask类控制时,这个方法在内部被 FutureTask类调用。在任务结果设置后以及任务的状态改变为 isDone 之后,无论任务是否被取消或者正常结束,done() 才被调用
public classExecutabletask implements Callable<String> {
   privateString name;
   publicExecutabletask(String name) {
       this.name= name;
    }
   publicString getName() {
       returnname;
    }
   public voidsetName(String name) {
       this.name= name;
    }
   @Override
   publicString call() throws Exception {
       longduration = (long) (Math.random() * 10);
        System.out.printf("%s: Waiting %d seconds for results.\n",this.name, duration);
        TimeUnit.SECONDS.sleep(duration);
       return"Hellow, world.I'm "+ name;
    }
}

public classResultTask extends FutureTask<String> {
   privateString name;
   publicResultTask(Executabletask callable) {
       super(callable);
       this.name= callable.getName();
    }
   @Override
   protected voiddone() {
       if(isCancelled()) {
            System.out.printf("%s: Has been canceled\n",name);
        }else{
            System.out.printf("%s: Has been finished\n",name);
        }
    }
   public static voidmain(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        ResultTask[] resultTasks =newResultTask[5];
       for(inti = 0; i < 5; i++) {
            Executabletask executabletask =newExecutabletask("Task "+ i);
            resultTasks[i] =newResultTask(executabletask);
            executor.submit(resultTasks[i]);
        }
       try{
            TimeUnit.SECONDS.sleep(5);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
       for(inti = 0; i < resultTasks.length; i++) {
            resultTasks[i].cancel(true);
        }
       for(inti = 0; i < resultTasks.length; i++) {
           try{
               if(!resultTasks[i].isCancelled()) {
                    System.out.printf("%s\n", resultTasks[i].get());
                }
            }catch(InterruptedException e) {
                e.printStackTrace();
            }catch(ExecutionException e) {
                e.printStackTrace();
            }
        }
        executor.shutdown();
    }
}

ExecutorCompletionService 类
ExecutorCompletionService 类实现 CompletionService接口 可用于处理在一个对象里发送任务给执行器然后在另一个对象里处理结果的情况,CompletionService接口使用 Executor 对象来执行任务,这样可以共享 CompletionService接口的实现类,并发送到执行器,然后其它对象可以处理任务的结果

public ExecutorCompletionService(Executor executor) : 使用支持基础任务执行功能的执行器 和 一个完成队列LinkedBlockingQueue 创建一个 ExecutorCompletionService 对象

public Future<V> poll() : 取出并移除代表下一个完成任务的 Future,如果没有结果返回 null

public Future<V> poll(long timeout, TimeUnit unit) throws InterruptedException : 取出并移除代表下一个完成任务的 Future,如果不存在等待到一直到 指定的等待时长

public Future<V> take() throws InterruptedException : 检查队列中是否有 Future 对象,如果队列为空

public Future<V> submit(Callable<V> task) : 提交一个有返回值的执行任务且返回一个代表还没结果任务的 Future。在完成时,这个任务可能被移走

public classReportGenerator implements Callable<String> {
   privateString sender;
   privateString title;
   publicReportGenerator(String sender, String title) {
       this.sender= sender;
       this.title= title;
    }
   @Override
   publicString call() throws Exception {
       longduration = (long) (Math.random() * 10);
        System.out.printf("%s_%s: ReportGenerator: Generationg a report during %d seconds\n", sender, title, duration);
        TimeUnit.SECONDS.sleep(duration);
       returnsender + " : " + title;
    }
}

public classReportRequest implements Runnable {
   privateString name;
   privateExecutorCompletionService<String>service;
   publicReportRequest(String name, ExecutorCompletionService<String> service) {
       this.name= name;
       this.service= service;
    }
   @Override
   public voidrun() {
        ReportGenerator reportGenerator =newReportGenerator(name,"Report");
       service.submit(reportGenerator);
    }
}

public classReportProcessor implements Runnable {
   privateExecutorCompletionService<String>service;
   private booleanend;
   publicReportProcessor(ExecutorCompletionService<String> service) {
       this.service= service;
       this.end= false;
    }
   @Override
   public voidrun() {
       while(!end) {
           try{
                Future<String> result =service.poll(20, TimeUnit.SECONDS);
               if(result != null) {
                    String report = result.get();
                    System.out.printf("ReportReceiver: Report Received:%s\n", report);
                }
            }catch(InterruptedException e) {
                e.printStackTrace();
            }catch(ExecutionException e) {
                e.printStackTrace();
            }
        }
        System.out.printf("ReportSender: End\n");
    }
   public voidsetEnd(booleanend) {
       this.end= end;
    }
   public static voidmain(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        ExecutorCompletionService<String> service =newExecutorCompletionService<>(executor);
        ReportRequest faceRequest =newReportRequest("Face", service);
        ReportRequest onlineRequest =newReportRequest("Online", service);
        Thread faceThread =newThread(faceRequest);
        Thread onlineThread =newThread(onlineRequest);
        ReportProcessor processor =newReportProcessor(service);
        Thread senderThread =newThread(processor);
        System.out.printf("Mian: Starting the Threads\n");
        faceThread.start();
        onlineThread.start();
        senderThread.start();
       try{
            System.out.printf("Main: Waiting for the report generators.\n");
            faceThread.join();
            onlineThread.join();
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        System.out.printf("Main: Shutting down the executor.\n");
        executor.shutdown();
       try{
            executor.awaitTermination(1, TimeUnit.DAYS);
        }catch(InterruptedException e) {
            e.printStackTrace();
        }
        processor.setEnd(true);
        System.out.println("Main: Ends");
    }
}

处理执行器中被拒任务
public classRejectedTaskController implements RejectedExecutionHandler {
   @Override
   public voidrejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        System.out.printf("RejectedTaskController: The task %s has been rejected\n", toString().toString());
        System.out.printf("RejectedTaskController: %s\n", executor.toString());
        System.out.printf("RejectedTaskController: Terminating: %s\n", executor.isTerminating());
        System.out.printf("RejectedTaskController: Terminated: %s\n", executor.isTerminated());
    }
   public static voidmain(String[] args) {
        RejectedTaskController controller =new RejectedTaskController();
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool();
        executor.setRejectedExecutionHandler(controller);
       for (inti = 0; i < 3; i++) {
            Task task =new Task("Task"+ i);
            executor.submit(task);
        }
        executor.shutdown();
        Task task =new Task("RejectedTask");
        executor.submit(task);
    }
}

原创粉丝点击