赞
踩
Java 中的 ScheduledExecutorService 接口用来实现延迟执行或者定时执行的任务。在阅读 RocketMQ 源码(release-4.7.1版本)的过程中,发现很多地方都是使用的 ScheduledExecutorService 来实现定时任务。比如,在 broker 启动过程,BrokerController 类中使用 scheduledExecutorService 执行 broker 启动后的定时任务,比如消息消费偏移量 offset 的持久化定时任务,大致如下:
- package org.apache.rocketmq.broker;
-
- public class BrokerController {
-
- /**
- * 这里省略一些字段
- */
-
-
- /**
- * 使用 Executors.newSingleThreadScheduledExecutor 创建单线程的定时调度任务线程池
- */
- private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "BrokerControllerScheduledThread"));
-
-
- /**
- * broker 启动的初始化方法
- */
- public boolean initialize() throws CloneNotSupportedException {
- // 省略部分代码
-
- this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- try {
- BrokerController.this.getBrokerStats().record();
- } catch (Throwable e) {
- log.error("schedule record error.", e);
- }
- }
- }, initialDelay, period, TimeUnit.MILLISECONDS);
-
- // broker 启动10s后,默认每隔5s钟持久化一次消费消息偏移量offset信息
- this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- try {
- // 持久化消费偏移量,将 ConsumerOffsetManager 序列化为json文件保存在本地
- BrokerController.this.consumerOffsetManager.persist();
- } catch (Throwable e) {
- log.error("schedule persist consumerOffset error.", e);
- }
- }
- }, 1000 * 10, this.brokerConfig.getFlushConsumerOffsetInterval(), TimeUnit.MILLISECONDS);
-
- this.scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- try {
- BrokerController.this.consumerFilterManager.persist();
- } catch (Throwable e) {
- log.error("schedule persist consumer filter error.", e);
- }
- }
- }, 1000 * 10, 1000 * 10, TimeUnit.MILLISECONDS);
-
-
- // 后面还有很多.......
-
- }
-
- }
所以觉得有必要学习理解 ScheduledExecutorService。大致看了一下 ScheduledExecutorService 实现类 ScheduledThreadPoolExecutor 的代码,跟我们常用的 ThreadPoolExecutor 线程池类还是有一些差异的,里面有一个基于堆实现的优先队列,后面要再深入学习下,本篇先学习 ScheduledExecutorService 的使用,先学会使用,再分析原理。
本文使用JDK版本:JDK8.
首先,ScheduledExecutorService 是一个接口,它实现了 ExecutorService 接口。
在 ScheduledExecutorService 接口中又新定义了下面四个方法:
在指定延迟时间后执行一个 Runnable 任务。因为是 Runnable ,返回值 ScheduledFuture.get() 返回值为 null。
在指定延迟时间后执行一个 Callable 任务。因为是 Callable ,所以 ScheduledFuture.get() 有返回值。
创建并执行一个在给定初始延迟 (initialDelay) 后首次启动执行的定时任务,任务执行具有给定的周期 (period);
也就是将在 initialDelay 后开始执行,然后在 initialDelay + period 后执行,接着在 initialDelay + 2 * period 后执行,依此类推。
如果任务的任何一个执行遇到异常,则后续执行都会被取消。相反,任务正常执行的话,只能通过线程池的取消或终止操作来终止该任务。
如果此任务的任何一个执行要花费比其周期更长的时间,则后续的执行将会被推迟,不会出现两个任务同时执行。
创建并执行一个在给定初始延迟 (initialDelay) 后首次启动执行的任务,随后,在每一次执行终止和下一次执行开始之间都存在给定的延迟(delay)。
如果任务的任一执行遇到异常,就会取消后续任务执行。相反,任务正常执行的话,只能通过线程池的取消或终止操作来终止该任务。
Executors 类中封装了几个方法返回 ScheduledExecutorService 的实现:
- public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
- return new DelegatedScheduledExecutorService
- (new ScheduledThreadPoolExecutor(1));
- }
-
- public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
- return new DelegatedScheduledExecutorService
- (new ScheduledThreadPoolExecutor(1, threadFactory));
- }
-
- public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
- return new ScheduledThreadPoolExecutor(corePoolSize);
- }
-
- public static ScheduledExecutorService newScheduledThreadPool(
- int corePoolSize, ThreadFactory threadFactory) {
- return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
- }
-
- public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) {
- if (executor == null)
- throw new NullPointerException();
- return new DelegatedScheduledExecutorService(executor);
- }
可以看到底层实现主要借助两个类: DelegatedScheduledExecutorService 和 ScheduledThreadPoolExecutor。其中 newSingle- 开头的方法返回的线程池实现只有一个核心线程,也就用这一个线程执行提交的延迟或者定时任务。我们也使用 rocketmq 中用的 Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory):
- public class ScheduledExecutorServiceTest {
-
- public static void main(String[] args) {
- ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "TestScheduledThread"));
-
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔1s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- }
- }, 0L, 1, TimeUnit.SECONDS);
-
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔5s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- }
- }, 3L, 5, TimeUnit.SECONDS);//延迟 3s 后再执行定时任务
-
- }
- }
- class ThreadFactoryImpl implements ThreadFactory {
- private final AtomicLong threadIndex = new AtomicLong(0);
- private final String threadNamePrefix;
- private final boolean daemon;
-
- public ThreadFactoryImpl(final String threadNamePrefix) {
- this(threadNamePrefix, false);
- }
-
- public ThreadFactoryImpl(final String threadNamePrefix, boolean daemon) {
- this.threadNamePrefix = threadNamePrefix;
- this.daemon = daemon;
- }
-
- @Override
- public Thread newThread(Runnable r) {
- Thread thread = new Thread(r, threadNamePrefix + this.threadIndex.incrementAndGet());
- thread.setDaemon(daemon);
- return thread;
- }
- }
注意:我们上面采用的是 scheduledExecutorService.scheduleAtFixedRate 方法。
运行 main 方法一段时间后,结束运行,观察控制台日志:
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374865
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374866
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374867
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374868
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374868
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374869
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374870
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374871
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374872
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374873
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374873
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374874
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374875
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374876
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374877
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374878
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374878
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617374879
可以看到 1s 中的任务每一秒执行一次,5s 任务首次执行在延迟 3s 钟之后,每 5s 执行一次。
scheduledExecutorService.scheduleAtFixedRate 方法如果某个定时任务某次执行异常了,那么该任务后续将不会再执行,我们测试一下:
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔5s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- //这里抛出异常
- throw new RuntimeException();
- }
- }, 3L, 5, TimeUnit.SECONDS);
控制台输出:
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375180
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375181
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375182
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375183
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375183
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375184
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375185
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375186
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375187
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375188
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375189
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375190
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375191
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375192
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375193
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375194
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375195
可以看出,5s 的任务只执行了一次,因为抛出了异常便不再执行了。 1s 的任务则不受影响。所以我们最好要像 rocketmq 中那样使用,用 try - catch 包裹住任务执行代码,并且打出 error 级别的日志:
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- try{
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔5s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- throw new RuntimeException();
- } catch (Exception e){
- System.out.println(e);//模拟输出 error 信息
- }
- }
- }, 3L, 5, TimeUnit.SECONDS);
修改之后,控制台输出:
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375547
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375548
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375549
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375550
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375550
- java.lang.RuntimeException
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375551
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375552
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375553
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375554
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375555
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375555
- java.lang.RuntimeException
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375556
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375557
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375558
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375559
5s 的任务虽然第一次抛出了异常,但是 catch 处理异常后,后续任务仍会正常执行。
再测试一下,如果任务执行时间超出了定时周期的情况:
- public static void main(String[] args) {
- ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "TestScheduledThread"));
-
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔1s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- try {
- // 模拟任务执行耗时5s
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }, 0L, 1, TimeUnit.SECONDS);
-
- }
控制台输出结果:
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375850
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375855
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375860
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375865
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375870
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617375875
可以看出:scheduledExecutorService.scheduleAtFixedRate 方法如果定时任务执行时间超过了定时周期时长,则后续的任务执行将会被推迟,不会出现两个任务同时执行。如果在这种情况下,我们再把上面首次延迟 3s 执行的 5s 的定时任务加上,会有什么现象:
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376360
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376365
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376370
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376375
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376380
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376380
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376385
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376390
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376395
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376400
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376405
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376405
- 我是间隔1s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617376410
可以看出,因为 1s 定时任务执行时间超过了执行周期,影响到了 5s 定时任务的周期执行,控制台显示 5s 的任务隔了 25 秒才执行,并且首次延迟的时间也非常不准确了,这是因为我们使用的是Executors.newSingleThreadScheduledExecutor(ThreadFactory threadFactory) ,它底层只有一个线程处理这些定时任务,所以导致这种问题的发生。如果我们改为如下设置多个核心线程的ScheduledExecutorService 实现就可以避免这个问题:
- //使用两个核心线程
- ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
控制台输出结果,1s 和 5s 两个定时任务执行都恢复正常了:
- 我是间隔1s执行的任务,线程name:pool-1-thread-1,执行时间戳:1617377444
- 我是间隔5s执行的任务,线程name:pool-1-thread-2,执行时间戳:1617377447
- 我是间隔1s执行的任务,线程name:pool-1-thread-1,执行时间戳:1617377449
- 我是间隔5s执行的任务,线程name:pool-1-thread-2,执行时间戳:1617377452
- 我是间隔1s执行的任务,线程name:pool-1-thread-1,执行时间戳:1617377454
- 我是间隔5s执行的任务,线程name:pool-1-thread-2,执行时间戳:1617377457
- 我是间隔1s执行的任务,线程name:pool-1-thread-1,执行时间戳:1617377459
- 我是间隔5s执行的任务,线程name:pool-1-thread-2,执行时间戳:1617377462
- 我是间隔1s执行的任务,线程name:pool-1-thread-1,执行时间戳:1617377464
这个方法每次执行任务是有指定的时间间隔:
- public static void main(String[] args) {
- ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "TestScheduledThread"));
-
- scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是每次执行完毕间隔2s执行的任务,线程name:"+ thread.getName() + ",开始执行时间戳:" + System.currentTimeMillis() / 1000);
- // 随机数模拟每次执行耗时不同
- int random = new Random().nextInt(6);
- try {
- Thread.sleep(random * 1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("我是每次执行完毕间隔2s执行的任务,线程name:"+ thread.getName() + ",执行完毕时间戳:" + System.currentTimeMillis() / 1000);
- }
- }, 0L, 2, TimeUnit.SECONDS);
- }
控制台输出,每次执行完毕后隔 2s 开启下一次执行:
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378188
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378193
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378195
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378199
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378201
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378206
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378208
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378210
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378212
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378214
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,开始执行时间戳:1617378216
- 我是每次执行完毕间隔2s执行的任务,线程name:TestScheduledThread1,执行完毕时间戳:1617378217
这个方法需要注意的是,ScheduledFuture 继承了 Future 接口,scheduledFuture.get() 也会阻塞调用方线程直至取到结果。
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "TestScheduledThread"));
-
- ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new Callable() {
- @Override
- public Integer call() {
- Thread thread = Thread.currentThread();
- System.out.println("我是延迟10s执行的一次性任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return 250;
- }
- }, 10L, TimeUnit.SECONDS);
- // scheduledFuture.get() 会阻塞调用方线程,这里是 main 线程
- System.out.println("scheduledFuture.get():" + scheduledFuture.get());
- System.out.println("hello world");
- }
把上面几种方法混合在一起调用:
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor(new ThreadFactoryImpl(
- "TestScheduledThread"));
-
- scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
- @Override
- public void run() {
- Thread thread = Thread.currentThread();
- System.out.println("我是间隔5s执行的任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- }
- }, 0L, 5, TimeUnit.SECONDS);
-
- ScheduledFuture scheduledFuture = scheduledExecutorService.schedule(new Callable() {
- @Override
- public Integer call() {
- Thread thread = Thread.currentThread();
- System.out.println("我是延迟10s执行的一次性任务,线程name:"+ thread.getName() + ",执行时间戳:" + System.currentTimeMillis() / 1000);
- try {
- Thread.sleep(5000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- return 250;
- }
- }, 10L, TimeUnit.SECONDS);
- // scheduledFuture.get() 会阻塞调用方线程,这里是 main 线程
- System.out.println("scheduledFuture.get():" + scheduledFuture.get());
- System.out.println("hello world");
- }
输出结果:
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379143
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379148
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379153
- 我是延迟10s执行的一次性任务,线程name:TestScheduledThread1,执行时间戳:1617379153
- scheduledFuture.get():250
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379158
- hello world
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379163
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379168
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379173
- 我是间隔5s执行的任务,线程name:TestScheduledThread1,执行时间戳:1617379178
运行非常好~
感觉 ScheduledExecutorService 很强大,后续要分析一下上述两个类 DelegatedScheduledExecutorService 和 ScheduledThreadPoolExecutor 的底层实现。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。