QQ登录

只需要一步,快速开始

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

    ! n& s3 @1 w& S5 u0 U6 Q) EJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
      B8 ^  y& a; m9 \# x& y
    " f' i3 h0 R: W5 k$ v一:时间控制的几种方案; l4 W8 m0 {; |6 v/ u; P  l; O
    ' \) [' `' t# m( l
    1.1: 从线程方面解决
    4 U+ G2 }/ f( j$ y0 s% Q0 l- Q/ {) D# L- ~% m  u. B" G1 o( A' e( Y! g
    最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    , [/ ~3 ]5 W0 I/ q' d& |9 Z0 R2 ]0 p/ W: h/ v1 R  i
    1.2:使用Timer2 d& @9 ?! W$ x  M; O- ~

    # Q' W! l& v! x查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:1 `3 |1 {1 g4 V4 C. |
    ! X: A/ Q8 h" `% D) y
    public class TimmerTest {# P" t" w+ I. P" C( u; k
       /**
    9 w' @" L! X( T$ H8 q  B- }     * 测试方法
    ' T3 k5 H" \8 h2 N     */7 r! ^% g7 A* ?2 o: h0 n
        public void test() {
    . J& q* O8 Z: s7 R! J        Timer timer = new Timer();
    0 H* C- |, J8 R+ e        timer.schedule(new MyTask(), 800);+ d5 F* f) w" }
        }. {& s) w$ ~, W" G
    9 W/ \2 [, ?, c- B$ s
        public class MyTask extends TimerTask {
    ) N7 j" O  z+ x# W. T' |  A0 t6 j7 G' l2 s; m# J
            /**
    . p" J2 [" R/ z. {* D% d2 I         * 运行方法
    - x! s8 i, g% B+ R$ p         */
    % p! i0 B- e- u        @Override
    % D0 G3 Y" k3 k        public void run() {2 m/ Q3 z. {8 j- |5 V' c1 n
                System.out.println("输出");7 |# `) a$ s1 J( t& A0 Q
            }6 s2 h" l& B5 [$ |/ s' @; W7 B& w
        }" Y( F/ j/ s8 W9 @" F. d
    }
    , d) v% _5 F; ?这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。) x& F3 R$ U9 h

    $ W9 m4 w; B2 Z1.3:redis延时' C! g% K" e; s: f9 r
    7 S- A5 b& J) D( z2 t: ]
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
    5 |. I* a1 J" l& n
    - S. W( L% z  ?6 X" l* D9 \8 V# x+ Z, d! i& }

    * j$ A; F) n2 K! k- ]  R通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:/ n( s$ g+ m4 p: {

    7 v5 j- V* _) q" V# ?3 r1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制
    / Z' h5 H: b6 ?4 O
    9 w& u8 X9 ~8 ^. b  P  ^7 v: I) I2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现1 m1 B" F% ^1 g0 \2 I0 w/ l7 G

    ) \8 _' c7 D' o$ i5 K. q0 ^3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
    9 u' T  o; X" U9 ~+ a8 D( J, v  d* t' Q7 p5 s
    二:redis
    / I! Z' L0 O; x- [! d
    # x  `: t" o/ f5 u/ P7 H2.1:maven中引入redis2 h3 g% r1 y. D- x; P1 v
    - H8 U) C! i, \  d# z4 D1 F: @8 U- T
    引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    6 P6 m* p" p9 W
    6 j$ b: w: D: ~8 x! k1 ~; Y<dependency>
    * U1 _1 {5 O! h( n$ a" j) d5 m  <groupId>org.springframework.boot</groupId>; W2 Y5 I& f+ G3 X& L& @+ `2 J
        <artifactId>spring-boot-starter-data-redis</artifactId>
    9 a* D; C2 K; p/ \9 A      <exclusions>
    & R; N: H4 X' [        <exclusion>
    1 n- u# @1 s" t) q* V2 U          <groupId>io.lettuce</groupId>7 v( q. e1 P# Z: [! o
              <artifactId>lettuce-core</artifactId>+ Y, U% M3 a7 G2 d# l! @
            </exclusion>; X5 V# o1 i7 k( l( m
          </exclusions>. X. Q/ A/ S! M  _
    </dependency>1 {: i1 O7 D5 U/ U
    <dependency>* S: U9 E( y7 ^& m  C/ m
      <groupId>redis.clients</groupId>
    4 z5 T9 N. D6 @9 E; r- ?) S1 x- I( C  <artifactId>jedis</artifactId>/ P* M- N0 p* m, V9 b& z3 T" J8 X/ a
    </dependency>
    5 C5 t; h  R# D5 s. k, l2.2: 在springboot中配置redis
    ! y) A5 z/ |6 `$ e( c3 n
    & R+ L. F+ U) V  v8 [% uimport org.springframework.beans.factory.annotation.Autowired;' t8 Y6 w1 H5 B9 n- d
    import org.springframework.context.annotation.Bean;
    7 j% [$ F  M/ b2 |; a+ Q0 s" }. ^import org.springframework.context.annotation.Configuration;
    1 D3 s+ w$ f2 ]6 y* Limport org.springframework.data.redis.core.RedisTemplate;
    . \( x; {7 f9 @! D% Y5 \0 O; Himport org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;: _. {$ V$ D5 ~# l8 n
    import org.springframework.data.redis.serializer.StringRedisSerializer;$ O8 p' I: G4 M* O

    ! c, ~0 {, \- z# a3 f@Configuration- z4 S) Q' m! p; T( D( |
    public class RedisConfig {
    ! p7 @, R" R6 |  x. e$ g- a- I/ Y4 H' l/ K* a
        @Autowired( u. O* Z" Q; p9 T9 N7 L
        private RedisTemplate redisTemplate;
    ; z- b/ u8 r9 _7 p/ o% J
    6 d* ?) i" Q$ `- E: d8 d    /**
    - Y5 U7 M' W9 g3 w' d' o     * redisTemplate实例化' z; u4 C% d- Q
         *
      R8 Z% D8 r9 q1 t     * @return8 q$ R1 H+ `- t: J8 I# i& N6 m
         */
    3 _7 T; X/ p  w# o! R+ N% F    @Bean
    & t! g# y7 |6 H, }    public RedisTemplate redisTemplateInit() {  }0 r# [, }. |" O4 u" H
            //设置序列化Key的实例化对象% S* w- j9 M3 i: V
            redisTemplate.setKeySerializer(new StringRedisSerializer());
    : O7 [9 M( T8 @3 ~# ]- c3 G+ W        //设置序列化Value的实例化对象9 A: U# ~' a) m
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    ; Z$ U1 s' ~1 q/ Q, X7 D        return redisTemplate;
    1 l; f% W% t2 V9 t; ^    }
    4 d$ w2 t. E/ m4 W. O7 E  I! u+ e- D9 B! p; `5 e2 p# F
    }
    1 b2 d2 \$ {8 Y0 J/ P" J3 K2.2:redisTemplate模板工具类
    1 r" U+ K2 }8 Q" e
    - t+ D- |" l; t0 Q2 o@Component" l  A/ F" j, x  }" J2 e
    public class RedisManager {
    0 Y, z& O; a# H$ v" T& L
    ) E9 L. o& F3 N' |    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
    , C  I! a* G$ `" S/ I- C7 V. y# g
    & I) }! \, m. u# ]3 S6 j; D, _    @Autowired
    # q* ^" d+ H- M% T: X4 r9 q/ c    private RedisTemplate redisTemplate;
    ; d5 |) A% |+ Y: d, m% S# w6 T8 Z" V+ `2 S' y& [
        /**
    5 A7 B6 @2 o/ @6 N) {+ b7 ^     * 设置对象; C* J  ]2 p$ Z: L% q$ m
         *
    ; g3 T  m1 C; m& B$ M     * @param key key
    . h" V; W* ^) M3 p0 L' h     * @param value value值
    6 p$ H/ b: o" y1 Q/ l' B6 a     * @param <T> 返回值泛型
    6 L( ~$ M  P+ i7 U( d     * @return 正确的值:<T> 错误的值:null+ e! E4 @+ |; r! k. ~6 _" V
         */
    2 R$ w) k. @5 @* u    @SuppressWarnings("unchecked")
    4 Q, E- J8 v9 N: {- _9 C    public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    # c. O- c/ Y! R2 Q        final ValueOperations<String, T> operation = redisTemplate.opsForValue();/ ?9 v% C) p' ~
            operation.set(key, value);
    * s* J" r: f- G: \        return operation;
    6 \9 Q# L% o  g+ Q+ X    }0 p$ I5 W) T* X/ g' n$ J
    $ p3 x# o- E/ q* ^  L( k& B) C: c
        /**
    , n* W6 {0 k0 t% }. P/ F     * 设置对象及失效时间 (单位:秒)
    2 [2 F- A) v! A+ P( N+ k     *4 ]; q4 k6 I$ k' L* L2 z- V
         * @param key key
    # u2 g$ w2 W7 S1 K& `! _     * @param value value值* |) I% `# Q0 W# D
         * @param <T> 返回值泛型
    6 d5 v; p& l. o1 O# U6 X     * @param time 秒值5 o5 P6 _0 k4 C+ |$ i$ {
         * @return 正确的值:<T> 错误的值:null
    , l3 l! G7 N( T( F' z5 f* A     */- ?3 R1 S" Q% N- O7 ^- h
        @SuppressWarnings("unchecked")
    ( n$ ]2 I1 q" _  J    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {  G: N' x; l; p( n" ?' u# W
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
      j  m. s, T; [. Q$ F+ ^6 |0 K        operation.set(key, value, time, TimeUnit.SECONDS);
    0 s- c4 R& ^3 ^& \5 S: Q5 E1 Q% F0 x        return operation;
    2 g4 f7 E! v$ Y& l0 t    }
    # x2 q% M0 f# g4 v! N- d  S1 _( j' S
    5 i6 K( p( c3 z% R. W# P& z7 X
        /**" m% ]5 @& \0 g. Q* O
         * 设置对象及失效时间(单位:毫秒)
    1 a2 d" Q6 r) Y  k& q     *
    $ ^6 u. k8 d6 M# X2 K     * @param key key# i- k5 S# J! O* l+ F! `% r' C+ X
         * @param value value值, b3 Q9 E$ X' ~* u) e, b
         * @param <T> 返回值泛型# l  z- z: `/ M
         * @param time 秒值
    4 W0 H- A. l  g; P" g0 V( v# V/ [. d     * @return 正确的值:<T> 错误的值:null- L; ~5 C. m4 Y, A' C+ ^
         */% b& C/ z) [5 s8 D9 v
        @SuppressWarnings("unchecked")
    , F% T9 x, m1 k9 d8 X    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {( e1 h( R# F& [* w. @1 K
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    # Z  A% |/ e% _4 O8 b% S        operation.set(key, value, time, TimeUnit.MILLISECONDS);4 @! F6 P  y9 S( y1 o. G
            return operation;
    0 E  P3 i+ A: \0 j  D5 h9 w    }+ D6 Y5 q; ^' ]5 E/ K/ I# ^
    # |# b. f  k! B: L; V! ^" l
        /**- L# H* E& h9 ]: a! b: H) m
         * 获取对象
    6 o) b- Q: l; H) W8 a     *
    1 R% ?$ T% C+ T  y; a* x     * @param key 键- x6 g" y9 D* T* J1 f! H
         * @return 正确的值:Object值对象<br>0 K( g: j4 a0 s! m
         * 错误的值:null
    + c9 j7 k+ y3 A) b/ a" `     */
    8 u* y# V8 r: r# ]4 R    @SuppressWarnings("unchecked")4 K0 }/ L, n& z& Q
        public Object getObject(final String key) {
    * h+ Z; c+ ^4 Z: P9 D$ W$ G9 @        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    # {2 ?5 P- F! ^$ U        if (valueOperations == null || !redisTemplate.hasKey(key)) {& N2 b6 ~! p8 B; h1 V( i) ]: y3 L+ S" [
                return null;
    ; F1 Q5 `- _  ~$ ~        }
    ! j1 }0 Q0 M: |+ }; Y# q' r0 i* n        final Object object = valueOperations.get(key);3 U' A1 z4 ]3 \, ?$ @
            return object;
    + p  v" u& {+ A+ o5 q    }
    % K/ {7 u- x% j- C% V" h# f7 f8 r- Y- A1 S) o3 U( d5 n1 ?* V
        /**8 U) |/ o7 y& A$ I
         * 从缓存中获取string值2 p; |" Q) @5 c, A4 J% U
         *
    6 _8 M# s9 j& U- e+ p     * @param key& U8 D7 ^3 Z2 i4 L0 h, e
         * @return*/
    . c* A- B# T9 @9 T4 u( t6 W, E    @SuppressWarnings("unchecked")
    1 v+ }9 D1 c, ]% [% y( _    public String getString(final String key) {: H! m$ t, z1 P5 {) Y* W
            String value = "";, O. L% ]/ j4 B1 T) }8 N& X0 ~
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();# b; T" C; U% O7 K$ ^$ N
            if (valueOperations != null && redisTemplate.hasKey(key)) {
    4 o4 V- i$ I' i+ K& t. h! ^            final Object object = valueOperations.get(key);
    9 M# s+ _8 ^& h: p0 x' O            if (null != object) {: ~$ P6 G: x0 _9 j3 t. I  S1 z# j  W
                    LOGGER.info("--getString--object not empty");
    + N; x1 u/ ~; s" c5 [                value = object.toString();
    , W$ y# q" @2 M            } else {2 ^+ ]/ A! A+ u- S2 E$ h1 o
                    LOGGER.info("--getString--object empty");* R( E5 k  N% M1 s, c5 B
                }  |/ ]5 c1 Z# u  o  V7 O5 a2 L6 y2 Q
            }
    $ ^8 k5 m! T/ G* Y2 L7 _; _: l        return value;0 L3 E3 S4 `. N
        }; j" ^* v3 p9 \. H
    2.2:在redis中实现时间控制# ~& i2 B& v& p& ?! N% ^

    , ?& O0 g) E1 |4 \* J3 K9 A9 Z' g2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    0 ^8 V% X- i, I$ l% L- n4 N' ]  u# E- z7 `4 q
    import com.youjia.orders.redis.RedisManager;
    8 M- Z2 |) R: Z' h5 ^1 l  timport org.junit.Test;
    6 H: _. Y5 G/ w& ?- simport org.springframework.beans.factory.annotation.Autowired;4 k) x' y4 N  z8 j! S5 p

    ' Q3 P2 i5 ~5 |6 Uimport java.util.Objects;+ m6 Q0 G5 {0 [% B+ z% d% {' M

    " a2 R; j, K7 D- Z+ _( c/**
    8 k7 l+ j) k+ [( X. H$ P( H * @Auther: Yrion" K( n7 g5 |4 X5 U: u2 v
    * @Date: 2019-01-11 23:36" R& V% _* M! I
    */3 p9 I. M! j; Q6 x' W4 b$ T
    ; S! X* M- s% W0 T7 F( [
    public class RedisTest extends OrderProviderApplicationTests {
    3 \- \1 R" Y: ?0 A( M! ~( A. s: V
    . J! n6 G" a; Y0 d    @Autowired' B+ W8 ?$ N5 {+ {8 f7 H
        private RedisManager redisManager;, M( Y# z3 t$ d& [! ^

    # E+ r0 I- E4 q8 [. t# `    @Test
    3 \8 _% U/ F# f3 m# u    public void test() {
    4 ]4 U4 E6 \$ Z# Q        controlTime("10000001", 10L);
    . z7 O! |- v, L( y+ D7 X% @$ Q+ a  ^    }
    ( {/ m* s9 A! m2 |% S1 L- I+ D0 P% L& U' y3 r6 X& V
        public void controlTime(String requestId, Long timeOut) {; ^7 E& u# k3 X; ~+ F5 u
    ! @4 C) F# o+ f6 P0 D' E
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {6 q7 s/ k' F2 c
                return;
    7 g+ n' W) i" v+ ^' P9 d' Z/ I! P  B        }
    ) r, ]. L% |8 M6 z% k7 K        //something code4 c' H2 L% K/ S/ G; b* D
            final String value = "value";
      }& \9 E- W( K, S        redisManager.setObject(requestId, value, timeOut);( @  y. i( Q* o7 U2 m
            final long startTime = System.currentTimeMillis();
    / F8 W0 w* l4 V4 f9 M: k+ v        System.out.println("开始控制时间");3 r8 C( N- ]/ G$ ~
            //start! E, w5 Q; e. }) T1 q) O- n
            for (; ; ) {) W( A: ?; P* t" e# a% e. c0 B3 ?# Q) ]
                if (Objects.isNull(redisManager.getObject(requestId))) {
    ) {3 B3 X- o  G' M8 R                break;- h9 x5 \  A0 V6 e
                }
    5 V; b( {: r; Y/ V: N* G        }
    % V: r8 x" M% `9 U4 z) }+ {        final long endTime = System.currentTimeMillis();  C. a6 g1 x, Q8 b% G# h2 X
    ! Q0 }4 f2 d# X" `
            final long useTime = endTime - startTime;
    ) A4 r2 A$ v& y3 x0 D( |4 ]1 ^: m2 s! _1 g+ m! j
            System.out.println("一共耗费时间:" + useTime);+ S1 g6 y+ W8 g1 v3 |
        }$ ?7 K! J6 B. i$ g5 X0 o8 v% U
    }
    : U5 g, ~8 W4 l4 woutPut:' e' g8 s# h) E5 V% T( h
    % m5 [! g8 X& o6 r( y
    开始控制时间) I: e" D6 Z( e& `8 y1 A  i& W/ G7 J
    一共耗费时间:100420 k" L3 |6 g4 k9 h% W
    三:总结; V" u5 }# o$ X' H

    ! I! Z* v4 h% c. g4 Y# z本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
    * S6 |% }6 x& K% I4 }. ?————————————————
    ( A3 z. k2 c+ m版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    9 K5 g$ E  O1 ?$ i- z& k原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325% V& n, o" |9 N( P

    4 L! b# }4 k* V0 p2 Z6 E& f+ 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-6-10 03:00 , Processed in 0.422506 second(s), 57 queries .

    回顶部