QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1613|回复: 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
    ! X- p  X! L( x" h* f: P
    Java如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。* c$ ~* W) H* M2 T* [

    4 p: ]6 v+ N0 F$ U% ]+ O一:时间控制的几种方案
    : g9 M0 r, t5 V# ?1 d( Z; x5 N! s  ~( R4 K9 W, x0 ~
    1.1: 从线程方面解决
    6 D- }. P! s6 G, a
    + I# o4 |8 y9 l7 c; K" w- Q! ]& P6 N最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。) S4 B# F0 T# e$ j. K; ^# |0 P
    $ F% H3 W5 {' p/ f
    1.2:使用Timer6 L- Z; B4 r: V
    * I# g9 P1 {* O" b
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
      m1 j8 z7 m: }
    ( P0 y( q, n+ _5 ]$ v6 jpublic class TimmerTest {* q2 s' @" f# b. Q/ @( T+ g- I/ I
       /**
    : u) R! ^; I. T* ~4 O5 H0 n3 n2 W     * 测试方法
    + x4 [7 f2 Y3 e. \     */
    ) M. C5 I: a: [/ u( {1 h    public void test() {0 K$ I) K* J( m0 j! d
            Timer timer = new Timer();
    0 G* \, V4 A! u1 _5 R  a        timer.schedule(new MyTask(), 800);; K" e3 o; @& I% l, r+ [! t! U
        }
    / v3 e& ?$ |2 Z% b5 _* ^5 X
    & }& b  [1 ?- }6 ~    public class MyTask extends TimerTask {
    7 P/ R% \# y$ C# t' ~% L' h2 b0 W& w/ X) R* B  N
            /**
    2 C9 B) C& g1 r) G, [         * 运行方法+ \  r3 X& C* e0 j' B
             */
    8 G% f# p; |+ c: n3 g1 [% Q        @Override9 Y& Y, Y+ v* T+ I- d2 ]. c
            public void run() {
      t8 \8 U( k+ [8 r. Y8 U1 L7 N            System.out.println("输出");: l% \4 ?/ ~% `" n- w7 Y
            }
    ) K4 W# q5 L% j$ p    }+ H: F6 i3 `/ B4 A- T
    }
    8 ]0 V, v5 M: I" a" l6 y+ r. U$ e% _这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。( T5 E# o/ b# s7 X

    ! ~: V/ l7 p" V4 a9 \9 F/ j1.3:redis延时
    + `5 u+ _+ V2 m0 ~# r9 S4 F$ L$ \( V, ?' E& s$ s- [4 \
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:% l* x3 u& k( ~8 H& q* d3 a$ `

    9 [8 s3 r. @2 J+ W4 }- z3 F% V% [" G
    8 |/ s) I# o3 o1 U6 x. ?
    通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    * Y7 ^) `: i  J
      V' |4 K* \1 `+ @1 j! N# a" e1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制- i7 `+ a" a$ w
    0 f# |$ @! B% c) n# F
    2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
    9 s+ }, X- N0 Z0 X6 o9 G, o( Z7 @
    ) b* W7 V; C8 y, x) e) r6 N( X3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
    4 r+ V5 Q0 E1 Y: `' I0 o1 {5 a8 M; R) t
    二:redis
    # j& }/ C0 x: x& o  E  G1 ?" h* U/ x
    2.1:maven中引入redis9 U0 j9 u- n  r" x; ]
    . A1 W1 @4 J; ^; V
    引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。) g! y* ^1 e7 {; p9 i

    5 j" `+ ?1 p) ]5 K- k<dependency>( ?+ P) j% m3 a0 E7 p- I: r
      <groupId>org.springframework.boot</groupId>
    % ~. ]" ~. A; ]    <artifactId>spring-boot-starter-data-redis</artifactId>
    3 _6 O4 g$ x  P1 Z# V5 v+ |      <exclusions>  \5 R8 A' x4 o2 v& h
            <exclusion>9 l2 }- T- ?) D
              <groupId>io.lettuce</groupId>- L! n! L! @8 T6 x% b
              <artifactId>lettuce-core</artifactId>
    % l* P# |! w1 ?9 Q' b; E3 y6 r        </exclusion>7 B* ]% e3 _! T
          </exclusions>
    # z$ @' h6 ~8 `; u$ B</dependency>
    ! t% v2 V2 \1 g& t4 ^' M' T: i<dependency>
    ; R! ~& P% }7 Z  <groupId>redis.clients</groupId>% s# h- m! S+ O8 ?
      <artifactId>jedis</artifactId>& G6 n) i2 {0 l8 y7 L% g
    </dependency>
    ! X. K4 B& y$ {. D& {' b2.2: 在springboot中配置redis
    / C! P" G) t- z
    2 `( A8 R* k! C2 x1 h* zimport org.springframework.beans.factory.annotation.Autowired;9 {0 k! u" `% V
    import org.springframework.context.annotation.Bean;; H+ r; J6 C9 E' ^- a# g
    import org.springframework.context.annotation.Configuration;) t/ [6 G' W! @( X/ @/ w
    import org.springframework.data.redis.core.RedisTemplate;
    ! t2 B7 B8 I) J: Cimport org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;( ~9 i- ~# J# c1 s3 o; g- M. ~/ K/ \
    import org.springframework.data.redis.serializer.StringRedisSerializer;8 e( o' T5 H5 I* W- e- Z

    ) M! u3 D% O* {6 E  A@Configuration
    " p! x2 c1 _5 u* y$ q8 Apublic class RedisConfig {
    5 {# [7 k6 ?8 `- E& x2 Z4 c! `1 W! s; C% p9 K- j8 o6 i7 L. n  t! P
        @Autowired! G/ O/ `) E. h- y
        private RedisTemplate redisTemplate;
    . b) B. y; y- Z( @% `+ K  H0 J: }7 J( t/ V: w- N5 Y6 }& ^
        /**
    % P. E) n: r% z# J7 t& j     * redisTemplate实例化
    " m) h! I3 u) x+ m  ?, K* F     *
    ; J  k1 P% U: |4 z- ^  N* l" V     * @return
    5 S. O. d. u/ }9 X     */
    7 M$ E4 s  ^: b' k( K$ \    @Bean
    5 h% P' u9 ]4 N3 k. |+ {+ _    public RedisTemplate redisTemplateInit() {
    " N0 G) V, w( D$ ^1 _* L* o        //设置序列化Key的实例化对象5 ^2 K, D- h6 E+ c
            redisTemplate.setKeySerializer(new StringRedisSerializer());" e: R- Z1 T0 X/ r+ V. G# O' K
            //设置序列化Value的实例化对象. p+ r' E5 U7 S" {
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());4 s* e/ T; k# s  |5 L
            return redisTemplate;
    2 W) b" |/ P0 O; O    }4 X( B* R7 ], |) b/ M) e* x
    ! E  x- ?; S( o5 @* L7 V
    }
    0 }0 d0 ?5 g# s& I4 i$ ^  L2.2:redisTemplate模板工具类
    ; O) G" U0 I) B' z3 f# x
    8 o0 `7 P5 O3 J9 c" }; n0 G@Component
    5 k. M6 s: T3 m! C  Wpublic class RedisManager {8 u7 ?3 t! l6 a( X. s4 s. p

    ! [" p0 H/ I2 ?; k; k# K+ n    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
    9 N; S5 K  p9 Q% V( ^- d8 i; f( ^( O0 p) W' z
        @Autowired+ _5 m8 X  ]# w
        private RedisTemplate redisTemplate;
    0 k4 U( ?2 a# `* K
    9 e2 `/ Y+ t$ ~% G    /**6 h# v5 E7 X0 S8 W% H# m% o
         * 设置对象, R5 c, |% k' b% I$ _
         *
    9 i+ u( T3 W0 L     * @param key key: b# b9 k: R% l3 B0 z! z0 n$ b
         * @param value value值
    " L9 S1 q  G5 J' O     * @param <T> 返回值泛型, {% `" ^. \+ C
         * @return 正确的值:<T> 错误的值:null
    % B+ D9 e6 h2 L4 z5 {     */
    3 ]: M5 L# w4 u, B4 Q    @SuppressWarnings("unchecked")4 s  e) C$ S! B$ u
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {. o7 p# i6 h$ f( `1 {5 ]6 z
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    ; [& w6 L. N+ [# V" H& u        operation.set(key, value);7 j3 c( m5 Q5 [5 {2 I8 ^
            return operation;# N/ Y! F, O: r9 y, l4 L: ?
        }
    $ F& O5 M. V7 W$ T# l2 J- R$ A. n' m$ \' I4 P
        /**
    % Z. Y) s7 u6 M- E; r     * 设置对象及失效时间 (单位:秒)
    . D- a; F3 r  s- i/ a3 |     *
    ' o/ f4 ]5 X, ]" M0 M     * @param key key/ ?$ o7 @7 S0 z. O; r# Q
         * @param value value值) C$ o5 L- j0 s! V4 J- ^8 J8 E
         * @param <T> 返回值泛型
    3 P+ G& `7 g/ K     * @param time 秒值' {4 c6 c0 h2 P
         * @return 正确的值:<T> 错误的值:null: W  a. H! Y3 u1 M$ b/ t. v, l0 R
         */0 A7 T" x) _8 `
        @SuppressWarnings("unchecked")3 R$ i; M  A( j$ _
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {2 s! q' V/ X# d4 [6 j
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    % m/ U/ z. M0 I% L+ i( G/ E* o7 h        operation.set(key, value, time, TimeUnit.SECONDS);
    - R  [/ ?! F& Z5 V& B        return operation;8 F( L% \* M" y# x! z% O, I
        }& W& \) n' Y% j
    ) S- ^4 p* O+ o$ ]' G
    / g+ r$ q" b, {7 F
        /**
    $ D8 k: ~: v4 m% r7 w8 s     * 设置对象及失效时间(单位:毫秒)( L3 {: T9 t# b7 t
         *
    ! }9 f. G, U8 P/ w$ Z! t     * @param key key
    + L% h/ j! B: O7 B' O, i1 P     * @param value value值1 Y9 T1 K, N1 C& P( N8 z& S
         * @param <T> 返回值泛型# U, B  p0 o. U2 o
         * @param time 秒值% R; n2 k, @' D4 k; f
         * @return 正确的值:<T> 错误的值:null/ }8 r7 @2 j+ g, ^& y9 [  l
         */# i: C, z# a' V4 L3 @( p2 T8 Q  t
        @SuppressWarnings("unchecked")+ f( J; N8 B' Y2 L
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {$ A8 H& T; Z" r; ?' _
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    . P2 I0 z6 S+ d- e2 u7 `- p  k! [        operation.set(key, value, time, TimeUnit.MILLISECONDS);
    1 [. ?3 _2 @; ^8 k* c8 h        return operation;6 d  V7 ~! ^/ @+ _% T$ q
        }( B2 ?0 u: v8 N; t% ?% O: G( E

    , i, G! q+ ?% Y7 ]$ @* G4 Q    /**
    ) J  f8 |3 K+ N- G2 T; i8 q     * 获取对象1 e. K* E" ]% Z' e9 \
         *' {. P" z# ?4 A! h) p6 X9 J
         * @param key 键
    % n9 h. E) B4 o. ~5 ]. `2 V4 j; m     * @return 正确的值:Object值对象<br>5 e2 K8 Z& G0 @
         * 错误的值:null
    4 h% i$ @/ e) Y* e8 p+ \9 ~& u     */
    * u2 Z# v4 ~0 ~" H2 M' D  ^    @SuppressWarnings("unchecked")" k1 n+ d8 Y; Z. |2 y& M
        public Object getObject(final String key) {; T; V! f# }; _% M
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    # M( c( F) W2 X0 I8 y        if (valueOperations == null || !redisTemplate.hasKey(key)) {
    " C6 O' I! ]6 d  `3 |# s  G            return null;! S4 S; x7 T( K1 C
            }, L. G& T7 ]. C+ x
            final Object object = valueOperations.get(key);
    / h5 N5 T+ P# O! j        return object;  i. O2 @+ V$ M3 b1 j) B
        }
    . j5 c8 j1 w" A; H" Y- b( g0 Z
    ( [- ?4 Q  v) t3 {    /**
    % b3 O$ Z+ m8 F7 Y" v# X     * 从缓存中获取string值) x. A" I# W- N# r: S0 P, Q
         *4 r1 Y: `1 P6 F( P6 _" E) y1 Y1 g- V
         * @param key
    0 M" D) H9 j# M' ?2 f9 `     * @return*/4 r9 N2 [8 w4 A8 A( L+ w$ g
        @SuppressWarnings("unchecked")
    ( W2 G; Z8 X& H# G    public String getString(final String key) {' `: B) W$ f. _0 b9 U8 u
            String value = "";# n% Y2 s2 }6 J& |! g2 u4 }
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();3 _) a* W/ h& I" V* [8 I
            if (valueOperations != null && redisTemplate.hasKey(key)) {9 p8 @+ j$ z: h( u1 q1 T
                final Object object = valueOperations.get(key);
    7 O, R7 c7 t9 f: b6 d* ^0 P8 @. o            if (null != object) {
    , v2 |& x3 @  G# x                LOGGER.info("--getString--object not empty");' V4 }+ h# W$ z8 m0 {+ ]6 j
                    value = object.toString();
    2 c: P* U: [( e, Q1 X            } else {1 ~' ?7 K- |6 |- u! O+ y
                    LOGGER.info("--getString--object empty");' X( I8 U1 s' h) R5 o
                }; f$ d& W0 P9 i9 K" p
            }! ^8 X$ d8 T" ]& p/ P! b
            return value;& s3 l- q2 [* q$ L
        }
    * e) }5 [2 b1 Z) P, U/ P3 F2.2:在redis中实现时间控制, a! Q9 S6 H9 ?( c, _$ o4 \
    . ]1 d' n, N( O" m7 u# U  R4 a
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    - g; A6 N2 u" q  C
    1 G: A: a6 j. B4 |import com.youjia.orders.redis.RedisManager;9 p- W; c+ @/ |( U# y' |$ x+ h
    import org.junit.Test;
    0 r% l: q, M  Simport org.springframework.beans.factory.annotation.Autowired;
    0 D3 F4 o% M% p5 T) S
    " \# i$ s2 W* _3 o& _import java.util.Objects;
    - s: a5 K* A1 F: B+ a, m4 y* _# w9 ~3 a$ X- x0 l$ _9 m" a6 X
    /**! S4 K4 Q5 T! \% N5 c7 o/ V& S; r
    * @Auther: Yrion
    2 L) P4 e5 w$ G. M) x * @Date: 2019-01-11 23:36. c; g8 t' `( m7 K, c
    */+ f  V7 S& ]9 J, i" o0 O

    6 k/ ?' E. V# P/ Mpublic class RedisTest extends OrderProviderApplicationTests {' n0 m5 F  H& R5 l
    8 i/ D, v  C( }
        @Autowired
    ( w( E, ^  n2 e$ c; l    private RedisManager redisManager;+ ^. R) S+ `/ P  W: t

    5 t% v1 a0 {* M2 ?    @Test
    7 r: @3 g( X' z, X( I& n    public void test() {
    , M( d6 Y* x9 N. {" {        controlTime("10000001", 10L);) k- h, p6 a  i; f6 N: x
        }
    % ^" a8 {7 i& i) M, Y% R3 i6 g% Z5 A& x/ P# ^! u
        public void controlTime(String requestId, Long timeOut) {
    4 c6 R9 w# m) e" w# H  `, L$ r2 m' T" C
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {* z3 [' d4 t5 J" K# L# A: @. T
                return;
    ! G  i) [# M0 j        }
    ! A& e0 L" f6 n* [9 f$ h/ U        //something code
    ; V; W: g# m0 K7 m8 ~        final String value = "value";
    & i3 ?  d7 k! D7 ]. i: T, K& c        redisManager.setObject(requestId, value, timeOut);
    5 V- b# S: l& [/ F' u        final long startTime = System.currentTimeMillis();
    , |+ J9 f8 |" M$ A7 p        System.out.println("开始控制时间");8 L! ?! q8 l' i
            //start
    # F! t  [- f$ v. e. u        for (; ; ) {
    * Q5 M. ?& z7 ?7 H, O; E( g" e" Y            if (Objects.isNull(redisManager.getObject(requestId))) {* Y! [$ a. V0 ^. N
                    break;
    $ n( T0 q1 u- B: Q6 Y# q            }
    9 \% y0 h( T8 \' d        }! j5 a* o: I4 X4 F
            final long endTime = System.currentTimeMillis();
    8 L$ z# Q! r% y8 a; w, C. X9 \2 F3 X7 o. V
            final long useTime = endTime - startTime;# I% I3 O* A3 {8 b; n3 b: T. v! s
    ! H/ t0 ~' ]) X2 S3 A
            System.out.println("一共耗费时间:" + useTime);
    3 E' _/ ]  V. T& G( Q4 T. K" u1 @    }+ |/ i% |; R1 A, w: A
    }
    , z6 Q5 t5 E3 ]5 \- ?/ Q. _: NoutPut:, r# C# s- c" _
    3 a3 L4 C% r+ F0 z" ]6 v+ I" d0 @
    开始控制时间5 _( e. n/ F: ^8 T1 V& d" ]
    一共耗费时间:10042
    & e  _2 v7 U- v; Z+ e9 x三:总结
    $ t& D7 d6 y) F0 ]) t9 x: c$ ~+ V( \& {9 k: b- }& Z
    本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!7 U' S- ?; C" q: Y" R* \4 n
    ————————————————
    " @8 E) G/ N1 s8 v" [版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    + J- `: w5 I, m& C* b$ \# K/ G9 n& [原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325
    - b( H  z; L& ~% \
    7 }1 o+ W  a1 j5 h5 y" \, z1 t* y, R6 g: j  z0 @
    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-10 01:34 , Processed in 0.288309 second(s), 57 queries .

    回顶部