QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1593|回复: 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 T" u" R( _5 j  p; D' g1 SJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
    8 t# B; M( U6 S) |
    2 {8 L( a0 w: n- Y) I$ ~+ Q! v一:时间控制的几种方案
    1 \% Y! w& \' T% V! h7 X, s! d" A) d+ [- N+ w; e5 Y5 ]
    1.1: 从线程方面解决
    & N6 K2 O/ j! o+ R' J* A4 W( W
    + ?% y# O4 M' a* L) N最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。$ [" {7 G. [; R7 i7 P, p$ {

    . X' o5 U9 H& m: R1.2:使用Timer9 ?8 U. x. L: D7 L
    3 j) c* j* }- I* `$ _2 K' t
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:- z" d7 M8 T: A! d) Q; f! }

    + w  U- x, f* w: G) gpublic class TimmerTest {5 E6 e$ F: r2 t/ O' I+ w' K
       /**2 ^4 S; G* p* p+ y/ N
         * 测试方法
    ) W* `* ^' i% Y5 Q     */
    ( A3 z2 T5 ], v) ]2 T6 j3 F8 ?    public void test() {
    2 d( P/ O& M7 A) Y* [" _  o        Timer timer = new Timer();# ^8 b; ^+ a4 ~
            timer.schedule(new MyTask(), 800);
    ( V/ U( m( l9 @, l    }: s: U* h, c/ P3 h- q

    ) @" c8 ~! _* V1 s    public class MyTask extends TimerTask {
    ( C0 w0 u' y6 ^- G; x9 Z  c/ y2 z. {% H  F% m3 G# [
            /*** _4 J/ n/ t  Y  E0 G
             * 运行方法
    : B/ k  ]' @" C  S: x  w         */7 M; K/ ^0 Y9 M
            @Override
    ! R! [- p, v4 I+ o9 f        public void run() {. T9 I6 r6 h* ^
                System.out.println("输出");
    ( y2 }- H$ A6 K- ?6 ?4 x3 n. z        }6 }) d4 |+ h  g/ d- e
        }5 {( }8 {8 g( m( t
    }
    3 \0 k1 g0 H  M这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
    ( e& T0 l, T' N' _' w$ x' F! |8 i  y1 l! W# ?* g) H2 E$ X# O1 u* I  H' T
    1.3:redis延时) `- s: D5 |4 A2 o  C8 M* y
    ( y7 F  }; ?3 C/ h. A3 B( N
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:  y# p" P" \- i4 @( @2 P$ _) t: y5 N

    3 X6 @2 k5 T* f( T: e0 E& R4 ^7 J9 G5 w+ ?3 Y; P3 g
    0 P5 G  P0 \+ d- ?
    通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:  x# E& u, b. S& H

    9 P0 E1 P5 d- i6 }: C/ P5 b$ [2 V; |/ m9 N1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制
    : G' Q4 X% a8 c/ O
    $ p6 h- Q+ P- _& N- y2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
    / h* ~$ L% P0 C6 z9 K7 F0 X$ I1 w% V: \
    3:简单,真正的代码实现起来只有很少,下面会给出代码示范。4 \  u/ c$ w& m$ O: Z: ^

    + t& s" l( U! O0 h! H, D二:redis7 k4 H' z0 |# h- W  Z+ c: x# a8 Y

    1 U8 T5 m! [) D2.1:maven中引入redis' n7 B5 r9 O6 @/ Q! S/ X% |

    / d1 d. F) O1 X$ }引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    $ H! [# f5 _- {9 ~( M  Y
    0 r) d. P  S( I, r) t9 g5 i<dependency>
    5 S! D* `1 @0 I) ~8 R  <groupId>org.springframework.boot</groupId>
    : g8 e( B: m. a; ]    <artifactId>spring-boot-starter-data-redis</artifactId>
    ! m7 m" I9 [2 x9 q9 \' X2 `      <exclusions>
    ! i5 b; J' f( W( A6 w  s. B+ W6 {        <exclusion>
    7 }4 f! I) B9 h          <groupId>io.lettuce</groupId>4 Z2 a4 J4 k8 U# p: r- |
              <artifactId>lettuce-core</artifactId>
    8 o! N8 s2 z+ p6 S6 ]. n! ]        </exclusion>) R; \" N& h# X/ n/ c8 E; b: B4 y0 q# n
          </exclusions>
    4 X& ]# r4 w; G) }0 d7 d2 t</dependency>. Z- ], S/ _0 t: h& S
    <dependency># e" I1 I6 a/ r: S. R
      <groupId>redis.clients</groupId>
    / M) @4 R4 ~, ]7 Y. p  <artifactId>jedis</artifactId>
    $ f, f  l3 s6 ~( X: z( n7 c2 T</dependency>1 g7 D& N$ Z  m; P2 f. ^2 n
    2.2: 在springboot中配置redis! n, B/ \8 z$ G4 X
    & }4 O/ v8 C# S: R+ n& E& m- t
    import org.springframework.beans.factory.annotation.Autowired;% G( i) ?% W1 }4 f
    import org.springframework.context.annotation.Bean;4 C0 o  T% c2 d2 C' i3 j
    import org.springframework.context.annotation.Configuration;
    $ J: z- w9 N" P, W9 Nimport org.springframework.data.redis.core.RedisTemplate;" l$ }0 n! @9 L# N
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;5 h1 j: |1 M: {+ G( D# T' F0 p
    import org.springframework.data.redis.serializer.StringRedisSerializer;& @, [: V$ z& l' d6 N, Q- G4 z) l

    1 D# p4 v4 c9 P2 K* ^@Configuration
    $ C- C/ `- a: U7 w9 S: Y2 s6 U% j% qpublic class RedisConfig {
    + `8 P6 r1 u% p4 R" S8 t# P) V9 z4 h- r! }, [6 |# P) |% B: A
        @Autowired
      J/ o6 P9 _4 `7 q' s' S    private RedisTemplate redisTemplate;
    # J% m& ~4 n1 F5 D
    8 T) i& T- l5 M2 \/ P, z0 Y    /**
    $ F1 d! R& h3 F3 C" _     * redisTemplate实例化
    ! U" U7 K, `% a; b* L! `1 S8 h     *
    9 a# c0 l% e; O. \. _. |1 f     * @return5 c3 a3 E% A% {$ v! z+ ^& q
         */
    : m. x% @, t* L    @Bean% V5 w/ d% Q4 {. d  X
        public RedisTemplate redisTemplateInit() {
    $ O/ O. d+ l" U( m        //设置序列化Key的实例化对象& t& \' ?0 N) w2 E
            redisTemplate.setKeySerializer(new StringRedisSerializer());% z5 H6 n# W' z5 G' K
            //设置序列化Value的实例化对象
    5 q2 T5 y  P) ?        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    / P& }: Y6 X8 T4 O        return redisTemplate;
    1 F6 a: C: |; j: C    }
    ' i, L2 ?7 Q7 P$ s0 ]1 Q
    9 X: v" U7 ?3 ~2 P& c; K$ R0 }+ R4 ^# [1 x}
    ( u/ V, }" B5 G; b) i  g4 N2.2:redisTemplate模板工具类6 Q9 d0 C% a+ e6 C
    8 q6 g! \3 u8 @
    @Component% J" z  l2 M8 \% n# G% S6 G
    public class RedisManager {
    8 }9 n* f& r0 {  u5 x) T
    6 G8 g8 I$ b6 M* |    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
    . x, Y6 l7 r2 C! X5 b7 q* k3 w. n' W+ r- r, s2 R/ {+ T
        @Autowired3 x0 H3 w, ^5 a" H" q. k8 u
        private RedisTemplate redisTemplate;
    0 L3 n/ i$ A) {+ r
    ( c! p5 S$ A# d1 x& ?. O    /**7 Y% a' y4 I% Z* {
         * 设置对象
    5 s6 a  C2 g. k0 Q0 O     *( t& W8 X# S+ }. ?. _; V
         * @param key key
    - w" N+ _3 ~' X6 O; ^     * @param value value值
    & `+ V* {* M$ Y     * @param <T> 返回值泛型
    % A' H# U2 n1 k0 r     * @return 正确的值:<T> 错误的值:null
    8 M6 r% M+ _$ B+ e+ P* \  ?     */
    ! u9 F$ ?% m- l    @SuppressWarnings("unchecked")2 \1 m0 |: y7 G- _1 z- s  Q
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {4 K4 I' C9 N. [! k
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    0 S$ |' J/ {) }" }+ J+ y' v5 W        operation.set(key, value);& q3 e) {  ]" k  O: O7 i
            return operation;  n" M/ B/ h3 p6 a$ D- f6 V/ {+ J+ w
        }/ w& P  D/ [7 I0 v0 U- c$ }) f

    ( V9 ^3 o2 l6 j9 ]; ?    /**/ f" K* O- g5 ~" h# Y; d  n, T! G+ c3 a
         * 设置对象及失效时间 (单位:秒): V9 M0 @- x6 A  h" v
         *
    . k) g  \/ e# S: ^, G: K     * @param key key
    9 t2 Y, Z* G3 Z) v     * @param value value值
    9 B. H2 t2 {( b, H. F, z# }4 `) E     * @param <T> 返回值泛型& W. b; _# A( ^
         * @param time 秒值
    6 S8 T+ U5 U8 S. d; D0 J2 b     * @return 正确的值:<T> 错误的值:null
    % y8 w1 k8 K- G5 @7 |, n     */
    2 f9 F9 a, A& k  F$ p    @SuppressWarnings("unchecked")
    & B2 n7 }6 _2 v, C    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {1 B: `2 V! w1 J7 |, U7 E
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    ; h1 {  k! Z; J2 ?) E4 [. E% E- h        operation.set(key, value, time, TimeUnit.SECONDS);
    / a5 ^7 K% M  L* K& t        return operation;
    # o8 G4 j; q) }    }7 f) }7 W: L9 ?* S/ o. O6 v
    4 _7 F  s" u  m9 t' X" ?
    / F* s: K9 z/ A2 ?- }& `
        /**
    $ Y1 B2 f) T& G2 S     * 设置对象及失效时间(单位:毫秒)' v  |# E$ `, s3 A- w, h6 C
         *  B2 z/ z) |. ~: \# v- V+ u, J
         * @param key key, M  `$ W7 T  f" T7 q
         * @param value value值1 D2 R" L7 `" m- i
         * @param <T> 返回值泛型
    8 P( g7 |  P: A     * @param time 秒值
    + d2 `* c! O" C4 e( Y% G     * @return 正确的值:<T> 错误的值:null
    8 W# y! k+ h2 l" u$ |     */
    $ V! r* k1 s$ `    @SuppressWarnings("unchecked")8 [/ b6 w* h# N7 T2 @2 i
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
    . B1 U" b/ K! a& W        final ValueOperations<String, T> operation = redisTemplate.opsForValue();: n: n3 |! T8 }/ g- r
            operation.set(key, value, time, TimeUnit.MILLISECONDS);
    ' z2 M8 x/ J+ |+ V% v" J        return operation;
    / w+ m& x) \6 B7 j' C+ l) z    }+ }3 w% w8 f* l. T8 E( D0 x- c
    0 s# R) K6 W$ S
        /**8 B, p8 I# C* h% D' P
         * 获取对象7 t; H  F+ y4 c  H+ q
         *
    ' b! \, Z+ e/ o% r) `, ~$ S     * @param key 键
    4 K9 {0 r& J. w' I: t* a     * @return 正确的值:Object值对象<br>
    ; X* m6 t% I# s! d- ^# V# C     * 错误的值:null6 p& b: F% k( w  V& k: f
         */
    - s- F6 _8 T4 ^  s4 G1 N9 y1 `: M+ J    @SuppressWarnings("unchecked")
    7 y& E  @1 a# ~) n; I2 X    public Object getObject(final String key) {$ O( a  ]0 p$ N5 }+ V; p8 V
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    , _2 f5 ]4 W4 K6 t" }+ c7 y        if (valueOperations == null || !redisTemplate.hasKey(key)) {$ S- V* i; `0 C( a! ^
                return null;
    * k5 h) e1 A3 M, s; j( m; N        }
    7 [5 s' ~% D8 l' f, K# Z        final Object object = valueOperations.get(key);6 n3 `- O+ w. N
            return object;
    - D1 q: g; F0 Q    }8 R: h. x7 a/ J8 A, c

    8 V2 L0 ~* E! ^& ?$ k+ q    /**
    1 _& X2 M! q( u     * 从缓存中获取string值6 A5 X; R4 c+ }( O: X' z; \
         *
    # _9 w9 D8 B& w; u     * @param key4 `# c7 K' l+ M0 i& H, l3 d  x
         * @return*/% }9 y1 x; Y% j. [8 t% i* R& ~4 E
        @SuppressWarnings("unchecked")0 A& M$ X: n  {1 w3 I# A7 R3 c
        public String getString(final String key) {
    5 {0 w$ w) W/ B        String value = "";% R9 U9 x2 a$ n$ E0 N
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();8 J. w* m& k- f4 N
            if (valueOperations != null && redisTemplate.hasKey(key)) {
    & L. \) ^$ E; ^6 o* l% ?& B            final Object object = valueOperations.get(key);
    & [. C+ m1 o- b. B) i; O( l% z3 l# T            if (null != object) {
      t& @7 ^% w8 p* U( E1 J& t$ K2 V                LOGGER.info("--getString--object not empty");% T6 Q- Y$ Z5 J! ]' g0 w2 l
                    value = object.toString();6 n" ]* Z  D( k( U2 [6 ^
                } else {  \) i9 Y7 N7 J% t# z; J
                    LOGGER.info("--getString--object empty");) A; T2 W0 W0 k+ C0 l
                }
    ! Q* i* \9 g3 r- |  {& d  [        }, _6 n8 t9 P8 Q' A
            return value;
    6 Y2 l- q, d/ Q& N( o& d    }* _% k/ W; z; d) m/ p
    2.2:在redis中实现时间控制
    8 Z4 ]) W+ S4 C2 A1 E  Q6 b3 w" `7 @
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。$ Z* ~' }8 V2 J

    ! p+ Y! E2 x( }- ^5 cimport com.youjia.orders.redis.RedisManager;$ _! _) Y- X) D4 b/ e/ Q- M! m- y8 ^- G
    import org.junit.Test;
    - w  x; i' D0 ?/ u" G/ t9 `- Pimport org.springframework.beans.factory.annotation.Autowired;
    ( ?% ^/ K& {# V' I/ \2 Q) O1 }8 ~0 v" x+ \; {
    import java.util.Objects;
    6 m% t  y) X1 D* W+ ~5 d( F8 H7 ?8 P0 T, u
    /**4 w' p! b1 C+ z( I- b3 u
    * @Auther: Yrion. E4 [' Y$ }. F: x5 ]
    * @Date: 2019-01-11 23:36
    . p& Q* _7 V' k5 ~( {. n */3 _/ l4 C  X8 Z& [" @

    2 F/ h8 {5 N9 ?, \% W+ q" |: k1 kpublic class RedisTest extends OrderProviderApplicationTests {+ A0 e$ A  h* B  o7 S5 ]0 h8 P

    5 h( M7 B; W) m# q6 t) U    @Autowired
    . ~  \' x  G6 Z% c  O    private RedisManager redisManager;2 s8 D) K  h6 |( [( e! p3 W. N
    ( E- R9 \0 E' i
        @Test
    4 h7 z' u4 t( i+ j4 H# q; Q    public void test() {9 D3 y. o$ w, r; X& V# ]- S
            controlTime("10000001", 10L);$ b9 P1 O8 Q4 r
        }- |& b8 n3 F1 W. q7 A
    4 ?* E! v, K1 X
        public void controlTime(String requestId, Long timeOut) {2 Z; e' b$ x8 @

      u0 o5 N% e* ]* S" B" x4 ^3 p        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {) X2 M. h2 m/ |
                return;
    8 ?1 B0 U; H  ?% x        }# u6 r5 ]- r4 w3 @! k- u6 R1 z
            //something code
    % \8 v7 g) j3 [7 Y! H        final String value = "value";2 t& N. f: W) j6 J! U) ?7 c; V
            redisManager.setObject(requestId, value, timeOut);$ l2 w, F; J  [0 U0 q4 q
            final long startTime = System.currentTimeMillis();* W3 D8 q' E- v! R' n& P
            System.out.println("开始控制时间");
    ' E0 k7 J* o  J, z. \, `6 _: e: }        //start
    3 m3 R: |" N+ x7 k        for (; ; ) {" o$ M7 G/ e9 I# }! Y$ x# [
                if (Objects.isNull(redisManager.getObject(requestId))) {
    1 g4 p  _+ Y5 r/ l                break;' h1 f! M$ Q7 B9 ]
                }+ h4 ^$ Q( R2 O# p# E$ q! b0 c0 v% ]* ?
            }
    0 O) ^4 E- a: g; N* v        final long endTime = System.currentTimeMillis();. [# s, e, P' m

    ; m' }9 }. B, t. j        final long useTime = endTime - startTime;
    1 s' k4 T/ b& q) R6 _" b
    ! U$ z+ q' x  _$ L        System.out.println("一共耗费时间:" + useTime);' G' k5 w# o4 H2 i
        }
    : e6 \' X% q7 o8 |7 R1 _4 Z, {}1 b' u" j" u# f6 q1 q2 G- D) X4 L
    outPut:6 A7 \& d/ k& F4 R* S
    - y; f8 I! h6 Z& u. `
    开始控制时间
    & z% k7 \6 ^; o  n# A/ U一共耗费时间:10042
    / @3 K! h8 F8 y' R; K; }1 s% l三:总结: x0 q- q9 {6 N- q) {$ x0 G
    & H4 U* B6 ?: k* |
    本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
    5 K' k( f' U, b1 C' `, X————————————————# g& K0 n% q, _/ ^( ]+ y4 E/ e
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    ; }# X# P+ v0 e, y原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/1058933258 z: a: J6 _$ W- T6 H
    + V7 d+ A2 }2 L% o4 [1 K9 d! c2 T

    / c. i" J8 J& U0 o3 k  U% C( C& r# T
    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 11:35 , Processed in 0.371056 second(s), 56 queries .

    回顶部