QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1650|回复: 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
    0 V5 O0 a; i8 J* T$ G' B% o
    Java如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
    # F& H6 R" }$ o
    6 C6 D( c; D8 z4 x5 d6 H; D. _7 l一:时间控制的几种方案. u/ G7 G& [: H; n
    % @+ N  f6 |# U$ ?7 L
    1.1: 从线程方面解决
    + c9 f) b+ ], P6 |( v, G# T. y) t6 V6 z! l! t" X
    最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    % z3 |: F% A7 b, x3 k4 n$ O
    6 i, A1 \" v$ g+ R5 o) Z  [8 ]- Q1.2:使用Timer' ?% R  }4 H2 Y
    + _+ Z6 W- \/ W6 h& l2 q/ ~. I8 [/ E
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
    % g6 _% X* {1 ?. x  x
    / m4 g* c& X( N3 ^; Ypublic class TimmerTest {
    ! p' ^0 n: P! W) d  T, {6 v   /**% _- o( O- T3 q/ g, n9 `
         * 测试方法
    / X: M2 ^, h+ C# J) |7 g2 t     */
    7 ?  g: b' G) G/ ^& B, \" B* F    public void test() {
    ) C- P2 U; x( }/ J$ V- s' p' J4 o- E; S        Timer timer = new Timer();/ X/ @# \( |4 M: e; \% Z
            timer.schedule(new MyTask(), 800);" e0 w, v. G8 E
        }0 U1 ?! j4 _. F; Z

    4 R7 w1 I; H! ~6 ~( b    public class MyTask extends TimerTask {" |. X2 h$ M. r" z3 X- S; \% b
    9 W& t. ?4 X- t1 N* _& w
            /**# h/ p$ `- O% x( e  V* Q
             * 运行方法
    & U( h- f8 d4 n! |% r1 R, H         */
    ' O" B! X; Q2 b4 O- t3 ~- F& U        @Override& V* P2 ?/ L# C; Q% X5 y$ {+ x
            public void run() {
    ! k0 ?2 T0 q- L5 m4 L2 j            System.out.println("输出");
    , l3 B6 |( b6 p+ i5 ~* s* \* x        }
    9 O3 n8 ?" U/ t. n* b, V    }
    % N- e8 D9 L$ u" c) Z1 x}. C. t( C# Z' L3 p: `" \7 b
    这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
    1 C! W5 \0 Z7 s  r" Q  R. q) p3 I) S6 u3 J
    1.3:redis延时
    % C/ o- R& {: m3 ~
    - i- v  B2 Q4 S. r5 e' U* p在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:5 r$ @, h( i+ E2 x$ v4 i) @

    1 d( i# T" P6 M) G
    6 \! D2 U2 g: D3 U" n% {1 a; \8 I% X$ t
    通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:, c- @) F2 F: j/ r5 q4 h& |/ l( D

    6 |: e  j- ^1 m: x, D5 b1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制8 E& w* s- b4 Z, ^! y

    4 r0 q; P2 j8 O- r& E0 v2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现3 B* o$ y; q* n8 J* Z7 s
    4 W3 l; D+ q; Y" r0 j
    3:简单,真正的代码实现起来只有很少,下面会给出代码示范。1 m: g6 E5 a6 t3 g+ |; Y* I7 ^  e
    8 y6 `" f. ]' D. ~3 M& ?
    二:redis; v4 @5 |/ S1 C( }$ c' }
    ( S" R/ Y% @1 m( T! j3 p. I3 [# z# r
    2.1:maven中引入redis' s+ u- r  k" ~+ h8 X% v' w7 ^

    : X/ l/ |, X3 H3 F  E! R引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。4 t0 Z( Z; n$ h; ~  V9 N1 {" t; J
    ! h# [% j) g7 b. B- r, R
    <dependency>
    % M( K9 F) @  Q; I" c# J& d  <groupId>org.springframework.boot</groupId>
    ; d9 T% O9 q& K$ |  u    <artifactId>spring-boot-starter-data-redis</artifactId>
    7 ?* G1 C+ e* K# X      <exclusions>: B" U  ^0 a" W& d. ?1 v% Z& L
            <exclusion>
    8 {/ {# ^5 u: z+ G+ [; Y          <groupId>io.lettuce</groupId>
    : m) {) q) q" r, m- `6 g; R8 L8 y          <artifactId>lettuce-core</artifactId>3 t7 Z% d; T$ k
            </exclusion>/ _3 L/ y) `8 Q! k
          </exclusions>2 H$ Z( z+ Y( k# i
    </dependency>4 B, I1 F0 z4 ^0 F  {0 Y* Z1 [# j
    <dependency>, [! A4 o1 z2 i0 ?" Y
      <groupId>redis.clients</groupId>  K0 O$ K6 K3 f8 p5 h1 c
      <artifactId>jedis</artifactId>
    $ T$ f6 A$ q. i4 o9 E</dependency>
    , Q* [' X' m* c2 o+ j6 @: _6 {2.2: 在springboot中配置redis
    1 Y3 J+ q' Q! I% b2 c
    2 C3 r; S+ u% h" aimport org.springframework.beans.factory.annotation.Autowired;
    ! b) B( G1 F( O8 d/ k: eimport org.springframework.context.annotation.Bean;8 y( D. ?& s; g1 w
    import org.springframework.context.annotation.Configuration;% ?) i: @& y2 |2 Z! o  Z2 |
    import org.springframework.data.redis.core.RedisTemplate;" N, p, a! ~) Y6 F! w0 s
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;! ^  [7 v2 w2 l9 L. o
    import org.springframework.data.redis.serializer.StringRedisSerializer;
    ( G  z* D0 k8 v: w! a( Q
    , q2 g5 U* Z/ l' @@Configuration
    ; {' A$ I4 V+ ^, H5 Vpublic class RedisConfig {/ i9 m8 i6 S) _5 \+ q
    ' h6 G* A' S1 w' R
        @Autowired
    $ c) x% [. C2 L3 ?9 k    private RedisTemplate redisTemplate;
    4 P& o& h5 y+ }
    " f1 N! B' z2 x" A: ?    /**; b- u* r! F+ I9 W  \
         * redisTemplate实例化) n9 U: a, }+ |- ~  l  g; l8 X
         *  t; h5 z: x. `3 _2 ^
         * @return
    % @) J) }$ ~: d! a: u     */) m$ M. h* v4 Z( o- q
        @Bean
    2 H$ S) D$ R( d1 {    public RedisTemplate redisTemplateInit() {9 ^4 d0 K  R6 z- s4 Y6 Q6 m4 d
            //设置序列化Key的实例化对象
    7 I* {( L0 Y1 e' Y* i% N& g        redisTemplate.setKeySerializer(new StringRedisSerializer());
    1 M$ J2 v' _/ B. X0 v  M) f        //设置序列化Value的实例化对象
    1 N4 x7 n4 ?, _0 C# s        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    1 ~/ L3 F" y8 @        return redisTemplate;
    / N2 |. X' i9 l    }* P) G1 A, J: H

    * d' f' y  r1 w3 `" A6 [% R1 g}3 {7 X# Q4 E" `/ f' P; [
    2.2:redisTemplate模板工具类5 w( E$ `7 W4 l+ L+ \

    ; p7 e9 {; }- B+ E# B& @@Component* o/ |2 c' N4 r& S
    public class RedisManager {
    - l- h5 `2 H& e: V. ^" m3 d( e  H* _. Y4 a; d
        private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);1 e8 \7 ]0 I/ r. l/ L% L% D

    9 h" C& S* \% _/ m  ]+ Q    @Autowired2 X8 c% T) ]7 m1 O& N$ Q
        private RedisTemplate redisTemplate;' G0 w9 N( i- `4 K$ E/ ~* [8 P

    . r2 C7 Z: }# N* P9 m7 c2 u% L* R2 {    /**
      I1 V0 ?% B! ~4 {2 s2 ]/ b6 c     * 设置对象8 z' z+ O8 F! t& c5 Y
         *' h6 |0 H/ Y7 f0 U. z/ O
         * @param key key
    # ]8 l3 ]4 U8 `) ]$ q" x6 N     * @param value value值
    % }' E( \# R, i2 D1 d# \     * @param <T> 返回值泛型: T+ D. v/ O( `& ]" t! c
         * @return 正确的值:<T> 错误的值:null" m7 E$ v( Q3 T
         */
    $ ~' I+ ]/ Z' f- N    @SuppressWarnings("unchecked")* I8 I- K: N3 A; w& W
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    , J4 b( G+ A5 j# S5 b& T( ]        final ValueOperations<String, T> operation = redisTemplate.opsForValue();7 k9 S+ J- d5 p: x  N4 y
            operation.set(key, value);
    ( W* ?7 a. C; u, Q        return operation;$ {4 C4 T" I& j
        }
    * y0 X% w' o3 [. r
    / Z8 B; U6 A0 H. A    /**4 `* q7 O2 ?; l6 {& G
         * 设置对象及失效时间 (单位:秒)5 Y, D/ E; Y* C7 m* e
         *
    . l- @! f0 q6 l+ @     * @param key key
    + Y" }' q$ Y2 P' s9 `9 f+ `( ?4 |     * @param value value值
    : e9 O: M6 W  c     * @param <T> 返回值泛型, B+ m. f0 v5 ]
         * @param time 秒值: N* S; V0 g& R/ F1 X8 p7 U
         * @return 正确的值:<T> 错误的值:null
    & H7 e6 H! G0 Z7 z     */( i% H" F. [8 A4 H! F
        @SuppressWarnings("unchecked")6 U- s$ Z) I! j% k6 s3 i
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {( _! d5 D! o; ?4 y, w1 s7 z
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    + G- H/ A5 B: n6 |" Z. w/ f" q        operation.set(key, value, time, TimeUnit.SECONDS);. U& m) U; `. \& X9 E3 z3 V
            return operation;
    % H% }; ^! \* P& T    }( |0 K7 q' z4 n/ y1 w
    7 _, ?/ \( J9 \9 h* {1 a+ n

    / F; K, A; E+ W7 ]( {9 g    /**
    ; f! u% \/ d. W: j3 r0 Z     * 设置对象及失效时间(单位:毫秒)$ Y# |- Y7 Z' {4 v. n+ V
         *) o+ B  X. T' |4 T
         * @param key key
    ; U9 g0 g( X* I* a3 g1 K( q     * @param value value值
    - S3 w) g) A9 G% L2 u     * @param <T> 返回值泛型
    3 m5 o# F" P. ?+ O& |1 d     * @param time 秒值8 ]% Q6 S( J. s* O4 \- v
         * @return 正确的值:<T> 错误的值:null' v6 G# v6 [$ o! `. {2 ]& X7 l
         */
    ; M$ V! J9 _$ B& B% R. D9 C6 k1 ~    @SuppressWarnings("unchecked")
    " w2 i9 V9 q# M5 B+ [) H    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
    ; u  Z2 E6 \8 _# W) s/ U        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    : S" ]/ o$ M$ Z        operation.set(key, value, time, TimeUnit.MILLISECONDS);0 P# A8 ~+ j! J0 a; }6 v7 ~
            return operation;5 R: D4 P1 p. n4 ~" ^
        }
    & b( ~) @6 [3 _* K
    ; @5 [8 S9 K! J, [: ?5 Q    /**) d* m: I8 ]: v' H6 N5 l% S0 s
         * 获取对象
    4 a4 a/ E/ u: J" I/ T6 _9 U' d7 r     *5 q! _0 Z9 P  }5 [5 z1 }9 _
         * @param key 键
    2 {1 n3 M1 _* J8 ~) V4 I' C2 _, n     * @return 正确的值:Object值对象<br>
    4 j- n( b) X. A     * 错误的值:null1 @/ z* T! h2 E2 _! |5 h# M1 I
         */* B0 `6 }, ^' k' q
        @SuppressWarnings("unchecked")% Q1 f# V1 _8 O  t
        public Object getObject(final String key) {
    " M3 }: o- o: `: t+ M- N' d        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    0 s# g* E, W9 }  V6 d; h+ p        if (valueOperations == null || !redisTemplate.hasKey(key)) {) ~# T9 b5 |0 A+ |) L" H
                return null;- t' p9 p% h, a: J) F' f
            }: n- b# Q9 k3 s1 O0 r
            final Object object = valueOperations.get(key);
    1 q% M3 T1 M/ z# C/ u7 I& g8 u        return object;
    * P* D+ G9 @! z! z# k" P) k0 Z5 c# Z    }- n1 M( w7 e8 M% G+ Q

    - k) b. v1 W& D6 p* U  D# j    /**
    / S, ~2 V, V9 H/ R     * 从缓存中获取string值
    1 @, O9 c* H& c) \     *  `& z1 Y7 Y- w" Z, O0 x" F
         * @param key' U( a  b# U# {, O! G% r
         * @return*/
    1 s! L, N) S3 N: I5 u7 [    @SuppressWarnings("unchecked")8 K0 Z# k# S2 z
        public String getString(final String key) {
    - T7 U1 X# v; ?. ^0 h        String value = "";' O1 H1 A, ^: ^! a2 a; E
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();- ~" |( {  f5 P& M5 S" D) w2 Y
            if (valueOperations != null && redisTemplate.hasKey(key)) {2 M7 g4 y- F" ~3 V8 I; I
                final Object object = valueOperations.get(key);
    2 [. N# S+ R4 z1 }  z. B            if (null != object) {2 H: t) O4 [+ P+ S
                    LOGGER.info("--getString--object not empty");7 I" \$ T3 m# c7 G" x
                    value = object.toString();4 K& `7 m" c+ T. x' [0 V4 A' ?" o' P
                } else {% b2 e+ w$ Q- G
                    LOGGER.info("--getString--object empty");
    / y& J5 Y  Z: ]8 y' o            }0 a2 w3 C' U6 Z% s, r4 z
            }
    ! L( L, n: e. Y% U$ h) S        return value;4 I; [2 d  ~! z7 O" z+ q
        }: ]- q( U. P& z7 b% I  ]) ~
    2.2:在redis中实现时间控制4 x* F5 ?& `. L6 _; T  Z
    ! i: n" Z# n. |# O4 w# e) s$ d  {
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。2 y, {9 e6 L' G% a5 ]
    + d, v4 K, v9 y# D: E
    import com.youjia.orders.redis.RedisManager;) n) ~' ~- H3 j& i: P
    import org.junit.Test;# W, U. Z# I2 [$ p2 `" y. x( s. W- |# W
    import org.springframework.beans.factory.annotation.Autowired;
    + ]) H, q% ?/ ?3 J5 K  d. o8 d8 D8 h/ o7 m
    import java.util.Objects;
    , ?1 p9 U( x' t/ F9 ]. M7 Y4 L7 E) u( x* I2 B, h7 x
    /**
    5 Q4 C# ~7 z) n3 F7 v4 O * @Auther: Yrion. B/ ?. ~2 W2 l4 [* \- F- L: n
    * @Date: 2019-01-11 23:36
    ; s- Q9 s# H, c2 g3 U0 P6 C$ Z */, ?. e7 Q9 e. x1 G# t

      G1 L& a7 \( z* P! spublic class RedisTest extends OrderProviderApplicationTests {) d, L; m' j/ r2 \( T: }' N

    ) G7 W4 u; t* \  ^0 @    @Autowired9 \1 [1 R$ @5 T9 F
        private RedisManager redisManager;
    " \6 {/ M4 q- O! d" v: P
    + J% {/ F& Y$ h$ g. y9 A    @Test! R/ ]* t! u4 b# L) z/ n0 y
        public void test() {
    8 w! }  Y$ d6 p5 u$ P        controlTime("10000001", 10L);
    % B0 h9 Q4 A* Z/ W& f    }
    . \+ W# z2 [9 b" R; V- @+ A9 p/ ?; W! _* ]$ J- v4 \# e
        public void controlTime(String requestId, Long timeOut) {% i6 a8 b% o, ^# C
    ! z& G, U, q, N% h# V# u
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {6 J+ t+ Q: {+ N! n, _- m) l
                return;+ S& c$ }! [! I1 v
            }6 N9 Q0 ?! l" @
            //something code
    6 y1 E9 J0 I# f! @        final String value = "value";
    $ O0 `4 ]! L5 D6 L) P2 _+ z        redisManager.setObject(requestId, value, timeOut);) w6 ]+ x6 T4 e2 ~
            final long startTime = System.currentTimeMillis();
    ( Y& }* X& `& ^. ?/ }        System.out.println("开始控制时间");* e* L. [: g  L$ c3 p
            //start, R  @  l8 _/ f- \! j
            for (; ; ) {7 ^" M9 n6 G4 t  x
                if (Objects.isNull(redisManager.getObject(requestId))) {+ Q$ m* G3 j. t% o6 E  x5 }! c( [  g
                    break;$ m  X1 }2 b* Q8 z
                }2 h3 {" i" J, A7 Z
            }* ?) m: v* _1 F/ n- U8 h  \
            final long endTime = System.currentTimeMillis();
    # j$ z: ^# g! b* U2 W% j
    5 j. r# ^/ _$ U* y0 ?+ j, ]0 r: }        final long useTime = endTime - startTime;" C" S1 D; {9 L$ H9 x  n/ ]

    3 c; Z+ [% P6 p: ?% f  P        System.out.println("一共耗费时间:" + useTime);. J$ p  A# x7 O- V4 B3 U6 o' g- p
        }
    ' ?& W- K( P( w& a}
    * C  X( \& d2 M5 R7 ]outPut:
    $ B7 _) [; Y3 L8 e
    - J( U& O; p0 ~; N! V开始控制时间
    9 f# q- c) o- k5 p8 u) y1 h一共耗费时间:10042
      |" U( b8 [% W6 W3 \三:总结* v2 X; \, k- l2 d! @: W
    / e" F! Y5 J7 X' V) j8 @
    本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!0 I' S9 w% @! W' O: A
    ————————————————- w3 l9 m5 X, T- a& ]
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。+ R8 @# I0 w' J
    原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/1058933254 ^7 ^) F( F8 ]+ N

      Z" B& v! i4 G* y8 b
    ) U6 H+ I8 u& `
    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-8-5 20:32 , Processed in 0.566348 second(s), 56 queries .

    回顶部