QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1596|回复: 1
打印 上一主题 下一主题

Java如何优雅的实现时间控制

[复制链接]
字体大小: 正常 放大
杨利霞        

5273

主题

82

听众

17万

积分

  • TA的每日心情
    开心
    2021-8-11 17:59
  • 签到天数: 17 天

    [LV.4]偶尔看看III

    网络挑战赛参赛者

    网络挑战赛参赛者

    自我介绍
    本人女,毕业于内蒙古科技大学,担任文职专业,毕业专业英语。

    群组2018美赛大象算法课程

    群组2018美赛护航培训课程

    群组2019年 数学中国站长建

    群组2019年数据分析师课程

    群组2018年大象老师国赛优

    跳转到指定楼层
    1#
    发表于 2020-5-3 16:03 |只看该作者 |倒序浏览
    |招呼Ta 关注Ta
    1 S* j" I3 o. q8 V  ?
    Java如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。1 g4 u, Z* y6 t& r- ?

    4 E$ G" h) g: x; E$ N6 f; t' j一:时间控制的几种方案
    / I. n- M; Z' n, w* R
    0 k+ d& J( \! Y2 e/ ^% k0 |1 }1.1: 从线程方面解决
    & d1 Q- ]( r8 O4 ]+ w/ g' I
    2 V/ j, Z/ d$ G! j) m) Q最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    & g! i2 ?2 ~* g& ^6 X# f( g7 s1 a( W' q
    1.2:使用Timer
    " ]( _/ a; p" X* a1 c0 D# ^0 u: k7 Z# ?# v2 N
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
    ) D. B/ Z, }: {$ ?' d9 I: x! W/ {$ `7 m% F
    public class TimmerTest {' t6 \0 H6 D/ b# `2 |0 `/ y
       /**
    3 `% e8 x+ I# P& T+ B% ?2 I     * 测试方法
    3 R. l6 J- q8 ?9 V. Z- D3 c& X     */* \1 h5 t- H5 a% u- s
        public void test() {
    & F# K$ U; n3 _( S        Timer timer = new Timer();% m6 F) q: o* ^9 b: H
            timer.schedule(new MyTask(), 800);
    # }; o% h- [' d, y) [3 ^% o$ ~7 Y    }
    * y" d( p8 K+ R( l  R/ E$ N! l# g
        public class MyTask extends TimerTask {# ^1 X# h& a1 @% M

    $ g# P- z  x" S2 P3 K! g/ ^  C        /**+ L* Z6 X1 ~+ k5 Z
             * 运行方法
    7 k# y0 A/ g: V- v2 X         */$ }9 ]9 g3 ^" ^7 `' A  |
            @Override, `7 q/ c( e4 M  s7 S7 e* T
            public void run() {
    $ @6 u3 T0 c, ?) T1 R            System.out.println("输出");; y. V- b. i5 @8 O, n# o
            }8 w; W/ `# v! Y0 J
        }$ {5 d/ `4 I& v' V  W2 P0 m9 i" T/ @
    }6 Z3 C3 S8 ]. |- y5 I9 {
    这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。$ V  ~3 `+ b* i! k0 ^; M# g
    + Z7 U% d+ z0 c; n4 [5 {
    1.3:redis延时
    6 `" L) I' i- v9 n# q: w( P, D6 U6 w; N3 Z/ i
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
    ) q7 T' [3 [% P5 c. Y% I0 }- u+ {9 ^) J% k7 h; Z, U. y1 K

    & Q5 p4 n. f$ i0 f/ Y, E2 ?7 }3 ^- ~' v# a4 E3 l! ]! W$ E$ o
    通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    1 ~9 Z6 P- o# s' G  ?/ a0 M  q+ K* R' _# J
    1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制
    ' W8 p1 E$ d" F7 [! |
    . N( U. \, [, s' c" A2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
    ! X% A# @/ X" ^( n7 T% x) z
    0 {( B9 d8 |8 v6 V3:简单,真正的代码实现起来只有很少,下面会给出代码示范。; Z% N3 j% y+ F/ x0 c$ _) P
    4 ~8 G. Y' h4 {
    二:redis
    ; f7 F4 X7 _, r; l+ T
      |0 H+ q8 z9 Y# C2.1:maven中引入redis) a% N9 G7 y( a. {( z  j
    # W, N4 n: B: w) m
    引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    % P) e; c( W/ e
    ! _8 Z. S+ z% }* y8 g# F<dependency>+ J6 x/ P0 B( ^2 b- v- M1 F/ _2 S4 P1 T
      <groupId>org.springframework.boot</groupId>
    3 r2 A! |  v6 T! a1 E. d% }/ z% R    <artifactId>spring-boot-starter-data-redis</artifactId>: k' x8 E$ l, {  Z/ ^
          <exclusions>! F# x' N3 Y* Y/ ]* v/ l
            <exclusion>! h' Y& ?" S( Y+ ~* T7 {+ z
              <groupId>io.lettuce</groupId>1 Q" d; V  @. h2 }
              <artifactId>lettuce-core</artifactId>* T4 [) U+ L" u6 a3 l& w0 y% C/ l- V
            </exclusion>
    + l/ E" D/ f8 \) h9 {      </exclusions>( R6 @" p9 O. G8 {" ?; A; S8 w
    </dependency>6 p  ?: e, {$ q% U+ Q
    <dependency>
    $ P, m+ N2 J; q8 c. L/ z2 N7 U  <groupId>redis.clients</groupId>4 t7 p! r8 g4 B/ U7 S4 }, P
      <artifactId>jedis</artifactId>
    - i, @/ Z- c& ]& ?2 ?! J</dependency>7 T! \" g/ M3 d7 @/ b
    2.2: 在springboot中配置redis
    # j. N0 T: D* {. \% C
    ! k; N$ _% O6 z" o+ P9 r8 @import org.springframework.beans.factory.annotation.Autowired;1 v7 s" F9 W: ]( N$ Z
    import org.springframework.context.annotation.Bean;
    & a* i- \* U. w4 [4 Wimport org.springframework.context.annotation.Configuration;
    / ?$ i6 a3 |% m3 cimport org.springframework.data.redis.core.RedisTemplate;: v  B& d$ X* b; T3 i
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;  ?0 U% I" C3 y# B) Z
    import org.springframework.data.redis.serializer.StringRedisSerializer;6 y6 y; [) u" @% Y6 r. b

    $ i% a, R4 k5 p* P2 C" Q" F@Configuration
    8 r5 E+ W: E- `% I' i( tpublic class RedisConfig {/ J! v, Z0 Q$ V% \: @
    - C' N+ x' K* T# S0 ?$ n
        @Autowired  y' F9 N& H1 ]4 a, K4 l. h* W
        private RedisTemplate redisTemplate;
    . L. C+ ~3 E. H9 y
    2 @2 I: F! i, P" B    /**
    ( c; C+ N5 m9 v4 z     * redisTemplate实例化
      V' p7 _. _- ]$ B+ p! p" D- p     *
    4 y# K$ W7 O/ R! I     * @return4 ?* Y. n0 m; g  D
         */
    & V/ S; i) |( I( N    @Bean; J7 B0 \/ M( f' W
        public RedisTemplate redisTemplateInit() {
    2 W, Y% b; q- g( ]! ?( Q  J  `        //设置序列化Key的实例化对象; U4 t7 ^: N1 r" [4 ]. {+ H
            redisTemplate.setKeySerializer(new StringRedisSerializer());2 X: p$ z1 s/ W$ L5 i+ H5 |; K3 h
            //设置序列化Value的实例化对象; W4 T! C& l: K) a
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());' y% h! n1 H8 m( ^
            return redisTemplate;
    0 M& E0 o2 v5 @' \9 _& R! V8 Q    }
    ; S4 F- z) t5 X& F6 o& l: h( w6 d9 R5 [8 R+ ]6 S2 ~
    }
    . V' |0 f3 |0 h; ~; N4 ]# ~3 k2.2:redisTemplate模板工具类
    ) b# S( Z0 K# \6 z4 D5 g. p4 O2 F7 ^3 h3 n8 _- D  E& n0 y
    @Component6 l+ d, M# i( R* j' ]
    public class RedisManager {* N' f# V( a: T; @% T

    & P- Y6 I9 U$ ]' Z" [    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);; M& M% I4 j9 {
    % z4 S; P% R+ F1 a1 D. {: ?: ^  N7 u8 v
        @Autowired
    ) i& {; D( U7 W% d2 y1 R3 I, F6 ~    private RedisTemplate redisTemplate;
    - W4 t6 _6 V8 [. ]. X( ^2 g; [5 H' g' G" Y
        /**+ ~9 I) ^' L+ q1 I+ o# H
         * 设置对象
    + y% M1 P2 q/ G, M     *
    + q7 Z& Y! t% X( l9 B; Y% k' O# ?     * @param key key
    $ K0 D2 o! q7 `% Q; ]! I9 R     * @param value value值6 C; e" e) p+ N9 Y% v0 m
         * @param <T> 返回值泛型
    5 @$ W6 V" ^- J/ A3 \% r; d2 w     * @return 正确的值:<T> 错误的值:null
    ) u9 f# F' Z9 u9 P  u     */2 G  x! w: H+ |
        @SuppressWarnings("unchecked")3 R- K( ^5 W1 ?$ N* n$ D  R6 T# A! R
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    , ~; N1 q  [: Y. ?: E! N# F        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    6 x+ D8 n7 @' y4 K) c& R1 k        operation.set(key, value);
    2 y; g! u# R1 x( @- b" R% ?3 Y6 \        return operation;
    ) ~. T$ p$ g& h) }! E* I2 L, V9 R9 U9 M1 ~    }( Q$ U# ]- p# {5 G4 H2 n! p* u
    + y6 E$ N8 f! c
        /**7 @+ V  C" I- L8 ]1 Z6 j* Y
         * 设置对象及失效时间 (单位:秒); T) i8 r6 V8 h
         *
    , t4 C' h* [9 b7 `& b     * @param key key( G7 H' u" ?4 X/ v1 l4 ~
         * @param value value值. p( y* c! j, |2 C$ d$ Q6 _
         * @param <T> 返回值泛型4 V; ]  `9 T) u# A
         * @param time 秒值- ]/ {2 ]* R& q" M
         * @return 正确的值:<T> 错误的值:null
    8 w- u# j# N( k9 b; @. @- c     */% b% P5 L: M5 c" S5 b* O
        @SuppressWarnings("unchecked")/ z) R; f4 M: A# Q8 b
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {
    ! `9 i! {3 |% V, j  U  B! L        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    / o  C1 [5 H$ x" \2 R        operation.set(key, value, time, TimeUnit.SECONDS);+ H& p4 w. a8 C& d8 l; A
            return operation;
    ; ~9 V( f& c: L* V7 Q6 H    }
    $ k0 ^- q8 U, X/ E2 N: W& @2 H
    6 j  d4 @: d$ f  c$ s
    ) Y9 h4 H" u* [/ q8 }$ ]3 o' s1 j8 q    /**8 e0 {: j, ~1 V! r1 J4 ^
         * 设置对象及失效时间(单位:毫秒)
      S9 A* j: K; D8 y5 M     *
    ( F0 z% y4 s* S3 k" }     * @param key key
    ; s1 \: A8 \2 T) f4 r     * @param value value值
    5 \5 C: J3 Y5 |9 |8 ~     * @param <T> 返回值泛型
    6 S( O% u9 c1 U1 ^3 q3 q     * @param time 秒值
    2 j8 R( }3 W4 |3 Q     * @return 正确的值:<T> 错误的值:null
    9 s  t4 B- ^1 Y     */$ v3 [: L  j+ c3 S9 F; d7 m
        @SuppressWarnings("unchecked")9 L' W( f* c% X! `9 @3 Q( m
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
    ( b) f8 X! J/ J8 x        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    ( M9 _* x! g# L  h; l/ [0 ]. h; g/ C        operation.set(key, value, time, TimeUnit.MILLISECONDS);
    6 N: l5 i4 g! S4 r! O0 v        return operation;
    ' {8 N1 E9 A6 i1 N. `    }
    . c9 q, s. o5 B( V1 S  x* X4 _8 t4 b& M: W
        /**
    ) p/ I4 m" q3 _- S! K     * 获取对象
    ) W  c: ]* F; a, q. Z     *( Y! {, H! k- c+ {8 r
         * @param key 键4 l4 ?2 z8 d0 C! g. `
         * @return 正确的值:Object值对象<br>3 R& g# T2 J9 i* u9 c3 c, d" |
         * 错误的值:null. z1 X# ~% N% h
         */! }  `  h. L2 [3 o) u5 |5 n
        @SuppressWarnings("unchecked")$ T; W% b" Q/ m
        public Object getObject(final String key) {
      x  q& \& g5 H) S! J+ B$ P- c3 u$ V        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    # W9 u% V$ Z6 J        if (valueOperations == null || !redisTemplate.hasKey(key)) {
    % i7 }2 y( v' e2 P1 y            return null;% X( h8 l" V7 J* J  p
            }; Z- z5 o5 u! x( K" b
            final Object object = valueOperations.get(key);
    0 B* D6 }. t# O8 g6 N+ G  _        return object;3 K# X4 X8 j2 ^4 Z
        }6 N* q' F& {$ H( x  d0 v  _1 h# x' }

    5 s& ^9 t4 U! K( t& {( A    /**5 Z- y7 I) T2 w! b# G+ @( x
         * 从缓存中获取string值: p4 y. x, O5 B8 L+ u" [$ m: i1 Y* e
         *0 ]0 I8 q. L- a2 z: q- x
         * @param key7 X8 _( T% D' \" A! b
         * @return*/( x: H8 ^1 L. b- @
        @SuppressWarnings("unchecked")# n  K6 Q8 y: J2 f8 }
        public String getString(final String key) {9 {% a( N$ a1 ~, m' \% z
            String value = "";1 m3 s/ E0 o' g! L9 n
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    $ U/ i* x& Q) g5 J        if (valueOperations != null && redisTemplate.hasKey(key)) {
    " s/ @# m1 z5 a* \  @            final Object object = valueOperations.get(key);
    % M, g+ L  ?9 M9 c            if (null != object) {/ |! m: e: V: B; w4 P7 e9 M* `
                    LOGGER.info("--getString--object not empty");
    % u! z3 w: J2 B" Q) a                value = object.toString();* E' j8 y- j, l+ Q4 L( h+ v7 y
                } else {
    9 v9 R5 |% A+ H# a+ X                LOGGER.info("--getString--object empty");
    1 z* S5 S- P: c  s7 T0 _            }
    5 m. |" ]' `7 l- G/ Y+ k1 g        }2 A1 M+ s/ e3 Y  ?3 `. Q% e+ Q
            return value;
    * f2 x4 \; N5 D2 d3 S; i, s: B9 k    }
    # M# P+ I5 l- V2.2:在redis中实现时间控制* @, ?$ ~: U- Y  |

    ) O( Z: f+ a. v* f  |2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    8 B: ?& w! d/ p/ U
    3 u# c" w7 W, w3 simport com.youjia.orders.redis.RedisManager;9 v0 F0 B5 N9 |! ^$ G/ L3 {) B4 T  M
    import org.junit.Test;
      N, L0 {5 h+ [# simport org.springframework.beans.factory.annotation.Autowired;5 `" F  e" M- v  Q. b' |+ I2 y$ a

    + S9 p& H$ e; o: @/ n% Wimport java.util.Objects;& T+ \4 `( K9 y+ g  z

    9 v% C7 H+ A' `: n2 Q5 G0 b4 j% [/**
    : x& R6 c- j" C0 [# A9 \8 O6 r * @Auther: Yrion0 j) z9 ?/ v- d9 ^) s
    * @Date: 2019-01-11 23:36+ H! K& W4 N# j  M* Z  e
    */
    / d8 o4 K) \/ E9 R3 w2 J8 v/ c9 F  `( G
    public class RedisTest extends OrderProviderApplicationTests {& ^1 `$ }. u% P6 Z  V
    * d3 X% v1 s# J5 w& U
        @Autowired# S% M& l# w; }3 H" L
        private RedisManager redisManager;& U6 e! X8 \3 V+ J2 ^
    * b5 k+ E( p& C$ a  s
        @Test
    # }$ C4 E2 o/ A8 v& Z" ^    public void test() {
    0 E. {% Z4 ~: J        controlTime("10000001", 10L);
    & g9 Z+ ]( P0 m) T" A- z- u    }4 y, }, v4 I) u! p
    8 ?( n; I( ~% k, ^7 U' M: ~
        public void controlTime(String requestId, Long timeOut) {
    / E' ?, w+ ]/ `. Z. t$ r( {" l' b) s! Z  c1 l5 ?) o" N9 S' |
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {
    & u& b+ k* X$ H4 ]9 y            return;. I$ g! M# R; N. A, l
            }
    ; W0 p9 r) h( G0 b        //something code
    % W4 U/ \3 |7 W% ]# a1 z6 E        final String value = "value";. D6 H9 Q% o) [2 A7 z: a3 B5 `
            redisManager.setObject(requestId, value, timeOut);
    # z' x1 [6 q$ a8 ^# |8 x        final long startTime = System.currentTimeMillis();
    7 ~0 F# p9 j; r. U        System.out.println("开始控制时间");# b" b4 h$ Z* y0 ^
            //start
    . Y9 ^8 R1 V- P        for (; ; ) {7 p6 l) p4 _9 N. |& j7 r9 ?9 t
                if (Objects.isNull(redisManager.getObject(requestId))) {4 ^( P# e! E0 p, h; i( H# @9 [9 U
                    break;
    % T( x6 V+ m% S0 X7 \/ i. b% ~% [            }5 C3 m9 e/ t. J0 L$ f/ Z5 N& O
            }
    3 A+ ]2 J4 n$ E8 x7 P        final long endTime = System.currentTimeMillis();+ ~* W& \/ X1 D
    , ]! i" y1 I; M' [
            final long useTime = endTime - startTime;
    . l5 b3 K( }2 E! v3 J# E8 k
    . Z0 |1 @" Z0 r1 H( v; E# T' D        System.out.println("一共耗费时间:" + useTime);/ }  a6 r1 }, Q. g
        }
    5 K" y' x3 ]7 k$ l5 i. P}
    9 L( _" K8 Q( z( z6 F9 ]outPut:
    % C# U% p; x# L0 H) A
    1 ^& q0 r" ]5 ?' u) ]开始控制时间
    $ p1 ~7 V7 M7 G一共耗费时间:10042' g1 ]. a* b; k& d
    三:总结
    ' n7 }+ |- r4 g
    0 m1 V5 b4 q7 m本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!( Z* n+ c2 g: q
    ————————————————; C6 n" d7 g9 b2 a5 {3 j
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    - r) D4 f( n+ X" ?3 C$ |6 G原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325* p# |+ e' {& E

    , M+ q1 I4 K) L; b  U0 J
    ' m/ @- }- c' a! U3 y
    zan
    转播转播0 分享淘帖0 分享分享0 收藏收藏0 支持支持0 反对反对0 微信微信

    0

    主题

    0

    听众

    27

    积分

    升级  23.16%

  • TA的每日心情
    慵懒
    2020-5-6 10:54
  • 签到天数: 1 天

    [LV.1]初来乍到

    自我介绍
    马上比赛了

    邮箱绑定达人

    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 注册地址

    qq
    收缩
    • 电话咨询

    • 04714969085
    fastpost

    关于我们| 联系我们| 诚征英才| 对外合作| 产品服务| QQ

    手机版|Archiver| |繁體中文 手机客户端  

    蒙公网安备 15010502000194号

    Powered by Discuz! X2.5   © 2001-2013 数学建模网-数学中国 ( 蒙ICP备14002410号-3 蒙BBS备-0002号 )     论坛法律顾问:王兆丰

    GMT+8, 2026-4-20 16:29 , Processed in 0.435951 second(s), 57 queries .

    回顶部