也尚无煽情的故事,然后介绍了其应用时的小心点

序言

先上本文讲述的demo效果图

图片 1

这几天博主在看kitten yang的A GUIDE TO IOS
ANIMATION,小编对动画片的施用令自己感动很深(同为同龄人实在感到羞愧),于是决定重新学习一遍layer。
coreAnimation作为iOS最紧要的框架之一,CALayer的要害毋庸置疑,本文将从上图的demo讲起,我会分成常规用法跟自己商讨完结的用法来促成,以此来更为长远的学习layer。
ps:本文不包蕴CALayer的属性讲解以及选取。如有需求,请自行百度读书

       本文首要分为五个部分,第一片段首先会对ScheduledThreadPoolExecutor进行简单的牵线,并且会介绍其利害攸关API的利用方法,然后介绍了其使用时的注意点,第二有的则要害对ScheduledThreadPoolExecutor的已毕细节举办介绍。

那边没有酒,也没有煽情的故事

进度条

1. 选用简介

       ScheduledThreadPoolExecutor是一个使用线程池执行定时任务的类,相较于Java中提供的另一个举办定时义务的类提姆(Tim)er,其重大有如下多少个亮点:

  • 利用八线程执行职务,不用顾虑职责执行时间过长而造成任务互相阻塞的状态,提姆(Tim)er是单线程执行的,因此会出现那么些题目;
  • 毫无操心职分执行进度中,倘使线程失活,其会新建线程执行职务,提姆(Tim)er类的单线程挂掉之后是不会再度创制线程执行后续职分的。

       除去上述七个优点外,ScheduledThreadPoolExecutor还提供了格外灵活的API,用于实施任务。其任务的实践政策首要分为两大类:①在一定延迟之后只举行三次某个职分;②在早晚延迟之唐朝期性的履行某个任务。如下是其首要API:

public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
                                                 long initialDelay, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
                                                  long initialDelay, long period, TimeUnit unit);

       上述七个法子中,第二个和第三个办法属于第一类,即在delay指定的延期之后执行第二个参数所指定的天职,分化在于,第一个章程执行之后会有重返值,而首先个措施执行之后是尚未重临值的。第八个和第七个艺术则属于第二类,即在其次个参数(initialDelay)指定的时光过后伊始周期性的进行职务,执行周时期隔为第八个参数指定的大运,然而那多个办法的分别在于第七个办法执行任务的区间是从来的,无论上一个职分是或不是举办到位,而第多个章程的实践时间距离是不固定的,其会在周期义务的上一个义务履行到位将来才起始计时,并在指定时间距离之后才初始执行义务。如下是运用scheduleWithFixedDelay()和scheduleAtFixedRate()方法编写的测试用例:

public class ScheduledThreadPoolExecutorTest {
  private ScheduledThreadPoolExecutor executor;
  private Runnable task;

  @Before
  public void before() {
    executor = initExecutor();
    task = initTask();
  }

  private ScheduledThreadPoolExecutor initExecutor() {
    return new ScheduledThreadPoolExecutor(2);;
  }

  private Runnable initTask() {
    long start = System.currentTimeMillis();
    return () -> {
      print("start task: " + getPeriod(start, System.currentTimeMillis()));
      sleep(SECONDS, 10);
      print("end task: " + getPeriod(start, System.currentTimeMillis()));
    };
  }

  @Test
  public void testFixedTask() {
    print("start main thread");
    executor.scheduleAtFixedRate(task, 15, 30, SECONDS);
    sleep(SECONDS, 120);
    print("end main thread");
  }

  @Test
  public void testDelayedTask() {
    print("start main thread");
    executor.scheduleWithFixedDelay(task, 15, 30, SECONDS);
    sleep(SECONDS, 120);
    print("end main thread");
  }

  private void sleep(TimeUnit unit, long time) {
    try {
      unit.sleep(time);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }

  private int getPeriod(long start, long end) {
    return (int)(end - start) / 1000;
  }

  private void print(String msg) {
    System.out.println(msg);
  }
}

       可以见见,上述三个测试用例代码块基本是如出一辙的,分化在于第四个用例调用的是scheduleAtFixedRate()方法,而第三个用例调用的是scheduleWithFixedDelay()。那里多个用例都是设置的在延迟15s后各类30s执行四遍指定的义务,而该职分履行时长为10s。如下分别是那四个测试用例的推行结果:

start main thread
start task: 15
end task: 25
start task: 45
end task: 55
start task: 75
end task: 85
start task: 105
end task: 115
end main thread

start main thread
start task: 15
end task: 25
start task: 55
end task: 65
start task: 95
end task: 105
end main thread

      相比上述执行结果可以观望,对于scheduleAtFixedRate()方法,其每一趟执行任务的启幕时间间隔都为稳定不变的30s,与职务履行时长无关,而对此scheduleWithFixedDelay()方法,其每趟执行任务的开端时间距离都为上次义务执行时间累加指定的日子距离。

       那里关于ScheduledThreadPoolExecutor的运用有三点必要验证如下:

  • ScheduledThreadPoolExecutor继承自ThreadPoolExecutor(ThreadPoolExecutor详解),由此也有继续而来的execute()和submit()方法,可是ScheduledThreadPoolExecutor重写了这八个艺术,重写的点子是一向开立八个霎时实施并且只举行四遍的职分;
  • ScheduledThreadPoolExecutor使用ScheduledFutureTask封装每个须求举办的义务,而任务都是放入DelayedWorkQueue队列中的,该队列是一个用到数组完毕的先行队列,在调用ScheduledFutureTask::cancel()方法时,其会按照removeOnCancel变量的装置来确认是还是不是要求将当前任务真正的从队列中移除,而不只是标识其为已删除状态;
  • ScheduledThreadPoolExecutor提供了一个钩子方法decorateTask(Runnable,
    RunnableScheduledFuture)用于对举办的职分进展装点,该办法第二个参数是调用方传入的职务实例,第四个参数则是利用ScheduledFutureTask对用户传入职务实例进行打包之后的实例。那里需求小心的是,在ScheduledFutureTask对象中有一个heapIndex变量,该变量用于记录当前实例处于队列数组中的下标地方,该变量可以将诸如contains(),remove()等办法的光阴复杂度从O(N)下跌到O(logN),由此效能提高是比较高的,可是一旦那里用户重写decorateTask()方法封装了队列中的职分实例,那么heapIndex的优化就不设有了,因此这里强烈提出是不择手段不要重写该方法,或者重写时也依旧复用ScheduledFutureTask类。

此间,唯有两颗坦诚相待的心

好端端做法

如上图所示,进程条并不是仅仅的线性拉长,在50%事先,每回进程增加,进程条就会在y轴上面偏移一段距离,直到增进到一半进程的时候偏移地点达到顶点,然后趁机速度继续扩充,y轴的晃动越来越小,直到变回一条直线。
从已毕角度而言,使用CAShapeLayer然后在每便进程改变的时候更新其path值就可以完毕。借使运用CAShapeLayer的章程,我们须求创立多少个实例对象,一个放在下边作为进度条背景,另一个在下面随着速度改变而变更。图示如下:

图片 2

老是进程发生改变的时候,大家都要依照如今进程计算出进度坐标地方,然后更新多少个图层的path,代码如下:

- (void)updatePath
{
    UIBezierPath * path = [UIBezierPath bezierPath];
    [path moveToPoint: CGPointMake(25, 150)];
    [path addLineToPoint: CGPointMake((CGRectGetWidth([UIScreen mainScreen].bounds) - 50) * _progress + 25, 150 + (25.f * (1 - fabs(_progress - 0.5) * 2)))];
    [path addLineToPoint: CGPointMake(CGRectGetWidth([UIScreen mainScreen].bounds) - 25, 150)];
    self.background.path = path.CGPath;
    self.top.path = path.CGPath;
    self.top.strokeEnd = _progress;
}

其实,使用那种办法贯彻速度效果的时候,进程会比一贯在当前上下文绘制的响应上要慢上几帧,即是我们肉眼可以见见那种延时更新的职能,是不便利用户体验的。其次,我们要求格外成立一个背景图层,在内存上有了额外的支出。

2. 源码详解

那边,唯有八个以文组成的人

自定义layer

这小节大家要经过自定义CALayer的子类来兑现地点的进程条效果,我们必要对外开放progress属性。每趟这几个值暴发变更的时候大家要调用[self setNeedsDisplay]来重新绘制进程条

@property(nonatomic, assign) CGFloat progress;

重写setter方法,检测进度值范围以及重复绘制进度条

  • (void)setProgress: (CGFloat)progress
    {
    _progress = MIN(1.f, MAX(0.f, progress));
    [self setNeedsDisplay];
    }
    再也纪念一下进程条,大家可以把进程条分成两条线,分别是青色的已做到进度条和黄色的进程条。根据进度条的例外,分为<0.5,
    =0.5, >0.5二种情状:

图片 3

从上图可见,在速度达到一半的时候,大家的进程条在Y轴上的偏移量达到最大值。由此,大家应当定义一个最大偏移值MAX_OFFSET。

#define MAX_OFFSET 25.f

一方面,当前进程条的y轴偏移量是根据进程按比例举办偏移的。在大家转移进程_progress的时候,重新绘制进程条。下边是粉色进程条的绘图

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs((_progress –
    0.5f) * 2));

      CGMutablePathRef mPath = CGPathCreateMutable();
      CGPathMoveToPoint(mPath, NULL, _origin.x, _origin.y);
      CGPathAddLineToPoint(mPath, NULL, offsetX, offsetY);
    
      CGContextAddPath(ctx, mPath);
      CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
      CGContextSetLineWidth(ctx, 5.f);
      CGContextSetLineCap(ctx, kCGLineCapRound);
      CGContextStrokePath(ctx);
    
      CGPathRelease(mPath);
    

    }

ps:
此间存在一个很关键的题目,自定义的layer必须加在大家自定义的view上边,才能兑现drawInContext:方法举办不断的重绘。关于coreGraphics相关方法的越来越多利(多利(Dolly))用,请参考那篇小说

第二有的的黄色线条基于当前偏移的坐标为源点举行绘图,在此处有多个小陷阱:

  • 不精晓的开发者很不难直接把绘制黄色线条的代码放在上边那段代码的末尾。那样会导致棕色线条在藏蓝色线条后边绘制而将粉色线条遮住了一有的使得褐色线条端末非圆形
  • 没有对_progress的值举行判定。当_progress为0时,下面的代码也会在线条左侧生成一个红色小圆点,那是不可看重的。

为此,我们在规定好脚下进程对应的晃动坐标时,应该直接绘制粉红色线条,再绘制粉色进程条。在绘制紫色线条前相应对_progress举行五遍判断

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs((_progress –
    0.5f) * 2));

      CGMutablePathRef mPath = CGPathCreateMutable();
      CGPathMoveToPoint(mPath, NULL, offsetX, offsetY);
      CGPathAddLineToPoint(mPath, NULL, _origin.x + MAX_LENGTH, _origin.y);
    
      CGContextAddPath(ctx, mPath);
      CGContextSetStrokeColorWithColor(ctx, [UIColor lightGrayColor].CGColor);
      CGContextSetLineWidth(ctx, 5.f);
      CGContextSetLineCap(ctx, kCGLineCapRound);
      CGContextStrokePath(ctx);
      CGPathRelease(mPath);
    
      if (_progress != 0.f) {
          mPath = CGPathCreateMutable();
          CGPathMoveToPoint(mPath, NULL, _origin.x, _origin.y);
          CGPathAddLineToPoint(mPath, NULL, offsetX, offsetY);
    
          CGContextAddPath(ctx, mPath);
          CGContextSetStrokeColorWithColor(ctx, [UIColor greenColor].CGColor);
          CGContextSetLineWidth(ctx, 5.f);
          CGContextSetLineCap(ctx, kCGLineCapRound);
          CGContextStrokePath(ctx);
          CGPathRelease(mPath);
      }
    

    }

那儿在controller里面加上一个UISlider拖拉来决定你的进程条进程,看看是还是不是想要的功用已毕了。

2.1 主要性能

       ScheduledThreadPoolExecutor主要有三个特性,分别如下:

private volatile boolean continueExistingPeriodicTasksAfterShutdown;

private volatile boolean executeExistingDelayedTasksAfterShutdown = true;

private volatile boolean removeOnCancel = false;

private static final AtomicLong sequencer = new AtomicLong();
  • continueExistingPeriodicTasksAfterShutdown:用于标识当前Executor对象shutdown时,是或不是继续执行已经存在于任务队列中的定时职分(调用scheduleAtFixedRate()方法生成的天职);
  • executeExistingDelayedTasksAfterShutdown:用于标识当前Executor对象shutdown时,是不是继续执行已经存在于任务队列中的定时义务(调用scheduleWithFixedDelay()方法生成的天职);
  • removeOnCancel:用于标识要是当前职分已经撤除了,是还是不是将其从职责队列中确确实实的移除,而不只是标识其为除去状态;
  • sequencer:其为一个AtomicLong类型的变量,该变量记录了当前任务被成立时是第多少个职分的一个序号,这一个序号的首要用于确认当八个任务先导推行时间同一时具体哪些职分先举办,比如四个任务的发轫执行时间都为1515847881158,那么序号小的天职将先举办。

比方您玩套路,对不起,请绕行

扩展

上边我们在贯彻绘制的时候,对填充色彩颜色是写死的,那样不便民代码增加。回想CAShapeLayer,在三番五次CALayer的根底上添加了fillColor、strokeColor等相近属性,大家得以经过添加类似的成员属性来完结封装,那里大家要求为进程条添加八个特性,分别表示进度条颜色跟背景颜色

@property(nonatomic, assign) CGColorRef backgroundColor;
@property(nonatomic, assign) CGColorRef strokeColor;

我们在装置颜色的时候一贯传入color.CGColor就可以形成赋值了,我们把地点的安装颜色代码分别改成上边所示后再次运行

CGContextSetStrokeColorWithColor(ctx, _backgroundColor);
CGContextSetStrokeColorWithColor(ctx, _strokeColor);

局地朋友们会意识一个坑爹的事情,崩溃了,出现了EXC_BAD_ACCESS漏洞非凡多——假使您拔取系统提供的[UIColor xxxColor].CGColor,那么那里不会出问题。
那是因为大家扩展的五个特性为assign类型,在大家利用这些color的时候,它已经被放走了。由那里我们可以看看两件事情:

  • CAShapeLayer会对非对象且属于coreGraphics的特性进行桥接或者引用操作
  • [UIColor
    xxxColor]方法重回的对象应该是大局或者静态对象。为了节约内存消耗,应该是使用懒加载格局。有需要的意况下,能够不调用那些办法来促成优化内存的法力

所以,我们相应重写这七个属性的setter方法来促成引用(欢迎来到MRC)

  • (void)setStrokeColor: (CGColorRef)strokeColor
    {
    CGColorRelease(_strokeColor);
    _strokeColor = strokeColor;
    CGColorRetain(_strokeColor);
    [self setNeedsDisplay];
    }
    除去,CAShapeLayer还有一个幽默的特性strokeEnd,那个特性决定了全副图层有微微有些需求被渲染的。想查看这一个特性的看官们方可在最开端的例行代码中为layer设置那个特性,然后你会发现此时不管我们的progress设置为多少,进程条的粉红色部分总是一样strokeEnd。效果如下图所示

图片 4

可以看到,基于strokeEnd举行绘图的时候,界面的绘图难度越来越错综复杂了。可是大家一致能够把那么些拆分,分为二种情况
1、strokeEnd>progress
本条情景对应图中上边七个图,当然,在progress=1跟progress=0的情状是一致的。可以见见,当progress不为零的时候,进程条分为三局地:

  • 偏移点左侧的粉红色线条
  • 右边多出的灰色线条
  • 末段的青色线条

交接点的y坐标应当是由strokeEnd超出progress的百分比部分除以当前右边总长度占线条总长度的比重,如下图所示

图片 5

所以大家必要判定七个坐标点,其中偏移点根据地方代码一样基于progress得出,计算背景观和进程颜色交接点的代码如下:

CGFloat contactX = _origin.x + MAX_LENGTH * _strokeEnd;
CGFloat contactY = _origin.y + (offsetY - _origin.y) * ((1 - (_strokeEnd - _progress) / (1 - _progress)));

2、strokeEnd<=progress
此刻就对应上边的两张图了,同样的,我们可以把进程条拆分成三部分:

  • 最右侧的粉色进程条
  • 地处进程条和偏移点中间的背景颜色条
  • 左边的背景颜色条

规行矩步下边的图解格局进行解析,相当于把右边的职位音讯放到了左手,大家得以无限制的汲取颜色交接点坐标的测算方法

CGFloat contactX = _origin.x + MAX_LENGTH * _strokeEnd;
CGFloat contactY = (offsetY - _origin.y) * (_progress == 0 ?: _strokeEnd / _progress) + _origin.y;

有了上面的分析计算,drawInContext的代码如下

  • (void)drawInContext: (CGContextRef)ctx
    {
    CGFloat offsetX = _origin.x + MAX_LENGTH * _progress;
    CGFloat offsetY = _origin.y + _maxOffset * (1 – fabs(_progress –
    0.5f) * 2);
    CGFloat contactX = 25.f + MAX_LENGTH * _strokeEnd;
    CGFloat contactY = _origin.y + _maxOffset * (1 – fabs(_strokeEnd –
    0.5f) * 2);

      CGRect textRect = CGRectOffset(_textRect, MAX_LENGTH * _progress, _maxOffset * (1 - fabs(_progress - 0.5f) * 2));
      if (_report) {
          _report((NSUInteger)(_progress * 100), textRect, _strokeColor);
      }
      CGMutablePathRef linePath = CGPathCreateMutable();
    
      //绘制背景线条
      if (_strokeEnd > _progress) {
          CGFloat scale =  _progress == 0 ?: (1 - (_strokeEnd - _progress) / (1 - _progress));
          contactY = _origin.y + (offsetY - _origin.y) * scale;
          CGPathMoveToPoint(linePath, NULL, contactX, contactY);
      } else {
          CGFloat scale = _progress == 0 ?: _strokeEnd / _progress;
          contactY = (offsetY - _origin.y) * scale + _origin.y;
          CGPathMoveToPoint(linePath, NULL, contactX, contactY);
          CGPathAddLineToPoint(linePath, NULL, offsetX, offsetY);
      }
      CGPathAddLineToPoint(linePath, NULL, _origin.x + MAX_LENGTH, _origin.y);
      [self setPath: linePath onContext: ctx color: [UIColor colorWithRed: 204/255.f green: 204/255.f blue: 204/255.f alpha: 1.f].CGColor];
    
      CGPathRelease(linePath);
      linePath = CGPathCreateMutable();
    
      //绘制进度线条
      if (_progress != 0.f) {
          CGPathMoveToPoint(linePath, NULL, _origin.x, _origin.y);
          if (_strokeEnd > _progress) { CGPathAddLineToPoint(linePath, NULL, offsetX, offsetY); }
              CGPathAddLineToPoint(linePath, NULL, contactX, contactY);
          } else {
              if (_strokeEnd != 1.f && _strokeEnd != 0.f) {
                  CGPathMoveToPoint(linePath, NULL, _origin.x, _origin.y);
              CGPathAddLineToPoint(linePath, NULL, contactX, contactY);
          }
      }
      [self setPath: linePath onContext: ctx color: [UIColor colorWithRed: 66/255.f green: 1.f blue: 66/255.f alpha: 1.f].CGColor];
      CGPathRelease(linePath);
    

    }

大家把添加CGPathRef以及安装线条颜色、大小等参数的代码封装成setPath: onContext: color措施,以此来压缩代码量。
coreAnimation以及coreGraphics作为最中央的框架之一,有成百上千值得大家去追究的特征,这个特色是怎么落到实处的对我们来说是一个迷,不过大家可以尝试去琢磨那一个特点。
文集:iOS开发

转发评释链接:CALayer的追究应用

2.2 ScheduledFutureTask

       在ScheduledThreadPoolExecutor中,紧要选取ScheduledFutureTask封装要求履行的任务,该类的要紧注脚如下:

private class ScheduledFutureTask<V> extends FutureTask<V> implements RunnableScheduledFuture<V> {

  private final long sequenceNumber;    // 记录当前实例的序列号
  private long time;    // 记录当前任务下次开始执行的时间

  // 记录当前任务执行时间间隔,等于0则表示当前任务只执行一次,大于0表示当前任务为fixedRate类型的任务,
  // 小于0则表示其为fixedDelay类型的任务
  private final long period;

  RunnableScheduledFuture<V> outerTask = this;  // 记录需要周期性执行的任务的实例
  int heapIndex;    // 记录当前任务在队列数组中位置的下标

  ScheduledFutureTask(Runnable r, V result, long ns, long period) {
    super(r, result);
    this.time = ns;
    this.period = period;
    this.sequenceNumber = sequencer.getAndIncrement();  // 序号在创建任务实例时指定,且后续不会变化
  }

  public long getDelay(TimeUnit unit) {
    return unit.convert(time - now(), NANOSECONDS);
  }

  // 各个任务在队列中的存储方式是一个基于时间和序号进行比较的优先队列,当前方法定义了优先队列中两个
  // 任务执行的先后顺序。这里先对两个任务开始执行时间进行比较,时间较小者优先执行,若开始时间相同,
  // 则比较两个任务的序号,序号小的任务先执行
  public int compareTo(Delayed other) {
    if (other == this)
      return 0;
    if (other instanceof ScheduledFutureTask) {
      ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
      long diff = time - x.time;
      if (diff < 0)
        return -1;
      else if (diff > 0)
        return 1;
      else if (sequenceNumber < x.sequenceNumber)
        return -1;
      else
        return 1;
    }
    long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
    return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
  }

  public boolean isPeriodic() { // 判断是否为周期性任务
    return period != 0;
  }

  // 当前任务执行之后,会判断当前任务是否为周期性任务,如果为周期性任务,那么就调用当前方法计算
  // 当前任务下次开始执行的时间。这里如果当前任务是fixedRate类型的任务(p > 0),那么下次执行时间
  // 就是此次执行的开始时间加上时间间隔,如果当前任务是fixedDelay类型的任务(p < 0),那么下次执行
  // 时间就是当前时间(triggerTime()方法会获取系统当前时间)加上任务执行时间间隔。可以看到,定频率
  // 和定延迟的任务的执行时间区别就在当前方法中进行了指定,因为调用当前方法时任务已经执行完成了,
  // 因而triggerTime()方法中获取的时间就是任务执行完成之后的时间点
  private void setNextRunTime() {
    long p = period;
    if (p > 0)
      time += p;
    else
      time = triggerTime(-p);
  }

  // 取消当前任务的执行,super.cancel(boolean)方法也即FutureTask.cancel(boolean)方法。该方法传入
  // true表示如果当前任务正在执行,那么立即终止其执行;传入false表示如果当前方法正在执行,那么等待其
  // 执行完成之后再取消当前任务。
  public boolean cancel(boolean mayInterruptIfRunning) {
    boolean cancelled = super.cancel(mayInterruptIfRunning);
    // 判断是否设置了取消后移除队列中当前任务,是则移除当前任务
    if (cancelled && removeOnCancel && heapIndex >= 0)  
      remove(this);
    return cancelled;
  }

  public void run() {
    boolean periodic = isPeriodic();    // 判断是否为周期性任务
    if (!canRunInCurrentRunState(periodic)) // 判断是否能够在当前状态下执行该任务
      cancel(false);
    else if (!periodic) // 如果能执行当前任务,但是任务不是周期性的,那么就立即执行该任务一次
      ScheduledFutureTask.super.run();
    else if (ScheduledFutureTask.super.runAndReset()) { // 是周期性任务,则立即执行当前任务并且重置
      setNextRunTime(); // 在当前任务执行完成后调用该方法计算当前任务下次执行的时间
      reExecutePeriodic(outerTask); // 将当前任务放入任务队列中以便下次执行
    }
  }
}

       在ScheduledFutureTask中,首要有三个点须求强调:

  • 对于run()方法的首先个支行,canRunInCurrentRunState()方法的扬言如下所示,能够观望,该办法是用以判断当前任务就算为周期性任务,那么其是还是不是允许在shutdown状态下继续执行已经存在的周期性职分,是则表示方今事态下是能够推行当前职务的,那里isRunningOrShutdown()方法继承自ThreadPoolExecutor;

    boolean canRunInCurrentRunState(boolean periodic) {
    return isRunningOrShutdown(periodic ?

                             continueExistingPeriodicTasksAfterShutdown :
                             executeExistingDelayedTasksAfterShutdown);
    

    }

  • 在run()方法的结尾一个if分支中,其首先会实施当前职责,在履行到位时才会调用setNextRun提姆e()方法设置下次任务执行时间,也就是说对于fixedRate和fixedDelay类型的天职都是在那个日子点才设置的,因此尽管fixedRate类型的职责,即便该任务下次执行时间比如今时间要早,其也只会在当前义务执行到位后即刻实施,而不会与当前义务还未举行完时就实施;对于fixedDelay任务则不会存在该问题,因为其是以职责到位后的日子点为底蕴测算下次执行的时间点;

  • 对此run()方法的尾声一个分层中的reExecutePeriodic()方法,其会将当前任务出席到任务队列中,并且调用父类的ensurePrestart()方法确保有可用的线程来执行当前任务,如下是该格局的现实性贯彻:

    void reExecutePeriodic(RunnableScheduledFuture task) {
    if (canRunInCurrentRunState(true)) { // 判断当前义务是不是可以继续执行

    super.getQueue().add(task); // 将当前任务加入到任务队列中
    if (!canRunInCurrentRunState(true) && remove(task)) // 双检查法判断任务在加入过程中是否取消了
      task.cancel(false);
    else
      ensurePrestart(); // 初始化核心线程等确保任务可以被执行
    

    }
    }

       从ScheduledFutureTask的落到实处计算来看,当每创设一个此类实例时,会初步化该类的一部分重中之重性能,如下次起来进行的年华和举办的周期。当某个线程调用该义务,即执行该职责的run()方法时,即使该任务不为周期性职务,那么执行该任务之后就不会有此外的动作,倘诺该义务为周期性职务,那么在将当前任务执行完结之后,还会重置当前任务的意况,并且计算下次举行当前职分的日子,然后将其放入队列中以便下次执行。

假诺您够义气,那么自己想邀请你一同,展开三次轻松欢跃的对话

2.3 DelayedWorkQueue

       DelayedWorkQueue的贯彻与DelayQueue以及PriorityQueue的贯彻宗旨相似,格局都为一个先行队列,并且底层是行使堆结构来落到实处优先队列的功能,在数据存储情势上,其行使的是数组来完毕。那里DelayedWorkQueue与DelayQueue以及PriorityQueue分裂的点在于DelayedWorkQueue中重视囤积ScheduledFutureTask类型的义务,该任务中有一个heapIndex属性保存了当前任务在时下队列数组中的地方下标,其利害攸关提高的是对队列的诸如contains()和remove()等急需固定当前职分地点的措施的频率,时间复杂度可以从O(N)进步到O(logN)。如下是DelayedWorkQueue的兑现代码(那里只列出了此类的紧要性性能和与贯彻ScheduledThreadPoolExecutor功效有关的形式,关于怎样使用数组完毕优先队列请读者查阅有关文档):

static class DelayedWorkQueue extends AbstractQueue<Runnable> implements BlockingQueue<Runnable> {

  private static final int INITIAL_CAPACITY = 16;   // 数组初始化大小
  private RunnableScheduledFuture<?>[] queue = new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
  private final ReentrantLock lock = new ReentrantLock();   // 对添加和删除元素所使用的锁
  private int size = 0; // 当前队列中有效任务的个数

  private Thread leader = null; // 执行队列头部任务的线程
  private final Condition available = lock.newCondition();  // 除leader线程外其余线程的等待队列

  // 在对任务进行移动时,判断其是否为ScheduledFutureTask实例,如果是则维护其heapIndex属性
  private void setIndex(RunnableScheduledFuture<?> f, int idx) {
    if (f instanceof ScheduledFutureTask)
      ((ScheduledFutureTask)f).heapIndex = idx;
  }

  private void siftUp(int k, RunnableScheduledFuture<?> key) {/* 省略 */}

  private void siftDown(int k, RunnableScheduledFuture<?> key) {/* 省略 */}

  private int indexOf(Object x) {
    if (x != null) {
      if (x instanceof ScheduledFutureTask) {   // 如果为ScheduledFutureTask则可返回其heapIndex属性
        int i = ((ScheduledFutureTask) x).heapIndex;
        if (i >= 0 && i < size && queue[i] == x)
          return i;
      } else {  // 如果不为ScheduledFutureTask实例,则需要遍历队列查询当前元素的位置
        for (int i = 0; i < size; i++)
          if (x.equals(queue[i]))
            return i;
      }
    }
    return -1;
  }

  public boolean offer(Runnable x) {
    if (x == null)
      throw new NullPointerException();
    RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
      int i = size;
      if (i >= queue.length)
        grow(); // 队列容量不足,对其进行扩容
      size = i + 1;
      if (i == 0) { // 如果其为队列第一个元素,则将其放入队列头部
        queue[0] = e;
        setIndex(e, 0);
      } else {  //如果不为第一个元素,则通过堆的上移元素操作移动当前元素至合适的位置
        siftUp(i, e);
      }
      if (queue[0] == e) {  // 如果被更新的是队列头部元素,则更新记录的执行头部任务的线程
        leader = null;
        available.signal();
      }
    } finally {
      lock.unlock();
    }
    return true;
  }

  // 完成从队列拉取元素操作,并且将其从队列中移除
  private RunnableScheduledFuture<?> finishPoll(RunnableScheduledFuture<?> f) {
    int s = --size;
    RunnableScheduledFuture<?> x = queue[s];
    queue[s] = null;    // 将队列最尾部的元素置空
    if (s != 0) // 将最后一个元素放入第一个位置,并且将其下推至合适的位置
      siftDown(0, x);   // 这里idx置为0是因为当前方法的入参f都为队列的第一个元素
    setIndex(f, -1);
    return f;
  }

  // 尝试从队列(堆)中获取元素,如果没有元素或者元素的延迟时间还未到则返回空
  public RunnableScheduledFuture<?> poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
      RunnableScheduledFuture<?> first = queue[0];
      // 在此处代码控制了当从堆顶拉取元素时,如果元素的延迟时间还未达到,则不返回当前元素
      if (first == null || first.getDelay(NANOSECONDS) > 0)
        return null;
      else
        return finishPoll(first);   // 返回堆顶元素
    } finally {
      lock.unlock();
    }
  }

  // 通过无限for循环获取堆顶的元素,这里take()方法会阻塞当前线程,直至获取到了可执行的任务。
  // 可以看到,在第一次for循环中,如果堆顶不存在任务,则其会加入阻塞队列中,如果存在任务,但是
  // 其延迟时间还未到,那么当前线程会等待该延迟时间长的时间,然后查看任务是否可用,当获取到任务
  // 之后,其会将其从队列中移除,并且唤醒等待队列中其余等待的线程执行下一个任务
  public RunnableScheduledFuture<?> take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
      for (;;) {
        RunnableScheduledFuture<?> first = queue[0];
        if (first == null)
          available.await();    // 堆内没有元素,当前线程进入等待队列中
        else {
          long delay = first.getDelay(NANOSECONDS);
          if (delay <= 0)   // 堆顶元素延迟时间小于0,可立即获取任务
            return finishPoll(first);
          first = null;
          if (leader != null)
            available.await();  // 已经有线程在等待堆顶元素,则当前线程进入等待队列中
          else {
            Thread thisThread = Thread.currentThread();
            leader = thisThread;
            try {
              available.awaitNanos(delay);  // 当前线程等待一定时长后获取任务并执行
            } finally {
              if (leader == thisThread)
                leader = null;
            }
          }
        }
      }
    } finally {
      if (leader == null && queue[0] != null)
        available.signal(); // 当前线程获取完任务之后唤醒等待队列中的下一个线程执行下一个任务
      lock.unlock();
    }
  }
}

       从DelayedWorkQueue的take()和poll()方法可以看出来,对于队列中职责的守候时间的界定重点是在那多个措施中贯彻的,即使义务的等待时间还未到,那么该方法就会阻塞线程池中的线程,直至任务可以推行。

我们聊天与文字有关的话题,谈谈与完美有关的回看,说说平凡普通的生活

2.4 scheduleAtFixedRate()和scheduleWithFixedDelay()方法

       前边大家对ScheduledThreadPoolExecutor的要害性能和要害内部类都开展了详实的上课,基本上已经可以看到其是怎么样落到实处定时执行职务的效果的,接下去咱们最主要对客户端可以调用的根本方式开展简要介绍,那里scheduleAtFixedRate()和scheduleWithFixedDelay()方法的完结要旨是一律的,八个办法最微小的差别在于ScheduledFutureTask的setNextRun提姆e()方法的贯彻,该措施的落到实处后边早已进行了讲解,大家那边则以scheduleAtFixedRate()方法的已毕为例对该办法开展教学。如下是该措施的切切实实贯彻:

public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, 
                                              long period, TimeUnit unit) {
  if (command == null || unit == null)
    throw new NullPointerException();
  if (period <= 0)
    throw new IllegalArgumentException();
  ScheduledFutureTask<Void> sft =   // 封装客户端的任务实例
    new ScheduledFutureTask<Void>(command, null, 
                                  triggerTime(initialDelay, unit),unit.toNanos(period));
  RunnableScheduledFuture<Void> t = decorateTask(command, sft); // 对客户端任务实例进行装饰
  sft.outerTask = t;    // 初始化周期任务属性outerTask
  delayedExecute(t);    // 执行该任务
  return t;
}

       从上述代码可以看出来,scheduleAtFixedRate()首先对客户端任务实例举办了包装,装饰,并且开端化了包装后的职务实例的outerTask属性,最终调用delayedExecute()方法执行任务。如下是delayedExecute()方法的兑现:

private void delayedExecute(RunnableScheduledFuture<?> task) {
  if (isShutdown())
    reject(task);
  else {
    super.getQueue().add(task); // 添加当前任务到任务队列中
    if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task))
      task.cancel(false);   // 双检查法再次判断当前线程池是否处于可用状态,不是则移除当前任务
    else
      ensurePrestart(); // 若线程池没有初始化,则进行一些初始化工作
  }
}

       上述方法为重点的施行任务的办法,该办法首先会将职务参预到义务队列中,即使线程池已经开始化过,那么该任务就会有等待的线程执行该职分。在参与到职分队列之后通过双检查法检查线程池是不是早已shutdown了,假如是则将该义务从义务队列中移除。如若当前线程池没有shutdown,就调用继承自ThreadPoolExecutor的ensurePrestart()方法,该方法会对线程池举行一些开端化工作,如初步化主题线程,然后依次线程会调用上述等待队列的take()方法取得义务履行。

本人不会“挖掘”你的前生今生,也不会“粉饰”你的励志经历,更不会“在乎”你的璀璨成绩

倘若《对话简书人》是一杯白开水,我期待“原汁原味”,从开首一贯维持到结尾

我是冬少爷,我在此处,期待每位朋友的光顾

对话嘉宾:卿安澜

文|冬少爷

直面爱情,她很执着,可以等待,却不愿将就

面对婚姻,她崇尚自由,希望自己努力去搜寻

她对另一半渴求是那般的:温暖,有任务,有负责,宠爱她一个人,无法有大男子主义

他爱好美食,喜欢睡懒觉,还喜欢交朋友

她自称百变小卿卿,她是卿安澜,她也是本身后天的对话嘉宾

谢谢他能承受我的特约,祝福她早日找到属于自己的甜蜜。

【对话简书人】第八位嘉宾:卿安澜

冬少爷:很欢腾你能经受自己的特邀,简单的牵线一下友行吗?

卿安澜:嘿,我是卿安澜,很乐意可以邀请自己看成嘉宾呢。结业一年半的老姨妈了,现居中山,和严父慈母经营一家护肤养生店,是一名美容师。更是一个爱护简书,喜欢交朋友的小可爱

冬:活着里最欣赏做的事情是怎么样?

卿:最欢愉的作业呀,心很大,所以喜欢的事物有点多。比如说美食,睡懒觉,看书,最最喜爱的或者交朋友,和有考虑的人交换,那样会磕磕碰碰出众多的火焰,让祥和可以司考得越多

冬:能或不能和大家简要的闲谈,有关护肤养生的基础知识呢?

卿:当然没问题啊,很愿意分享,让越来越多的人加入养生护肤大军,让每一个人都远离亚健康。

首先说有的半数以上的人都可能会油但是生的有的问题。

1.肩颈题材,现在的人干活儿选拔电子产品的时刻过长,不难形成职业病,最大的问题就是肩颈过度疲劳致使的一层层题材,比如筋痹,脊椎病,腰椎盘出色等问题

2.熬夜问题,包含我自己,也是平时性的熬夜,现在的人备感早睡就不可能好好睡眠了。在保健角度来看,超越11点睡觉都是属于熬夜,而不是说凌晨才算。

说了那多个全民大题材,咱现在说说怎么去改进一下那个题目,学习一下怎么在家或者零散时间也可以革新一下这几个题目标。

本着肩颈问题,首先第一点就是在源头上找关键。

1.干活一个钟头左右,起身移步一下,眺望一下远方,下跌眼压。倘使不可能站起来,那就最简易的,多伸伸懒腰,底部前后左右转动一下,腰部以后动一动

熬夜的话,滴几滴薰衣草精油在枕头上仍然点个香薰灯,有助于睡眠

句斟字酌网瘾意况

冬:毕业一年半,有没有被身边人日常关心婚姻问题,你会什么应对?

卿:有的呀,家里长辈可担心那几个事了,平时说给本人找目的,老爸老妈也整日着急。

自己一般就是拒绝啊,我想要自己找,毕竟婚姻是人生大事,不想将就,想找一个三观合,性格合的另一半,过更好的下半生呀

冬:那就是说怎样的男生,可以走进你的内心深处呢?

卿:第一要三观合吧,对于事情的处理格局不可能相差太大,否则简单出争辩。然后就是性格合了,我此人可比强势,大大咧咧的,希望对方是一个采暖的人,只温暖自己一个人的人。

其余的负总责,有负担,就OK了

自然,不要太大男子主义呀,怕怕

冬:假定遇见心仪的男生,你会主动出击吗?

卿:本来会,不能给自己的人生留太多遗憾呀,心仪就主动去争得,人生把握在大团结的手里的。

冬:刚刚您关系喜欢和有思想的人互换,那么你是何许晓得“有沉思”那八个字的?

卿:有温馨的呼吁,有温馨的思想方法,有谈得来的老到的想法,不会人云亦云。锲而不舍自己的盘算,不偏激,也不偏执外人一定要援救自己。

冬:俺们精通爱情可遇不可求,若是不能相见,你是挑选等待,如故将就?

卿:不将就!!!如果没有赶上她,一个人也得以过好

就此近日的目的是,过更好的祥和,等待那一个很好的他

冬:咱俩种种人的爱恋观分化,我认为爱情就是相识相知相爱相惜相守,你的爱情观又是什么?

卿:互动尊重,相互尊重。相互包容,相互让步。

冬:不是种种人都会遇上爱情,大家总要面对现实,所以最后,不是您想等,就足以等的。父母不会让你等,时间不会让您等,朋友不会让您等,所以到那么些时候,你是否还会坚贞不屈,或者无奈将就?

卿:持之以恒,所以此时此刻,我盼望得以进一步努力,积攒未来可以一个人过得很好的本金,给家长放心

冬:一个人形影相对的坚定不移,四个人凑合着过,你以为哪一类更难坚贞不屈?

卿:五个人聚众更难锲而不舍,我不爱好勉强自己,更不欣赏人生的前程都勉强。

冬:大家将话题回归到辩论咖,近日在专题里是怎么地点,负责什么工作

卿:当下是辩论咖的话题员,负责辩论咖辩题的编撰。也有专职做辩论咖的文案以及辩论赛统计,未来会尝试做主持人等工作,毕竟自己是百变小卿卿

冬:品味分化的东西,对你的话,有何收获?

卿:最大的取得是取得不一样的技能,有时候须要自己去上学很多新的学识,其次就是培育自己的权利心和权利感吧,做怎么样事都有秉着负责的千姿百态去做好。

冬:是什么的机缘巧合,让主编大大注意到您,将您征集到社团内部?

卿:唯恐是自我平日可比外向,让逗霸注意到自身,然后有一遍聊天我说某个话题可以当做一个辩题来打辩论,所以逗霸主编就说让自家应聘话题员的。

新生闲谈发现,辩论咖做的不在少数事务和本人事先实习过得活动很像,就马到功成参预了理论咖团队了。

冬:你会以如何的观点,去认识您眼里的《辩论咖》,又怎么让小编更能主动的投稿那些专题?

卿:就自己而言,辩论咖是一个欣赏辩论,想要辩论的一群志同道合的人的成团。

有关何以让辩论咖走得更好,首先要内部的制度各方面要健全,其次就是要增强宣传力度吧

冬:投入专题之后,认识了过多小伙伴,你想对她们说些什么?

卿:第一是感谢遇见吗,遇见是人生最美好的事务。其次就是,希望和同伴们一齐变得更好,人生的路上因为际遇而更开玩笑,美好。

冬:在简书这些平台,你的笔名是什么,主写什么类型的文字?

卿:卿安澜,暂时写得不多,更加多的是想学学如何撰写,一向以来自己的作文都有点好,见笑了。将来主要会写干活以及励志类吧。干货,希望团结的一对经历可以让其外人少走一点弯路

冬:关于编制审稿的话题,你是或不是期待附上拒稿理由?

卿:可以依附的哟,可是出于稿件比较多,附上统一的拒稿理由会比较好。再说,一个人和好的题材也亟需团结想想,拒绝伸手党

冬:竣工如今,有没有相逢让你大跌眼镜的损友?

卿:到头来有啊,不过曾经漂流瓶见了,能够损友,可是无法大跌眼镜没有灵魂

冬:从前俺们在群里聊过,加上这一次的接触。你对自我的先导映像是怎么?但说无妨,我都得以接受。

卿:俺们聊过,固然有争议,不过不意味着我们不可以成为好爱人,只是观点不等同而已。我以为您是一个充裕有力量的人,自己开主旨,向你学习。你是我后面说的有沉思的人,希望大家继续有越多调换。

冬:即使给您三遍宣传的机会,你会如何推销自己?

卿:毫无保留,把最真正的亲善表现给大家。你若盛开,和风自来。

由衷是最好的推销自己的法门

冬:最终一个题目,你是不是同意,我将本次对话的所有内容,以文字的花样发布?

卿:不可以分歧意呀,哈哈

冬:重新感谢你能经受自己的邀请,天气那么冷,还锲而不舍打字,且认真的答复每个问题,劳碌啦?

卿:但愿冬少爷的专题越来越好,我们直接支持你

不虚心,也谢谢您的特邀,你加油哟

冬:祝你早日找到属于你的爱意,满面红光高兴每天!

贴心的恋人,假使你也想那样平静,轻松的聊聊天,说说话。请在篇章上面留言评论,也许下一位对话嘉宾就是您哦。

相关文章