QQ登录

只需要一步,快速开始

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

    * Z, B+ h' X- E7 T+ W. zJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。9 |$ ?6 V  {# e0 t9 b" [

    6 Y3 C. E" P6 ~1 R, G3 s+ u一:时间控制的几种方案: s- O3 {- i$ Q0 I) w/ L

    & r: F& _# {. p) {; K1.1: 从线程方面解决
    0 y+ R, t3 N. s3 s: R, R9 e& ?6 @
    5 `* d: F1 D7 @4 P  z4 y' Y$ h. q最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。. u1 e  u5 M! s  @7 m$ K

      A: Q# V9 M7 X. s. ^& z- P( {1.2:使用Timer% W! a2 H% O' u' W6 l
    # a* r6 f! x9 x7 t- ~
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:6 ?7 _+ g8 W+ L$ v( l

    5 `4 v* ]/ F1 Wpublic class TimmerTest {" C" S0 M  a: X8 j& V- S
       /**
    5 D% Q% ]/ V4 @7 U     * 测试方法) [/ T( B& I2 y
         */% m1 K: d2 O5 j
        public void test() {) I0 u' f! \$ x& {+ `7 n
            Timer timer = new Timer();4 z; {; I, i- S! z0 m3 Y
            timer.schedule(new MyTask(), 800);. I7 K: U9 j4 Z0 r  v; ]
        }
    5 _2 o! ?( t' d+ g
    + e8 |, u0 g) v8 p5 q% Y    public class MyTask extends TimerTask {( \0 i1 Y' [7 I1 p# i

    8 c9 |3 K0 ]6 e# O        /**, l& J" C+ k3 a: d2 c: e, z
             * 运行方法/ Q( Q. c1 d) ]/ ~8 a5 v* e6 W
             */1 s8 e: K% s  ^9 g
            @Override+ R) E* M/ g+ T5 m6 Y; Y) o$ X3 J- [
            public void run() {4 C7 z- f" l8 X2 Z" I
                System.out.println("输出");
    0 H1 E& H- M$ H5 v( }$ \2 @4 P9 J' K5 Y        }
    8 F( w& }$ U  N1 Y# I5 I/ E9 ~$ d    }0 F; Y+ B& g  x4 [9 i; {
    }% w) {. P* r2 f* k$ p+ g1 _
    这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。/ e# _1 y$ ~4 Z2 c# P
    * Q9 h: ?* `& c8 d/ X5 L
    1.3:redis延时
    : r! r( t9 R  e+ z( N7 `9 B' d7 ]( l$ ~8 \
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
    $ M- g! y% R: o/ |* s) ^3 V4 \; A( K
    1 g0 |; e5 u9 C8 V% L+ x9 [6 u

    ( V( Y: ]/ P& v) a4 M8 a通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    / X0 _- _4 t* A5 ^: O9 w- G) w& R* H" T! J& q! i
    1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制; E6 k: F8 w$ S/ P

    ' S2 I+ Q3 w% D/ p2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现5 c- r) h+ p  B- \) Z
    3 f' q7 \' k. X) R& b: [
    3:简单,真正的代码实现起来只有很少,下面会给出代码示范。( S. j) n4 y- F" t6 O
    8 q# `2 o" ]0 V% c( a; S# @
    二:redis8 v$ h! J! S& G$ _* _# V0 n- k; s
    7 Z5 N! b$ h  a: b
    2.1:maven中引入redis3 P% b) q. W9 f" r4 u( u4 S& v

    , u' z7 I0 A" Q4 j' I引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    - ?% V+ _: b& o
    - I2 o9 Q: h) ~: E% t$ J, u+ k<dependency>. _, Q" L9 ~" S/ ^3 t, |" w
      <groupId>org.springframework.boot</groupId>
    $ V. W( e! T6 E    <artifactId>spring-boot-starter-data-redis</artifactId>8 H4 ^5 v, y; [: Z- t4 n8 m
          <exclusions>
    2 g3 s1 T; o( i1 I5 H: m        <exclusion>- E6 B) N2 L! v5 `2 O) C
              <groupId>io.lettuce</groupId>
    5 X# L$ Z4 n0 k2 e; L3 n          <artifactId>lettuce-core</artifactId>( I# U& ~$ H4 n1 r% D! M
            </exclusion>
    6 V8 s+ W3 a3 _1 C& X      </exclusions>
    ! l% \8 ^6 ^6 x: [2 v+ y</dependency>
    3 N- g. ^. u  `/ L0 ]<dependency>' V8 q# c9 l( g' y
      <groupId>redis.clients</groupId># F2 f9 s, o1 c8 }" w/ U8 m4 }
      <artifactId>jedis</artifactId>
    ' _. ?5 w* _# y! B& H  @1 g</dependency>
    / _5 g- j* D3 B$ Q" t9 R& o2.2: 在springboot中配置redis
    - f- y0 r# G8 c" g- ^: b7 o; N: P) K* k
    import org.springframework.beans.factory.annotation.Autowired;  m. j( y% u; s: K: L+ m
    import org.springframework.context.annotation.Bean;8 j3 r9 I3 `9 D
    import org.springframework.context.annotation.Configuration;
    5 F$ n9 `( ]) U- w( L1 j0 Simport org.springframework.data.redis.core.RedisTemplate;
    7 d, v! ?; M( l% O, G# L" d$ e: [import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    1 Y+ {8 P" c$ u+ G& h( z" }* jimport org.springframework.data.redis.serializer.StringRedisSerializer;
    . |7 g, W$ }- A: X: h* ^# `  V* Y  }/ Q8 [7 s
    @Configuration
    ; Q7 Y5 b& l1 E" f' E% u- jpublic class RedisConfig {
    9 }, Z" i/ t4 a) ]0 W8 J' w: s! e  T$ v/ F4 u
        @Autowired2 `- u9 }9 X, l& e0 V& L# W
        private RedisTemplate redisTemplate;
    . i/ R7 a7 R! [# d, Q7 f+ r
    ; w3 N- z  k& p1 Z2 j    /**7 d6 [% v* a: S) n* I0 x1 ^
         * redisTemplate实例化3 q, x" V% ~1 ^. y/ r- v1 C
         *
    - V* r3 x2 B- J' m- {$ q     * @return- l! S3 y. D; L6 a" E2 d; l
         */
    / f2 j. t( e/ I- J% k2 m. m8 |    @Bean. W6 W- Z. H; o/ A$ ~
        public RedisTemplate redisTemplateInit() {, s: u2 D9 V% M9 q9 e! T6 d
            //设置序列化Key的实例化对象
    8 M5 d+ r' L' h0 K/ }0 y        redisTemplate.setKeySerializer(new StringRedisSerializer());
    1 S. d! b: _9 b( z        //设置序列化Value的实例化对象
    : D0 C- Q, S9 K' ]% c        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());. U8 N; w" g# _  \6 ^2 c
            return redisTemplate;
    4 G+ e% N' a0 x0 I2 C; ^0 H2 z    }
    , y& r" V: z5 c9 c. c. ?
    8 }) Q4 p* c7 T' a& g}
    0 P5 F# U* \7 h, k5 {1 i2.2:redisTemplate模板工具类  W$ W) T" p3 `; X

    0 \: V' u1 X# h- ?* B' R$ G4 d; X@Component* U" f; A. e4 s# V: e, S3 B
    public class RedisManager {
    # D9 a! V/ j' \8 ~, j9 h( `/ Z$ s
    * \  I9 b+ s/ a/ T* p# S    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);! g" D& v/ g$ @) D: k$ q5 R" Q
      H; V7 d- I* q2 x; O
        @Autowired
    1 `7 D6 f( y+ p  K    private RedisTemplate redisTemplate;
    ' V/ O) I4 C# G( E* [- I3 C" B5 \& f
        /**, U( {" Q" t  g& p, L
         * 设置对象: f8 l" C% A/ m) C6 X
         *, L! c* R( h- h
         * @param key key
    4 M* a* p$ g# ^$ b$ D     * @param value value值
    4 e8 U3 t- E, S- @$ C0 `' r9 a     * @param <T> 返回值泛型
    5 `* @- r: o& x6 A- v     * @return 正确的值:<T> 错误的值:null( L6 m' o5 }' G5 [" C5 |  j
         */
    1 M8 g, I3 A2 ~    @SuppressWarnings("unchecked")
    1 p7 ?/ g$ G. x1 @. e    public <T> ValueOperations<String, T> setObject(final String key, final T value) {0 u& V" E3 q* A: v
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    9 h8 k( E7 I/ Q/ w: o. L  `        operation.set(key, value);
    3 ?8 o3 D( ^' v  M8 t        return operation;& M: i: q0 I) d' M3 ~9 Z
        }: P; l7 x+ j& {  m
    ! v, |( G+ H9 ^# F/ f2 a. D
        /**+ e6 n, j! L7 K9 d4 g0 p' z
         * 设置对象及失效时间 (单位:秒). Q, j  Y0 Z* b- j! f4 p6 s3 t+ X
         *
    3 g0 `) Y+ v# `' o. B$ t     * @param key key
    8 r5 b$ c8 c+ M8 s% E3 F9 _$ l     * @param value value值2 a" _4 R" `$ t7 E7 A. U* V
         * @param <T> 返回值泛型' D8 B+ O% ]' H4 p* s
         * @param time 秒值
    , ~7 Z8 y$ i" Y: f. N; G     * @return 正确的值:<T> 错误的值:null
    2 Y# A5 R. k; d3 H4 @$ }, x     */
    ) y7 ]5 z' [5 @  b    @SuppressWarnings("unchecked")
    % P5 d- e# ~% u- g5 \    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {7 a- G/ a4 L) W* @3 w
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();; F- c  p& I/ ]4 H: c# `
            operation.set(key, value, time, TimeUnit.SECONDS);
    ) @* A. B+ U( a5 @3 L        return operation;8 l- B; f/ S/ L( {) a. y
        }) o+ w: |% b9 w) A
    # K% _0 q+ q9 b9 |/ |% h+ t* y

    & ~3 v0 M" |$ o    /**2 j9 V2 v# D! t  \% h- V
         * 设置对象及失效时间(单位:毫秒)
    ; w* H  I; i' B6 }+ y0 Q9 e     *% O8 u; m) W0 O) ^' G
         * @param key key
    2 k% N4 h0 m4 j     * @param value value值& G: T8 K1 F% x) I2 @- T, j( I
         * @param <T> 返回值泛型* Y  C; ^( e8 o( U6 {
         * @param time 秒值* b* ~( h* ~& A  o) L
         * @return 正确的值:<T> 错误的值:null
    : g6 k. l; U* w$ q     */
    6 V" C5 c+ U* [    @SuppressWarnings("unchecked")1 S) Y! `; `7 b5 q
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
    8 U  ^: i, C/ W2 L, u7 o        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
    # J8 B8 U8 g5 a3 Y& e        operation.set(key, value, time, TimeUnit.MILLISECONDS);  b2 Y2 H) C( t0 L2 e9 Q$ h! _
            return operation;6 u' C1 {; g% `8 G5 A0 m) b
        }
    8 o0 k6 y/ I+ _7 e- k: c" V5 |
    # j, _( d0 u$ V- s    /**3 s1 p: K0 g& S- \! A! `
         * 获取对象- Z1 U" }* C, r! }* v" D
         *; x1 d, G' @# C& _. q
         * @param key 键
    ( h' X6 J* C( [5 _3 @3 B0 q     * @return 正确的值:Object值对象<br>
    : Y3 V5 V/ Q9 o     * 错误的值:null2 h- S. z8 S* M# a* m
         */- A9 p8 l! I$ R3 L7 Q
        @SuppressWarnings("unchecked")
    , [0 l2 ?1 r' r" l( y2 c    public Object getObject(final String key) {
    * n2 n8 O! D. ]+ g# Y* y8 h        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
    1 Y: l! G5 ], ]4 `        if (valueOperations == null || !redisTemplate.hasKey(key)) {9 y) j* n/ Z/ ?; y, d
                return null;
    % C5 v6 D7 T" O4 L1 ?; H        }
    % i) M" K. U# a8 i        final Object object = valueOperations.get(key);
    7 F& Z# X$ c- z) D        return object;* e8 W+ e% T1 [+ D5 ^3 o5 D( w' J
        }
    5 C2 m  Y  E. }: |3 W
    6 w* ?3 K& E! n' y0 T    /**! D1 n" p: T- }( P
         * 从缓存中获取string值" i- e* q& H1 ^# L
         *
    $ r, v! |; r- x2 W     * @param key" R( \: A" D# a
         * @return*/5 C6 J" q; d& V) E
        @SuppressWarnings("unchecked")
    - e! F3 K6 A4 G" H    public String getString(final String key) {
    + r0 {- S1 h5 [6 k' N        String value = "";
    % h3 j# i+ I7 r: y0 j9 B( m        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();' [1 T4 q4 w7 z% R
            if (valueOperations != null && redisTemplate.hasKey(key)) {
    / @1 F! G, C0 H5 H. ]8 b            final Object object = valueOperations.get(key);
    5 q, t1 z9 Z1 e4 o8 d& R/ _            if (null != object) {
    # m5 L0 w, h1 h/ k% U1 P4 w0 @                LOGGER.info("--getString--object not empty");
    , o- S  M! \4 b6 Q! a: r                value = object.toString();# G- O% A; \/ y+ U; x+ T8 v
                } else {
    + S# R5 D5 ?" L8 @4 Y7 K9 k  F# z                LOGGER.info("--getString--object empty");
    6 o4 z( w, i2 N            }' |) F% y$ m" z7 d# z
            }
    $ n% D; A5 Q# P        return value;
    % b; p' o1 l  S, T    }
    , \- `7 Z; ~6 [% r$ A2.2:在redis中实现时间控制# x$ b8 z9 C2 ^5 G" x: u/ H
    9 p5 i$ T" L5 _2 ?% k: _
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。  s! q6 E4 ?1 ?8 I
    3 ^% W5 V" }$ G1 ^# L4 L- v" e
    import com.youjia.orders.redis.RedisManager;
    5 O: [% N' S6 p/ P4 G3 E& Uimport org.junit.Test;7 q4 Y. }; e5 F+ A3 W
    import org.springframework.beans.factory.annotation.Autowired;% r+ h$ Z: S% |1 V5 _" s/ h2 d
    . m# \% O5 S' S3 A' i3 G
    import java.util.Objects;+ }$ h- D3 c) ^: f

    / t- H/ [3 y$ I+ H7 \' _. ~" N' t/**
    ) q4 {" w  e4 S5 M$ a * @Auther: Yrion
    ; l; Q' ]; j# m) x * @Date: 2019-01-11 23:36" o0 R+ F! C4 Q4 f3 L/ {
    */
    1 E% y, y2 V, L& a8 ]& }, b8 u% \0 j
    % N5 |. ~0 t* k1 qpublic class RedisTest extends OrderProviderApplicationTests {
    8 q& ^/ K" d' P8 [8 a
    & n+ Y# F1 Z% C/ O8 J    @Autowired7 T( N. _/ ?8 a
        private RedisManager redisManager;
    9 ~4 {4 Z: U- ?0 `2 _! M; W+ U" c. U- }+ H5 u
        @Test
    , ^2 Z4 ]# {/ r5 F% h    public void test() {! ~+ J( p# o6 ~3 w
            controlTime("10000001", 10L);/ F5 ^' ]9 H2 R/ M& e* C
        }
    ) S9 p) l6 x% b( A/ s" Z$ p. G" T# l- }$ P" X! W
        public void controlTime(String requestId, Long timeOut) {: o' W: }: s# O$ z: q8 E$ Z

    8 }/ C; d' d7 Y1 A- D! T        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {
    3 J* j  ]9 V) Q4 H& Y            return;
    , U4 i& b# S1 `* H        }
    ( P: E" _  v  i* w; I) K        //something code
    1 |( p5 u5 F+ z: }# u" T        final String value = "value";
    3 L0 k% c3 N$ V) R: Q' p        redisManager.setObject(requestId, value, timeOut);. l, A( E# E0 r- r8 L0 q* ^
            final long startTime = System.currentTimeMillis();
    1 |" ^7 T) }0 ?        System.out.println("开始控制时间");! y7 A% S$ u0 c8 z0 W, l# k
            //start' L8 F9 g+ u8 ]" h! Q
            for (; ; ) {/ D* `, ?! A: s# s
                if (Objects.isNull(redisManager.getObject(requestId))) {4 P! B3 ^, |5 {7 L- ?# U6 y
                    break;
    $ N* T( Z6 d- Z5 O/ d            }- u- Q9 y5 ?+ j) e5 L
            }
    ; S4 c. |+ w; e' d        final long endTime = System.currentTimeMillis();
    , e0 f; E4 C1 d- A4 Z7 s3 r" P- L
            final long useTime = endTime - startTime;
    ( T3 A' ~# y3 N3 L5 J9 m% y) F* }( A2 z: I7 t
            System.out.println("一共耗费时间:" + useTime);. A5 k) S: L: w6 ~: R) ]0 {- [$ `
        }0 @0 F$ T/ ~  r( o
    }
    4 y* {( Y8 j  Y6 \6 V9 N, }. doutPut:
    % b" ^- @; F/ {# o- E- R( Z, y; P# O( m
    开始控制时间
      V9 Z0 j, s0 x9 S一共耗费时间:10042
    9 S# ?0 E7 h. f- I三:总结
    ( @5 s% E- R9 P* H6 C
    $ i2 i0 s  t2 L# v) M4 l$ v本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
    1 {* L* o9 b  i5 Y' r————————————————
    # n  v7 L. G$ v- V( N" V1 D/ k5 {版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。/ w- u9 M2 L9 n
    原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/1058933257 P: {) u2 s8 s  I+ e/ ?

    0 r4 }) M; V' H  s5 {
    + e7 q! |% g' x& @. S) q
    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 05:39 , Processed in 0.514850 second(s), 57 queries .

    回顶部