QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1611|回复: 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

    8 B+ T- b* I1 |7 D4 T. a8 S$ FJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
    + m) U" ]5 S+ T1 }
    ! c' u9 l4 P8 e5 f1 Y. I一:时间控制的几种方案5 s0 l; i: b' F7 Y% _
    1 e6 `' X1 J" e- q0 C0 T+ L+ }. X  d1 r% L
    1.1: 从线程方面解决
    - x% V7 t1 O* ?( ~6 s: f- G
    ! A. ^( X& p( d6 X最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。" [# r" F) c* F% j

    8 n- b7 l0 c8 E  W8 ^1.2:使用Timer
    . C& J& ]5 o; X, G  H* i  M
    3 k# j; `7 v- O& I$ y! {查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:1 F: G( I( \7 P/ u
    ; r, i, \$ n, C
    public class TimmerTest {1 d8 t9 G# @' m  t
       /**
    : x9 x1 N5 }: J2 e& C4 b( M. h     * 测试方法" G9 ~" s4 Q3 t3 A) r
         */
    5 B. p: Q" j0 o0 K: e    public void test() {$ d( n0 k4 P  k
            Timer timer = new Timer();
    9 W5 N* {2 z. G; Q2 F% r        timer.schedule(new MyTask(), 800);
    ) B+ ~% m: \$ `$ }    }
    ( {/ D: d; P* a( L
    # E7 {9 G( M- L    public class MyTask extends TimerTask {8 S+ I7 w& n# m6 g
    * ~& ~$ o% ^5 P( ~/ h$ t
            /**
    * `6 {% E$ c5 R& U/ ?" F0 [         * 运行方法/ R: N$ M" m( U: ^% K: W7 s
             */8 y" Q; r+ i, v5 M
            @Override$ S2 @3 K8 ?( ~* e9 P8 R, k
            public void run() {6 y% l6 O: A* e) i$ I" o
                System.out.println("输出");3 [+ h* ~% T; I- b" e
            }
    . @/ Y+ H* r9 ]# {8 y/ Y* A    }
    7 F* a# A$ D$ T, ^3 i& l}
    . o0 x5 i4 ~1 \! n+ O- V) W+ M这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。4 l" }  a* e) `$ V3 n
    0 ]% W. X6 p2 E8 h. `" O
    1.3:redis延时  e" B5 n; H  |, i6 {

    " p2 [3 f  _8 W1 M  v, B6 z在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
    7 w" s; l$ z# Y# @+ F- |% r% c
    7 `) j) S! P/ _% h: z# j4 F& W, q+ a' S8 `% ~7 Y* j

    ' |/ z3 d$ x# ~8 e3 c9 S通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:" R, ~0 ?* Z7 o6 Z

    ) P6 j! b; d6 T+ X9 A1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制! M# a  E  P" U$ X

    5 D* S% K. {  E+ d) B2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
    - p; I/ i# a+ s5 s: T) F# w2 B! U. I5 e
    3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
    ; L" t5 _/ ^1 \6 r: o$ Q2 S  {5 `: [; ?9 ~( v# u, M4 a9 |
    二:redis) z: S9 z* C1 |

    9 j* g+ ?& l( Q' ?9 }- `2.1:maven中引入redis& x3 S* e& {. m; d, A

    3 Y7 v* u9 L- f引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    ) K1 p# F% D1 k- a; w! F
    " p. f' k5 V; C$ U# a0 e; J1 d<dependency>
    8 o# Z2 @9 B# j9 n  <groupId>org.springframework.boot</groupId>
    ; S' _; o: e+ W    <artifactId>spring-boot-starter-data-redis</artifactId>  d; B& j# \9 M7 k
          <exclusions>+ Z# f" x/ w( J' l/ I
            <exclusion>9 n" g  j( t; g
              <groupId>io.lettuce</groupId>
    ; `2 Y% Y. {! ]! j$ R0 ]          <artifactId>lettuce-core</artifactId>
    7 `5 l$ T9 I* m3 U8 y7 C( l  m        </exclusion>- E" t# \) Z5 K, \6 y, C$ S7 k' N5 m% ^
          </exclusions>2 r+ }+ Q7 @7 T5 q; n  s9 f/ t$ y5 K
    </dependency>& _7 M2 T& J3 f  ^8 \: O2 |# f4 n
    <dependency>" Y/ t9 _8 B! @
      <groupId>redis.clients</groupId>0 ~- q9 I1 _9 Q$ v  p
      <artifactId>jedis</artifactId>; P& Z3 Z& J7 W$ J
    </dependency>
    * S1 c" `2 y5 b) r# p- F; U2.2: 在springboot中配置redis
    6 b! v* Z# @$ V$ n# J! H: i8 W* Q. c' C
    import org.springframework.beans.factory.annotation.Autowired;6 `2 Y: C! H& f0 v
    import org.springframework.context.annotation.Bean;0 C3 e& L, X  T8 O
    import org.springframework.context.annotation.Configuration;) B9 E* s9 T. |: o9 Q& ]; {( G! l
    import org.springframework.data.redis.core.RedisTemplate;
    7 v+ [$ J/ `% R8 k6 O4 J; S; Uimport org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    5 G0 F/ p0 d$ i( c4 F( [& n- Y# X0 iimport org.springframework.data.redis.serializer.StringRedisSerializer;" f* ^9 \9 k4 a5 M
    # W% w! \0 Q: k. z3 {/ y4 O
    @Configuration
    ! Y3 V! o* ?& g* C- ]public class RedisConfig {
    1 r' V. r: F: C" b! J  K0 l) s
    / j% U/ R+ L4 P$ B" o    @Autowired
    ! D& M% R6 }+ S) }/ w/ [    private RedisTemplate redisTemplate;) @) B( B2 G3 B
    0 r" c, {% i: v
        /**
      g  i% r# C& P& O     * redisTemplate实例化3 \! ~( b9 }5 P7 v2 u
         *2 z' w" X8 L' i! I6 j
         * @return- q7 E6 z* p, S4 I' l
         */
    $ J, ^! {1 v9 a* I7 [" E" T    @Bean
    5 x8 W5 z( c! j6 [) a( Y! b; M    public RedisTemplate redisTemplateInit() {2 Y& X( e3 u% D& V( Z
            //设置序列化Key的实例化对象
    ; |9 n- L6 P9 o6 d- @% t; K. X        redisTemplate.setKeySerializer(new StringRedisSerializer());; E! c  e8 }* i9 ?& E( K8 S
            //设置序列化Value的实例化对象
    # Q) r) q+ t# V4 T: b4 m        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());& {, }- z& l) G3 }8 n, f6 }) e. E# G
            return redisTemplate;
      h5 j# o: |6 B0 B* @: S) t    }
      V# P6 {, O- B3 A, ?# r, X# P( G4 N! D6 Q
    }; r9 X2 ?- I) \
    2.2:redisTemplate模板工具类3 `8 P  _2 Q: p8 ~, s9 q
    " [$ b, Z, L2 y8 X
    @Component
    9 S8 O/ H+ X) N% W( `9 v  l6 kpublic class RedisManager {
    6 R) N5 x% r$ @0 x- ~- Z. H# Z" S" d: s% a, y# c: y- N; E  O
        private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
    ( ~; X9 [" M8 o- w- c
    . d; I( F# c/ b- f  Z    @Autowired  I/ e3 X% @! K% b; h
        private RedisTemplate redisTemplate;
      T' n7 w4 J+ f1 a
    ! B9 `, V/ g$ r    /*** I  n) a4 e1 B3 z
         * 设置对象
    8 O3 Z. O% K( f  O; M! m     *
    / G' m7 J4 d; M8 q5 A  n1 w. e     * @param key key! l- o+ b) w: ^' s1 q
         * @param value value值0 f2 p7 B; q' ]  Q
         * @param <T> 返回值泛型" p  B+ I% \( t2 h. O/ p6 f3 c! F- V
         * @return 正确的值:<T> 错误的值:null0 `' ~" a& Q" n2 Z2 c( n8 W
         */: S; n) K' l9 N: W/ V9 ?
        @SuppressWarnings("unchecked"). |# _, h2 x9 ~' k" B8 T# R+ d
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {+ g* g; m8 x5 ]6 J; k. j4 K
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();1 X2 q9 W. e1 O
            operation.set(key, value);: Q8 M! c7 q0 {% l
            return operation;4 r7 K2 y* z9 b: _) A3 U8 J7 z( i5 E
        }  @  p* F& {: c' t7 @

    ' _# U0 a2 n4 {% F    /**" Y4 T- T. _+ ^" e7 G$ b# U4 x8 S, R' P
         * 设置对象及失效时间 (单位:秒)
    + g5 {! {2 ?. P  z5 J( N1 ~     *# O- v" T7 {8 M6 h' u7 ~
         * @param key key" e4 h1 o, C( k- ?4 p
         * @param value value值3 Q. z% ?# |' U  r0 Y6 h4 m
         * @param <T> 返回值泛型
    3 {. c. c" Q9 U4 b( j4 Y     * @param time 秒值8 K( Y, r" `% V  }6 P# L
         * @return 正确的值:<T> 错误的值:null  v! u* a, G) O+ x. m! `
         */
    6 ~* e% v( E/ j    @SuppressWarnings("unchecked")
    $ @# w) V, Z# O. H8 b2 U    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {
    2 l/ ]* e! I: g$ C        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    5 r7 ~# w. T" s. d. n/ y        operation.set(key, value, time, TimeUnit.SECONDS);5 d. K, W: T+ t0 b  t+ h
            return operation;8 A4 u" h4 H) I( h9 C. x+ u* {% p  Z
        }
    ( ~) P1 k2 R- E
    $ q, L9 N2 D$ P  U% Y$ k: b' _) c' ^- A( Y
    ! M$ E& \6 n& T  U. O% M    /**
    ( P  p  }& Y$ N( P0 o8 k, ^/ ]     * 设置对象及失效时间(单位:毫秒)
    - _' ?( R, p7 Q7 ?( r7 r5 t     *! G" S7 H: F) t( I/ G
         * @param key key
    - E+ `2 O" \- o7 J7 |1 N     * @param value value值
    , i9 X* @; V$ M6 J1 F. L* ]     * @param <T> 返回值泛型
    * D0 Q4 ?; W+ V1 g, q2 a$ {: D( U     * @param time 秒值# X6 N) F; \# ^0 Z! O3 M
         * @return 正确的值:<T> 错误的值:null9 j5 k; r/ P4 A5 E9 e! ~
         */; {  b% I; S/ Z$ U; B! x' o1 h
        @SuppressWarnings("unchecked")
    * V! i2 z( w! w( p0 C    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {: N8 R* J  v1 r0 G% v
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();: f; h6 X3 f( ]- [& c
            operation.set(key, value, time, TimeUnit.MILLISECONDS);' k. N0 s1 E( I5 W8 q5 g  G- L
            return operation;* T' W) X' s' \  t
        }# w: y! ^; T( n. w
    7 O* w' B) B2 F2 H9 F
        /**5 h" _# g6 _, J! t% ?. F- ^6 x
         * 获取对象3 T: B% z* A( s) l$ T/ s
         *7 |' Q, h+ x/ ]# z+ Q" T8 I, h
         * @param key 键! ~( S8 K7 ?) l+ X2 l% Q
         * @return 正确的值:Object值对象<br>
    5 }; b, V, T( C9 d- ?     * 错误的值:null
    ' Q! _, h6 Y/ f5 D( p& A     */0 t2 U( T0 E6 k
        @SuppressWarnings("unchecked")% V& e* N% x0 J7 [
        public Object getObject(final String key) {1 l! W$ ?6 P+ x5 ~! z7 |* o5 F
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();2 z5 ^# j' s# ^0 p! e
            if (valueOperations == null || !redisTemplate.hasKey(key)) {% D* [# M6 T$ u8 n8 Q& |' m
                return null;! v) T  {! e% O- Y8 t
            }
    . F+ [. O. `) ]) M- `9 Y( h        final Object object = valueOperations.get(key);8 g/ M$ z! S5 C! w7 Y& y
            return object;
    2 a+ ^4 ~& g2 A    }
    7 K1 v  T9 }- g) t0 d' I! W0 d( _+ I3 _
        /**: a- W+ p( h, X9 `' `
         * 从缓存中获取string值
    - F6 y, z7 x; O$ \) _1 T     *; C" g1 p  x# X) A. V/ t4 ]
         * @param key
    ; a$ M- c# B, w- D+ `' Y2 E) U     * @return*/
    1 P9 G9 U- z# G    @SuppressWarnings("unchecked")
    5 a- p! k8 e3 q* ?, e    public String getString(final String key) {. [7 n& O# G' |$ o+ t; t4 `, O
            String value = "";
    , k7 t3 |. S" }; u6 k        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    # D# j# ~' E% O7 W+ ~  V* h        if (valueOperations != null && redisTemplate.hasKey(key)) {
    ) h3 {. K5 H; H5 J8 _            final Object object = valueOperations.get(key);4 j. _2 E8 {2 K* l! g  |
                if (null != object) {
    3 L0 p. Z2 r+ s                LOGGER.info("--getString--object not empty");
    " C" S: `4 D1 A7 P' V7 Z/ R                value = object.toString();
    ( A- n  {7 w9 t- Q5 L6 ]            } else {- p1 E5 ^' K5 N4 P. p$ ]
                    LOGGER.info("--getString--object empty");& f4 w1 K3 R9 _, L' Y" a& c- C
                }! f3 l! \/ B. G  T# T% ^4 W* x
            }% N0 N9 s) R5 m' x& [, t8 J" Y
            return value;' \# X* j. j2 l9 d7 d" P/ g
        }7 T4 k- ?) B! G: @
    2.2:在redis中实现时间控制
    7 q* e; _) c$ g6 J3 O
    9 g; u  @2 `: l+ P2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    ! Q0 B: R) Q: O9 R9 n) }) j
    7 V5 L' Y( J6 }$ Dimport com.youjia.orders.redis.RedisManager;# Z' N* t/ K9 G5 }; v5 D, `
    import org.junit.Test;4 I$ ~6 ~# u0 x' i! C
    import org.springframework.beans.factory.annotation.Autowired;' d# l3 e+ g, `$ X& A

    8 v) O/ f$ ?- [import java.util.Objects;
    ' B+ a) h7 y. t; `' M9 j  K* j& F1 I4 a, u
    /**% y5 T2 E+ b! `" E& D
    * @Auther: Yrion: I$ x( y6 J0 e9 v
    * @Date: 2019-01-11 23:361 B3 K+ w' T& O9 O
    */4 L. ]9 K' B. E, _, Q* v# X, p
    7 N0 y# M1 \) c/ B* s
    public class RedisTest extends OrderProviderApplicationTests {& `( {4 u" |5 \$ {) z% q  l8 X
    5 I* A$ T" ~) q+ T
        @Autowired& l- V6 g) T/ x0 r5 m, S* z
        private RedisManager redisManager;8 Y4 ^/ z& P& ?+ ~# h4 @
    ; Y! F6 v, r$ p% I& b
        @Test
    ) }6 o' k1 J6 T! a    public void test() {: c  s  }/ f* K- r4 i
            controlTime("10000001", 10L);
    6 p% _- R( k$ M9 W* D    }+ s& p+ d; W" p
    1 j0 L, \9 ]( v2 H2 b
        public void controlTime(String requestId, Long timeOut) {4 F- ~1 x- o! S- I$ `( ~& U

    ) e# u$ B6 ^% j% V1 B& r0 h4 u        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {
    + Y) M* P- G' y1 j+ G# Q2 a            return;
    5 R' z- ~7 F( O( n( R: B        }
    $ A  \: Y+ r' j( `+ I, {7 T" {& U+ Q        //something code( U3 o: w8 n; V4 Y! X2 I3 w
            final String value = "value";' \9 O  u, M/ p, D7 j' r. r
            redisManager.setObject(requestId, value, timeOut);
    4 f0 j% \2 f' Q; P: \4 \        final long startTime = System.currentTimeMillis();
    / M, N6 k9 y. U1 y& i! J        System.out.println("开始控制时间");
      Q/ _- u$ ]( P5 M$ A* q        //start7 a' X- ], K0 j2 r1 V8 w# c# \2 f
            for (; ; ) {- t7 \, v* n' n& V( b' ^
                if (Objects.isNull(redisManager.getObject(requestId))) {
    : e1 W) u7 _  |6 O) _7 e' b: q$ R4 e                break;0 t; ~  H$ n1 y) a( q
                }4 N* b6 L! Z  u/ q8 b
            }8 P, ^0 @2 q. ]. F: v! P
            final long endTime = System.currentTimeMillis();
      Z3 V! I+ x6 r* A: Z) `" R8 e, b9 ?; G' a  }5 M
            final long useTime = endTime - startTime;; d$ o4 K8 z5 q( J' I  s0 O
    ( Q0 m; q) ?; ]' P
            System.out.println("一共耗费时间:" + useTime);1 s% ]- ]* W( d5 i- Q/ G
        }
    & `& T! ?! A9 O4 e; j8 a}
    , Y; i; |! Y# _) x& B( N- XoutPut:
    ; G( ?) r/ X' I- f' j" @: I/ d7 y& K7 a* A
    开始控制时间6 W3 d0 q/ ^) U/ z% [! f  b8 ^
    一共耗费时间:10042
    : x1 M- r: ~' T三:总结
    : q" k2 n8 \  e8 d' P; a: `+ H3 j" k# K6 v3 v
    本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
    9 d$ K+ f4 E3 i————————————————% c( u6 v; O; Q! Q5 n7 [* ~6 B
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。0 @6 y6 i' X; @8 O/ `
    原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325# \9 S! R( t5 N/ x

    + \$ O0 Y' _- O  O& X0 V8 ?5 P6 V: `% M
    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-6-9 18:49 , Processed in 0.525950 second(s), 57 queries .

    回顶部