QQ登录

只需要一步,快速开始

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

    ' i- J. e) X5 l' pJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
    5 \9 X- x1 t+ x6 V% ?2 g
    ) F! E* l1 R5 m/ }, a6 [一:时间控制的几种方案& k+ O' R: w% e3 L: g
    , D& h) }( g! i' b0 N9 L
    1.1: 从线程方面解决* W3 E& z% @1 F. S- p  W
    ) z  ^! |1 @1 I2 g8 `+ l
    最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    + K2 |0 |, u. \7 F2 I% U% v  U" q& R
    1.2:使用Timer' d4 V8 C, H3 l/ O7 ?

    8 r$ |" J$ {2 e/ E3 Q查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
    1 O/ b: |, D7 j1 V6 C5 [" c+ i) w3 I: T6 i3 v5 l) U
    public class TimmerTest {' k# e3 y5 n! ~& }" @
       /**$ l" \3 l3 M9 k" o/ K8 Q
         * 测试方法
    0 W3 g. b' s1 W' k9 S     */
    & N* K4 V! ~& N    public void test() {
    ! d0 o6 V6 G0 u2 r) W' C, P- Z        Timer timer = new Timer();7 T# R  |' t) m1 p. ]) d2 |: p
            timer.schedule(new MyTask(), 800);
    4 I+ e, N4 p# o    }5 B$ Y; U' k& F* p9 _

    : ^1 g- r; P2 \: c- Y7 A1 Z    public class MyTask extends TimerTask {1 V/ n% x1 L+ \- C( g
    1 @' G9 b* v* ^- ~8 U! B
            /**
    2 a: N5 ]4 T( _1 W         * 运行方法
    + {2 k; }3 e, ^9 k4 H! N( c         */- m; P* _5 \( ^
            @Override" ^5 z4 A9 p- H5 W
            public void run() {
    , _, F: V  f9 g2 c            System.out.println("输出");6 B3 T6 j% x4 {( L/ `# I( Y1 L
            }0 u- B- A. Z. n; \, u
        }8 @! ]9 L. V+ k4 U# ?- g( h' q8 \
    }0 b* l+ C, c1 H" d4 A
    这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
    % h+ A/ o/ @; W: g( W, W$ X0 @" S6 \
    1 F* E3 O0 N, d! O, W2 k# O1.3:redis延时
    " P) l* w  w: g/ r+ B( [# F' u4 W# i$ k9 \6 j  N
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:' Z% A# t2 f* S' s

    7 O' q+ e% y) q; z
    & {( ^0 }2 |7 F( v9 Q1 n2 J7 G, P3 N! b& N: ~; n* c, t' L
    通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    6 X. T' m) z. i+ o/ M- i3 T4 C5 x" T1 O) T  [0 ~
    1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制& x9 j/ H0 E2 C. l4 ~  @1 @* `1 |

    : E7 x& f0 v- j2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现9 G9 n6 r6 H* `& K: O+ m( b

    / v9 _( s/ j/ P; E# t3:简单,真正的代码实现起来只有很少,下面会给出代码示范。8 _8 j, m" W8 I7 \7 D  c
    $ _; B$ c& n. ^) D. j
    二:redis
    : ^* T$ G7 B, y4 b7 u2 z) T3 [4 y- p/ B% u& E) \/ P/ s
    2.1:maven中引入redis% x' h% E8 _5 N+ ~) K$ j2 y

    # d* w: N& y, S& S4 M) F. V# H引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    # d2 U6 D5 q0 V/ }; N4 F/ _
    % b5 F  J: g9 N  g( y<dependency>
    ; q2 v# y, s9 R2 j3 V8 I  <groupId>org.springframework.boot</groupId>3 [8 C6 r% f8 T. X, ?' j# X
        <artifactId>spring-boot-starter-data-redis</artifactId>
    3 x! v. K! q6 _; L/ ^( j      <exclusions>$ Y( @  \# h) q
            <exclusion>: x+ L- B( C% y
              <groupId>io.lettuce</groupId>1 p7 \( H/ w. L. ]; r' d9 F
              <artifactId>lettuce-core</artifactId>
    / d7 ]! ?  I8 i+ r  R; Q: d        </exclusion>
    2 z  |0 H. C$ x6 x  J5 ^4 G      </exclusions>
    , h6 x# {/ ], K+ f' }</dependency>
    & e' @# e' U; L1 H5 n<dependency>
    , G& S2 s3 r/ u  Z+ M4 h  <groupId>redis.clients</groupId>* Q$ F8 ]9 J4 O
      <artifactId>jedis</artifactId>
    1 G7 x0 F3 g3 I0 \. U: X# J2 i</dependency>$ U7 k. g. f# a0 q2 m4 o# k/ u
    2.2: 在springboot中配置redis
    , V2 W1 `) k- A9 A* J# v+ t
    : T( m5 X4 R- E! wimport org.springframework.beans.factory.annotation.Autowired;
    $ e+ g& E6 z4 _9 B& simport org.springframework.context.annotation.Bean;0 C  W  L; B# y' [( ~" f! q
    import org.springframework.context.annotation.Configuration;5 T8 ^( _# d9 t0 @: z" C
    import org.springframework.data.redis.core.RedisTemplate;
    . A1 Z9 F7 o; @9 A* p' u! }" @import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    - V* M( w* ]( b; cimport org.springframework.data.redis.serializer.StringRedisSerializer;+ q5 N  o+ w! s3 y
    5 j9 k3 x7 E5 a8 ~6 q& ]! S( t$ |
    @Configuration* S& a1 F' M. V6 T$ t: \& r' ]
    public class RedisConfig {
    & s5 }# y! J3 Z8 ?. S
    4 O6 ~9 D& K* p3 ]: t6 C- `9 M    @Autowired
    2 d% K' X* ]+ v. f" R( D$ B    private RedisTemplate redisTemplate;# r0 m  Y4 Q, Z9 B7 J

    7 l3 i$ s# H  n" J8 ?    /**
    % ]0 @* Y& `& R, a     * redisTemplate实例化9 E8 z2 w; E6 R- ~; i; G/ }! W
         *
    " H1 o/ j! d7 I0 X- {0 |- v8 ]     * @return
    ! t9 c- m& [7 w, k     */
    " l  D& q2 F9 N" o' Y    @Bean% _8 d" p( ]7 e+ Z% p- t
        public RedisTemplate redisTemplateInit() {
    ) k0 |# k; c* Q: S7 A/ m- P  G" F        //设置序列化Key的实例化对象
      o1 m/ `. G- g' \( T3 Q; _* v1 S        redisTemplate.setKeySerializer(new StringRedisSerializer());( N/ S6 Q. D- \! B% d
            //设置序列化Value的实例化对象. A- w6 B. q5 F7 a: h; Z
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());& b; Y! L( R0 _2 r6 E
            return redisTemplate;* j# x/ X7 \9 d5 ?  g
        }) d; z9 e/ O. S1 S/ i- {$ m% ]

    % ^( h- ]% u' i* k; t' a3 d4 T1 g}$ H( b# @+ j# b0 p7 f+ q$ S
    2.2:redisTemplate模板工具类3 {" J$ }5 i) }6 @8 b% ~; o
    7 t, X4 x" _. p. S1 D3 a5 g* E
    @Component3 D9 I1 Z& N; S8 W
    public class RedisManager {( j) l% D8 }; u- r% A7 O, ^6 a  K$ N
    7 h- Z8 d/ d4 ]3 ~
        private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);( h& P8 E) d+ o' V& l2 _2 c" N$ S7 j
    % R. y, W5 _0 ^1 D
        @Autowired! r" T+ y9 }9 w% P3 v; o
        private RedisTemplate redisTemplate;" m) r0 m3 Z$ o2 L( P9 y. m
    % X) q$ X0 k. r# T7 a. x) |
        /**( _" a# X7 W7 Y7 ]3 p1 E
         * 设置对象
    ) O7 w9 \4 K6 ]/ a" S3 w     *2 ~' {& Y/ G' n* h8 H/ V
         * @param key key2 m% {  N2 c/ V9 d* B  Z
         * @param value value值
    ! u5 I: o9 A4 t# @# e     * @param <T> 返回值泛型
    / b$ Y  u: s4 m4 k7 a, a9 X     * @return 正确的值:<T> 错误的值:null
    4 H! ~5 H- p: _  ]     */
    / ^# y* n4 h9 z: K  W2 i0 k    @SuppressWarnings("unchecked")
    ' ]4 R5 h$ w3 m9 R    public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    $ I! G( ?' g3 P7 j4 l) j( z7 i0 W1 P6 Y2 j# Z        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    3 m9 j2 d7 Q$ T; ]  k        operation.set(key, value);
    * T1 d1 P: r/ k/ C5 o        return operation;4 j) U- @- ~$ g  y2 c& N( C
        }0 e' k5 E, q. ?0 u0 c! X5 X$ U

    7 x3 s# n' I' v8 y# i) U' A    /**
    9 |. a2 u9 P% ?, Y/ T, H     * 设置对象及失效时间 (单位:秒)& s+ x! f0 q. M3 X
         *
    , ]9 u" K: ~* n! p- `8 ~     * @param key key
    : E& Q4 N+ |& c8 |8 o. f+ k     * @param value value值
    " M( x% @. q" T3 L) }- r5 `% a     * @param <T> 返回值泛型
    - `8 [8 u9 M9 [  \6 U     * @param time 秒值$ V$ Y3 M- Q. g% D! j" D4 R! m5 t- A
         * @return 正确的值:<T> 错误的值:null
    ! y; B- I# Z, B; i- K, g     */
    0 s3 x" G8 k2 ]8 m) q    @SuppressWarnings("unchecked")" p5 C* P# f+ x/ l6 ]) |9 ~1 K
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {3 H; I5 I8 a: v& G" C1 e9 T7 N% ~9 c
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();0 D! v" J5 G* M" d# d
            operation.set(key, value, time, TimeUnit.SECONDS);, t8 j5 M  H& j3 v3 s: `
            return operation;
    1 b% M* Q& V/ Q. R" P    }& B; Y1 p; _. j7 _$ R% C' H9 C+ k5 C7 j" o

    ) U8 t4 A* d0 k  A* L# L
    + J0 G; `, M. h5 `& _    /**
    + ?/ ^0 l! A1 ?: v/ ?4 H* f     * 设置对象及失效时间(单位:毫秒)
    7 I- r# v8 b) c* l% ~7 x$ v     *( D! l: S6 X# R& g- y
         * @param key key
    ' y& Z0 J1 @* {/ C9 J1 F$ d5 g  r+ p     * @param value value值
    . ^5 o" y- \. _6 L1 {     * @param <T> 返回值泛型, M1 l8 \+ w6 N+ \6 s
         * @param time 秒值
    $ R. ~8 k$ s+ q1 i$ I# m4 Y8 k     * @return 正确的值:<T> 错误的值:null8 U% _: X5 s9 j* `. C3 ~
         */; g2 g3 G0 S9 K8 Y" H. k
        @SuppressWarnings("unchecked")
    : _8 ]* r& ]1 f# }8 q: [" Z    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {( n6 q4 S. j6 r* ]& {# v: s+ C) i
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    % _% C$ J# D8 Z) Q8 J( D8 [  `        operation.set(key, value, time, TimeUnit.MILLISECONDS);. _5 Y2 O8 R" I3 R6 i
            return operation;3 H/ I/ b" z2 D4 Y- D5 @5 J
        }
    : G0 X7 N; O; C- b
    0 w0 W3 ?) |4 U+ t    /**0 |. U" w- }8 V6 @) q! U
         * 获取对象
    : G' m4 X2 A/ U* }$ J5 K6 n     *
    " w- [2 }# w/ Q3 ~$ @. X     * @param key 键2 c; v; L0 A- M7 N7 B+ c% f
         * @return 正确的值:Object值对象<br>! Y3 i5 `; A2 J. v) J9 C
         * 错误的值:null* k# c7 q) |' X7 t
         */
    9 g) R1 x0 b7 _- T' ]    @SuppressWarnings("unchecked")- |( w3 w; t+ @5 T$ u1 T
        public Object getObject(final String key) {
    - U. r' }8 R; y# x/ n% s6 o' P        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    . H+ d3 k, w4 B7 _- `        if (valueOperations == null || !redisTemplate.hasKey(key)) {' F6 C) f& g; ]- J4 u
                return null;
    % O, K1 n9 o' k        }' @: s4 V( M( G$ a  H4 r" c
            final Object object = valueOperations.get(key);4 c9 Y0 q+ c! ]! j1 t
            return object;
    # q8 n: `' ]2 \+ O$ T    }" ^3 Y5 \$ l! r  E0 F3 a+ j8 u- `, u
    ) P# t* o+ P" O
        /**
    : A3 x5 T% v! l2 j4 I     * 从缓存中获取string值% E0 M0 J6 t6 X0 l  F
         *9 Y3 X6 ]0 f, a
         * @param key* L: W1 n4 |  y! |% c3 u
         * @return*/
    # c1 u% `0 ^/ f1 O9 }$ d    @SuppressWarnings("unchecked")
    ) D5 X( G. N4 T5 ~/ y    public String getString(final String key) {
    ) K" W4 C0 o0 g3 C& U; D  Q        String value = "";
    $ I! L) u2 j, o  ^: c$ `- k        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();5 u7 n$ p. P* K0 c8 p, f5 f
            if (valueOperations != null && redisTemplate.hasKey(key)) {7 k1 n+ i+ [2 T" |: P# A# J1 w
                final Object object = valueOperations.get(key);1 Y) p& Q) i* z" p* K' Q. |' P
                if (null != object) {3 Q4 c* k. n5 d  V
                    LOGGER.info("--getString--object not empty");% A: ^* |" D7 h* O8 z- K7 ^
                    value = object.toString();
    7 ?) @7 b2 y. Y! d% S) a9 p            } else {8 x; q7 q3 w; R
                    LOGGER.info("--getString--object empty");& R* K7 z/ s2 h" F' a( U  h
                }
    ! J6 q/ c9 n6 K( W) i. z( d; f* N. f; E        }( R/ G3 j; T0 A7 B- H- ^
            return value;# _) ?/ C  N- b9 m6 Q, I
        }1 A$ |. f. F4 q. X. L
    2.2:在redis中实现时间控制+ @; m, [9 x2 h% n3 `: |
    0 b8 ?+ e, B4 s/ `  n6 o5 v
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。% H# c! u3 c, O2 X# e+ H) K
    , A! `8 l: K# s# B/ p" ~! \
    import com.youjia.orders.redis.RedisManager;
    8 k3 Y% y+ f( \, Rimport org.junit.Test;- h  N3 f: f/ K6 I; _  l2 W. J
    import org.springframework.beans.factory.annotation.Autowired;# @+ B; ~9 m9 Z& O. h1 W. k
    ! m6 P# U# m2 [6 b( I/ Y
    import java.util.Objects;
    1 N9 D9 ?" n+ F+ q% a
    8 j$ w9 }3 v: ^2 U( s/**6 O! E) M9 K6 K, w% u/ q6 d
    * @Auther: Yrion- q/ D7 y; H. S
    * @Date: 2019-01-11 23:36
    / N. C' U4 `, K% T */
    8 E5 s  \5 i1 A/ k9 I# p8 K$ W8 v8 l
    9 y8 u8 ^( o4 ]1 Kpublic class RedisTest extends OrderProviderApplicationTests {8 D8 v) H( j1 ~. ]+ w3 L+ O

    ) s8 K1 O2 s) t6 J& u2 W    @Autowired# d* y+ y( N! b; w$ y' M1 ?
        private RedisManager redisManager;
    1 t+ @# r/ Y6 y6 F* t( Q4 {) ?1 \1 w0 B: h$ T
        @Test
    1 r9 d  ]9 X  J5 \0 C5 h. o    public void test() {  z9 D' }: `4 O$ g* o- z; u4 b
            controlTime("10000001", 10L);
    ( \' C' u5 D* I5 X; C! ~3 G  ?# V6 X    }" G, J$ i% [+ W

    / D* n5 D2 F; i5 ^2 n    public void controlTime(String requestId, Long timeOut) {
    ' r* H/ s  B9 |" v8 B/ _
    2 m1 K4 I0 n3 x2 C        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {
    2 Z7 |2 B& x8 U/ E8 K! b            return;
    ) d( s4 _, y+ p* x7 o        }
    . n$ Q) N1 p1 Q, d6 W2 y        //something code
    5 _# f- Y- A% S( u+ p        final String value = "value";
    / b( o" s# M) w- `" M        redisManager.setObject(requestId, value, timeOut);
    8 _) l% ?9 Q6 Y" k; _. d        final long startTime = System.currentTimeMillis();
    8 N( a, ^: P) i! B        System.out.println("开始控制时间");
    0 o/ o( B6 R7 `* w1 Z- {        //start3 J8 ~" i7 [/ u
            for (; ; ) {
    $ R$ E1 p% f& Z3 ?            if (Objects.isNull(redisManager.getObject(requestId))) {
    4 W2 [) A5 Q' w8 Z( y  B3 A; m                break;! y3 @- `3 v- t
                }/ l* ~: j; R3 f+ o& c# |) ]2 ]* n: S
            }+ W9 F  ]* w9 K0 `8 u. G  e
            final long endTime = System.currentTimeMillis();: q2 P8 F4 A! Y9 I, X/ e

    1 q; A- ^" A4 _6 W        final long useTime = endTime - startTime;
    & v3 {3 I9 f3 T$ m: F+ B$ J* I) d% t
            System.out.println("一共耗费时间:" + useTime);
    ! I1 [/ v  n; N( T+ s0 ^+ r    }) U+ L' p2 I! @: ]+ d. G7 \
    }9 W8 J. H: o. v" X1 X& h
    outPut:
    6 _7 j0 }* p3 F8 `9 d7 H: R8 y) B% b6 G0 l
    开始控制时间
    % T  A6 t" A0 ?, t一共耗费时间:100421 G; i9 Q8 c. a. k/ n4 s! `
    三:总结( I. s% Y+ F% u& W# k

    ( |% g8 ~+ s' j$ r) s0 i" q% g本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!$ T$ x: ~* C6 N0 T: z7 `8 ]0 m
    ————————————————1 }# q2 C0 u4 P2 E; r: {! J) T" T
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    9 ^2 e- I6 K, b- M. M) l9 C原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/1058933253 V* }( s. E' |3 C$ m9 h/ T3 L
    8 p4 y" N, X* w( G/ }5 `
    9 n7 y% \2 z- M; |7 |7 @
    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-7-25 03:42 , Processed in 2.522764 second(s), 57 queries .

    回顶部