QQ登录

只需要一步,快速开始

 注册地址  找回密码
查看: 1659|回复: 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
    $ R2 |- Z& V) r% \, r4 D- c, l
    Java如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。% c$ w! M& P& S/ D' p5 y* i7 y

    9 d5 Z% d, x% l- v4 N8 g' D一:时间控制的几种方案
    7 f8 u6 P' N( P6 H' U( n3 }8 q  x2 U3 b: z
    1.1: 从线程方面解决7 V3 w" [9 `: y9 j* U

    6 Z9 E- r0 a8 m" Y% n9 n最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    * u; g$ _$ x7 d- i2 j3 P+ I1 C5 U& `/ N% X5 c6 C; y
    1.2:使用Timer
    ' G9 s; F' j) s
    & B0 e) ?: K; i查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:" B9 ^! c( r0 {4 p

    " f: c6 n  A3 A8 w% Ipublic class TimmerTest {4 U$ ~4 P' Y4 w& N' `! P: p8 c
       /**0 y. b- B. F2 {/ o  F
         * 测试方法
    ; A& U5 y/ a9 m$ G6 [     */
    0 `! X6 ^9 o2 h( N! p    public void test() {7 e: V2 q7 u7 `& C- R( X
            Timer timer = new Timer();+ c( |' O" S4 b: x# w% \
            timer.schedule(new MyTask(), 800);
    # D+ c* Q+ y8 `6 c    }8 K* I# x! ~- [. M/ R  o

    ' r( y: G0 R9 n, Q! P5 g    public class MyTask extends TimerTask {: P1 P% Z6 u8 ^. O8 P8 P
    * W4 \: F* ]+ v9 a: ^4 Y0 h6 B9 K) z
            /**1 g0 [1 q' }0 l, y9 o) X3 S7 a( C
             * 运行方法
    5 w1 |. c. F6 }* \         */5 B' F+ i6 Z  k) E( |9 _7 P/ u9 N
            @Override
    $ f3 Y2 L2 ~( m        public void run() {
    . A8 \; \3 C7 V1 x+ I  O. j* h            System.out.println("输出");
    0 Z( s- ~; p! b8 h! W! U0 j/ ~) I        }
    + \* W8 x0 ~4 V) h    }
    ( u9 l& X: r* y; M( m  c}
    # E2 }0 c: }+ _1 `' n: h# ]& e7 I6 G这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。; o% D: _1 P0 A/ t4 W8 }! p- c- S
    - u1 S+ i1 {+ T$ g3 d
    1.3:redis延时
    7 d4 l% d, i# ~# n
    $ y1 s" k4 H/ x: s在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:2 ]. h: _" x. D4 Q! m1 ]( N0 w0 c' I
    % q% Y% g! Y, n  X7 L/ M) \7 ]. F( B
    0 D0 S% x+ {( g- P5 ^6 D) D

    & o6 y" m, ~8 y, r; F; @通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    : d' H8 S7 A' V# e2 q
    1 r) z5 L) a1 y3 d, y( w3 d- b& V% t1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制0 p1 X0 H+ r, y8 E1 I& F" v
    . N9 m" `! S0 V$ c: ?/ A! F; r
    2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现4 [4 P2 i. n8 ~4 c3 E

    4 q# Z6 _) ^) O" s7 S8 c7 R" z3:简单,真正的代码实现起来只有很少,下面会给出代码示范。( Z' N9 q7 w5 X$ h/ d# M( f
    ! |: o8 @  i! T, `# f# H: ?  V! R
    二:redis# y9 Q8 R" W9 f0 B* \0 y
    " B& p: t% n9 `! y. z6 w
    2.1:maven中引入redis
    ' b) r4 v- u# ^: U# {* c/ U' z1 ~# ~
    引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
      ^! b* e% [& i% q3 c4 |% M' |( q2 W+ W& F. J) q4 f1 ]" q
    <dependency>% Z: [( O1 h, v& {, `! B  e1 ?- W
      <groupId>org.springframework.boot</groupId>9 O! ^& s4 W3 r5 h8 t4 f- o) w$ \7 {
        <artifactId>spring-boot-starter-data-redis</artifactId>2 b5 z3 }/ T9 o* Q# _
          <exclusions>! b, X+ D' |8 m- A. ~
            <exclusion>
    # A8 T/ n- _6 R0 q- P          <groupId>io.lettuce</groupId>
    5 X% ]( o. s( ~          <artifactId>lettuce-core</artifactId>
    ) q' ]* ^% H, I, \9 s( O: {        </exclusion>) {; S9 a$ e' G. |1 Q" H* Q
          </exclusions>" w( ]# e: H/ P; ~, F
    </dependency>$ Z9 z0 |2 Z& K% p2 k7 L
    <dependency>2 w6 R1 H2 m( a* r, F4 q9 H
      <groupId>redis.clients</groupId>( H0 W- r0 O; ]& `, E' y: h
      <artifactId>jedis</artifactId>
    - N8 Z; t- F/ _0 y) H</dependency>
    1 x0 l! U1 [* _. H, W* _, c2 i* Z2.2: 在springboot中配置redis% n$ W& f! c% @* |  o

    - x3 Y1 G. `4 r. X& @& himport org.springframework.beans.factory.annotation.Autowired;0 |$ l& m" l8 ~5 }% D& Y: O
    import org.springframework.context.annotation.Bean;
    / j1 a* o: l/ z3 K, Vimport org.springframework.context.annotation.Configuration;7 A9 c* C& C, y2 \3 l* A
    import org.springframework.data.redis.core.RedisTemplate;. z* I$ o: D( P6 D! }
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    / P5 j, G* {8 t* ]: Q1 ^import org.springframework.data.redis.serializer.StringRedisSerializer;
    & y6 U/ s. V; r: r0 F# G, v
    , H6 T; `. G% P; b' J5 J@Configuration$ O, U8 y) |' I1 y3 j
    public class RedisConfig {
    + q# u- H7 f7 b( m7 _; j
    . q/ {; ^: F/ ]8 Q" O    @Autowired2 H- {. I4 g& p- }
        private RedisTemplate redisTemplate;
    % w. U, k7 `+ n. o" z6 X7 K0 u- [4 B1 j2 k8 m$ R/ _
        /**
    & G6 [7 u! R8 u) ]6 Z     * redisTemplate实例化
    ( S5 e" y) z. R) Y. H. m- H     *2 ]) E  p: R! E7 p# {, l
         * @return! b- N+ h6 D1 x4 s3 ?" i
         */# N  p' o" J! P; V
        @Bean9 R8 G7 H7 t7 U% ^+ V+ l2 _0 u
        public RedisTemplate redisTemplateInit() {; P& P( D8 m+ C" t  T
            //设置序列化Key的实例化对象7 [) R. {0 z# k. R, c! c! B
            redisTemplate.setKeySerializer(new StringRedisSerializer());
    6 x1 F, `2 N0 |5 j# W& x6 ]) S        //设置序列化Value的实例化对象
    % m( K8 k+ w- e        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());: n" H* i5 J4 o, _
            return redisTemplate;
    : D$ e9 h) R$ \" g" @3 b" b4 F6 |5 D6 X    }  `8 v. Y/ y( ^+ }9 G, F! Y

    3 v, V; p, O- K1 a}. ^+ `# f9 C8 C* a% }8 O
    2.2:redisTemplate模板工具类
    0 z4 e; D, d+ P3 R  K; o
    4 b2 |/ a9 K8 V@Component9 }, ~% _* l6 S1 b- x( @
    public class RedisManager {
    # w' [! O/ B: O- B# d4 f' Z3 Z  q/ U! R6 N- t6 `
        private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);% s" s) B! M# W4 X6 _8 U$ s0 Q
      h6 l9 O4 @' n
        @Autowired. g( i7 o+ D# J$ A) g
        private RedisTemplate redisTemplate;& X" X/ X5 u+ G0 G1 O( ^

    . ~: _* A* k: f    /**
    ! E$ ~: l' Z4 D2 Z3 |     * 设置对象
    ) T5 H/ E1 E' q: }3 _     *: |0 C+ `6 |) ~/ E; @, W
         * @param key key- V8 U5 G$ ~3 U7 _: h  j
         * @param value value值
    $ W8 Q1 D# H& Y, I- K     * @param <T> 返回值泛型% `2 ?0 d! A, x7 w& f
         * @return 正确的值:<T> 错误的值:null8 v9 p+ g: j2 f
         */
    - O  e) i4 D) C. I4 R+ a2 [5 g! U" p( I    @SuppressWarnings("unchecked"). u" p. q' z! @: J% f; n
        public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    * y1 B" O1 T+ T+ B4 S$ S        final ValueOperations<String, T> operation = redisTemplate.opsForValue();, b  P% w/ F* h7 {7 \
            operation.set(key, value);
    + H$ p/ G/ z  L1 k, F! b& Q2 y        return operation;+ B* c( w" d2 B9 {/ Q. ~
        }
    ' b: Z% o+ {& j
    ) J% k; ^! U8 ~# E6 \" d% }: z( Z    /**
    / R9 R' h1 I; K: n  h1 i2 I     * 设置对象及失效时间 (单位:秒)' D8 t; r. r' t- N; a$ [# D6 T3 N
         *
    ' |) b8 x% G8 q% J     * @param key key
    ) R4 o+ j4 D# y( `/ z1 S) {     * @param value value值5 [7 q2 u9 Y8 j' j2 V# c8 U
         * @param <T> 返回值泛型
    5 ~& h- X1 @- G: q: x+ S+ F     * @param time 秒值
    ' S2 g* W% f5 t8 y6 T( P7 U4 e" m3 b     * @return 正确的值:<T> 错误的值:null2 [. x/ P& ?6 e7 l: H; a4 S2 S
         */- V4 E4 n9 h  y- B( ]% _
        @SuppressWarnings("unchecked")# C1 {7 {' z# P# @$ P! D% I
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {
    2 {* A8 d8 |" D. f1 s1 q        final ValueOperations<String, T> operation = redisTemplate.opsForValue();3 H' ]% L  M" S/ ^& M3 I; b
            operation.set(key, value, time, TimeUnit.SECONDS);) w  U' b! ]& Y( G( w8 y0 ^2 |4 C
            return operation;
    $ E: H7 z. [* z% p2 t/ S    }
    7 z0 M% O0 m, c, q  }* `( |
    % q0 ~" h' N' U% X' D# b( |) `: G2 v$ X9 t3 S
        /**
    ) o* U2 v- N$ Q1 P- f+ v     * 设置对象及失效时间(单位:毫秒)
    , b6 k% W; d3 Z5 W- I     *
    % k8 @* v4 y2 u* z# [8 t     * @param key key+ g, D( R7 g2 \# e5 u
         * @param value value值; T4 `* z) W5 X0 M3 a9 s+ q
         * @param <T> 返回值泛型" `' A$ ]3 a% Z/ K
         * @param time 秒值' h1 o' c# X' q, P" E  h
         * @return 正确的值:<T> 错误的值:null
    + |: A+ S# |" n1 j+ @  w6 M1 P     */
    ) S% n7 g# M. P    @SuppressWarnings("unchecked")# z8 i; ^* X0 X" m/ S
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {9 H# y3 o# q1 q7 X: L$ D. e) {
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();" @4 w$ a3 Y- q& t
            operation.set(key, value, time, TimeUnit.MILLISECONDS);
    1 t  J( ^$ k" J2 f( P* {( V        return operation;
    ( a6 Z: G! Q- ^3 J7 H    }
    ; {9 m5 |) l8 o6 Y9 [+ ~# q  o1 o
    : U  F) o! B3 j. s    /**
    * w* q8 T, q4 I7 E6 J     * 获取对象& N! ~' s# r9 E) L3 K
         *  G  y+ C$ a) R  \+ d$ u' Q0 x
         * @param key 键- s% ~' M( l" J( s* E$ I
         * @return 正确的值:Object值对象<br>
      z7 [2 i& y8 U1 i* Z' I     * 错误的值:null, f( E4 I4 V5 I( w) H1 H/ Q; c
         */5 U3 Z5 x& g5 @9 ], Z9 O! l
        @SuppressWarnings("unchecked")
    : }: S. n. q$ f, S, N    public Object getObject(final String key) {3 y+ ^  H' l; M& V) t
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    ( y0 |4 ~) I/ X0 O' X6 V6 H        if (valueOperations == null || !redisTemplate.hasKey(key)) {( n9 L- Q6 v2 i& m( z3 v5 H
                return null;
    % o6 H. r; n) v9 ^3 g/ @        }- _/ B5 }; h) P5 r1 [  {
            final Object object = valueOperations.get(key);
    " E' r  t; }9 W$ I) f6 M* u2 e* K1 K        return object;1 S$ r( J* k! i3 F2 q4 Y
        }- A( `) c1 w  |, t& b2 `& r

    1 U5 d7 c$ ]. u6 o0 w    /**4 R3 h+ c- H7 y% h5 n% J
         * 从缓存中获取string值8 d+ _% x0 [! l* y) X! G
         *2 j1 t5 q* M5 V5 B0 u
         * @param key5 o; P% `8 [- j9 e
         * @return*/" y8 b0 w- Y( ~' b
        @SuppressWarnings("unchecked")" r# u% [1 u' f! a; e6 Z2 F
        public String getString(final String key) {5 H' K2 \8 ?  V! w  s( D
            String value = "";4 f8 b# f+ a. o# M: u9 R3 R3 U) @, n
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    * Z* w9 c6 U9 j$ O) W* ?: j        if (valueOperations != null && redisTemplate.hasKey(key)) {& I% w% d2 b' V. h
                final Object object = valueOperations.get(key);
    2 \* i" U3 B4 V3 z* ?5 p' F            if (null != object) {- Z6 B: a  H1 T
                    LOGGER.info("--getString--object not empty");) X+ C/ j$ H1 {' M: i$ \
                    value = object.toString();
    " K. n; T2 A# r+ v3 m6 Q            } else {
    5 S4 [+ F; C. i7 M8 d; _" k% G                LOGGER.info("--getString--object empty");
    + a1 b, E' s& R' D: r+ s            }5 \- ~9 X3 ?# O# e6 n
            }4 n1 N  q- I1 L! O* x, T
            return value;9 e# b" z1 H9 w2 S
        }! k, f- y% P3 _% D# A9 m' q
    2.2:在redis中实现时间控制
      K: U# |1 g% q* s. P* ~1 O0 A4 u& V0 t- S) i: L
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    . P% `8 A# R) X7 Z( e$ \7 c. V4 L
    % U& W' S' N  L4 m' X/ j8 Oimport com.youjia.orders.redis.RedisManager;8 ]# O' E) K/ V
    import org.junit.Test;
    8 s$ p; w3 e' ^3 `" G9 t0 J7 himport org.springframework.beans.factory.annotation.Autowired;
    7 l3 `! b8 \2 n( ?! w! [  f! @
      d: I  h% T# }( R; r' Himport java.util.Objects;3 G+ Q/ _1 z3 H
    8 S- H6 \) `3 [, E/ e
    /**
    * J( O4 \! c1 V2 X * @Auther: Yrion
    4 n) ^3 i: y/ v  { * @Date: 2019-01-11 23:36
    % ]+ ^/ _0 k8 f# @/ G */
    6 S; u8 D/ P* G+ E' x3 f$ I6 _3 E8 _1 q2 m# F
    public class RedisTest extends OrderProviderApplicationTests {6 ^1 A1 \; O2 d: Z0 V

    ; t, P! [: v& C1 ^  E    @Autowired
    5 y7 X0 z. O/ j7 l# X4 }9 l! n2 @    private RedisManager redisManager;
    ) J/ `5 H$ q* W3 P: l1 {2 E$ K) _
        @Test
    ) \# b# z# H: a( {, o    public void test() {
    ' Q% P6 }  [$ o        controlTime("10000001", 10L);
    " j& P8 ^# o$ v" S7 z& o, G; g    }
    1 S7 J) b( F* s3 d' J# E, `/ e& p& d  n
        public void controlTime(String requestId, Long timeOut) {* l  V! S4 e& Q2 G/ T* ]
    ( d, @% x) O& ~7 G' D& V0 D0 Q% s* ^
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {7 D8 K6 r" o8 {" x7 B% _# L
                return;: o7 w0 u% t2 d1 }  ^
            }% V2 v3 e3 i! D9 ?3 K
            //something code
    ' C. Z$ D$ u# K        final String value = "value";9 `) I+ A) _+ _/ H! F
            redisManager.setObject(requestId, value, timeOut);
    6 n& y) B. F0 }% V        final long startTime = System.currentTimeMillis();
    + r+ `: ]3 I4 N0 z) n3 z' E        System.out.println("开始控制时间");
    2 ~5 U  I% ^( X6 N+ I  c1 b        //start( Q/ v% [% ?7 ]. a6 x) X- }: K0 F
            for (; ; ) {% j' G! d! j- s6 [
                if (Objects.isNull(redisManager.getObject(requestId))) {
    + ^, u& {# s- ]. l% w                break;
      Z: F$ c2 l  ?0 J; s, T8 D. Y! z            }
    0 y4 A+ z5 L( c1 v  ~6 @        }! o5 ]# C, s; ]% n
            final long endTime = System.currentTimeMillis();1 d; `" J) A7 X0 v: c( U: J4 W" |: s5 U

    0 f6 o7 I6 q- K; {* c# u        final long useTime = endTime - startTime;5 z4 \- k* w1 [  l/ T

    ! \% o- u, S+ C9 d' C        System.out.println("一共耗费时间:" + useTime);4 ?1 }, Q1 f, B( n9 Q' I
        }
    - \' c$ f& E: P, n6 A4 H}0 W: r3 [0 y5 L% ^6 K
    outPut:  A/ R9 H% L8 d: o8 d4 j0 k6 y. }* @
    $ R+ `* e4 R# T3 ~1 d! `
    开始控制时间
    # ^2 N  S, \5 c5 [, x/ `& w一共耗费时间:10042( H. {& |% r# A, E1 @
    三:总结" ]' v2 w$ c/ h7 _- d1 t

    6 Z3 i# w5 t& W8 \0 B% q本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
    3 u: \# m7 ?# d/ \————————————————
    + h$ y* ]) w) m% j0 R版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    9 K- m8 N- f* N. \, g$ I9 I原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325
    ' ]3 t; [' J( ]0 W' A0 H9 W
    . v/ G2 ?( j, `2 Z5 `. n, e3 L7 H( ]2 }7 H5 X3 C& ~6 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-9-8 14:37 , Processed in 1.002514 second(s), 57 queries .

    回顶部