QQ登录

只需要一步,快速开始

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

    " e9 @* V" ~& F- \8 a9 }+ ~Java如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。6 w% `7 m3 h2 a' J* J# o( |

    + `& G: Q7 C& O  K6 S5 ~+ W一:时间控制的几种方案8 f0 |$ x) ~, O9 D2 N9 _4 I- x

    % L0 P4 y* y' z2 k9 Y1.1: 从线程方面解决
    $ @2 X/ A& ?" x+ [$ s5 ^( {0 a5 ]! D# {# {
    最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
    4 _, g" m' i$ M' i$ F& x9 N$ N6 i
    5 r% t2 G4 P7 _1.2:使用Timer
    8 @2 o" {& V# e; E2 m4 u0 p; E' O) u/ K, v) \3 c
    查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
    ( [1 c5 z, g! U0 N9 g" M. P
    % o) \& Q( g' `! y8 R( C4 V4 u: Jpublic class TimmerTest {4 l) q: r) V: b! s$ ?. R$ m
       /**/ Q" i2 a3 R/ _- A6 X' R
         * 测试方法5 y: t/ ?: F% k+ p, z# F& x
         */
    6 X( k# Q  b5 {& r  Z+ h. u( S    public void test() {' u2 g; S. s( @9 z* }0 T
            Timer timer = new Timer();/ @! z6 |4 c* a! O, h
            timer.schedule(new MyTask(), 800);8 Q, Y7 P2 D0 M4 I- o
        }8 ~- S( U4 X: D# A; `

    ; Q4 R( V1 q' w- w7 {    public class MyTask extends TimerTask {6 M$ t7 K2 l) v0 V" H) R
    7 M7 B, [! z0 ?- W$ O- y' h- s  s% ^
            /**0 [  N# j" `( d2 Z- X$ K3 K
             * 运行方法: G' i4 p! R/ a, L" j
             */& ^3 ?4 y! a/ m0 z: n$ V1 ]1 Y2 k
            @Override' O  k7 \/ C7 p1 y& A
            public void run() {
    9 P  g. B$ [* T9 h6 o# V            System.out.println("输出");
    3 B: d2 Q8 d7 f        }
    6 V4 t- a5 u0 G7 q/ M( T' \    }
    & S8 b: w2 n' ^* C+ i  q6 E3 \  q}+ P3 G) k) P0 Z" t
    这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
    % E2 F6 p4 G! V# ^
    4 q) g8 R! g2 z. ?6 z, q; I1.3:redis延时
    / B/ ?5 l4 [6 h4 h/ J4 C$ I* N# Z+ H$ A: i
    在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
    ; y- u! r8 s' ?; m4 d- ^
    % C# k( h+ D# C+ }* w  u& L( v+ ]0 W5 d8 |# G- ~8 K8 b4 }

    1 T* p3 D; C- i! b通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
    9 h. @. e. M4 J; P# ^7 F( I9 k5 E2 a6 t& c6 I& t
    1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制
    7 |& C, C- A9 \$ `/ [0 A. U2 p& U- g' D! h0 K" @$ a' d
    2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
    7 ?- K5 P9 Q3 R+ Z, e* r* u" l/ N) o. N/ c
    3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
    , e; U' {* U( _# M) r0 P. N7 Q! s( s6 g7 V8 [: l& A# R9 H
    二:redis: E" X. G6 ?+ z% {3 E, `- S1 {$ q
    + \9 X& r) u$ D- l* Z- V
    2.1:maven中引入redis  |/ A5 R9 f, ?3 e( a6 U0 `! ^  ~/ f

    / S7 Y. p  Y1 B) j( F引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
    # B2 O5 C2 N' N/ \& Y9 j0 N( M! I( w% d8 v' e4 Y' i% ^2 @1 K
    <dependency>
    ' [; U7 w/ G/ c, k* G+ v7 U$ y  <groupId>org.springframework.boot</groupId>
    3 i; c2 j; Y. Q, N- _    <artifactId>spring-boot-starter-data-redis</artifactId>
    % c# [5 B0 z' e) d5 W' \      <exclusions>
    " X4 }8 t4 O( \: O        <exclusion>
    5 o" L* ?. D, b" [3 Z. d          <groupId>io.lettuce</groupId>& s' h( O& R2 z3 ?; v
              <artifactId>lettuce-core</artifactId>
    6 E' m8 ~0 P9 P! [% f1 x        </exclusion>. b3 b. D# B! O
          </exclusions>; Y- u9 l; \! _& |- e/ _% J
    </dependency>5 P8 n/ \9 N1 G# z0 |2 R5 p
    <dependency>: u9 v9 _3 n/ U; `6 k2 N, ~# t" U
      <groupId>redis.clients</groupId>9 I3 l% M% `. p5 }4 Z3 k: ^! J
      <artifactId>jedis</artifactId>9 V+ j# p3 e- ^/ f% h
    </dependency>
    ) [& R5 I& d9 D" P5 Y8 x2.2: 在springboot中配置redis1 j7 k% p: Q* B! [$ \9 T+ Z

    " q1 Z. [+ Y9 |import org.springframework.beans.factory.annotation.Autowired;
    . u, i2 V7 u/ W" C( _, ~  Kimport org.springframework.context.annotation.Bean;
    " q0 V8 G- `0 r/ Kimport org.springframework.context.annotation.Configuration;! m/ o; |+ L- _8 ?. h' k6 o
    import org.springframework.data.redis.core.RedisTemplate;& ^/ j7 d2 O. I, i. x+ ?" X- r
    import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
    : b2 _8 |  t# B) b: R2 Yimport org.springframework.data.redis.serializer.StringRedisSerializer;
    1 O& r; Q' n4 T" {0 K
    . z' {/ u3 A- m# F" ?. M# _; g@Configuration
    3 r( c, |4 l6 F- G% {5 a  Epublic class RedisConfig {
    4 [/ B! U- ?" \+ B4 w+ s1 L5 X% P6 ~
        @Autowired- H2 S5 f% c6 K" L5 ~$ W0 P& z- f, T
        private RedisTemplate redisTemplate;
    # U' ]- U  D+ ], D6 w2 c6 R/ a' K' b6 K8 F9 f
        /**- u  x0 ^& n* z% s4 i" A, U
         * redisTemplate实例化7 C# Y& o- S& I! r0 J6 L
         *$ Z0 \3 m2 ]  k) U" |
         * @return( W- T4 v: ^# C
         */6 _/ E: W! F2 y. c; D( |4 q. I
        @Bean( Z" w  P  [7 d) G
        public RedisTemplate redisTemplateInit() {
    3 X7 T2 W6 c' s        //设置序列化Key的实例化对象/ r! V* i5 X2 s% e
            redisTemplate.setKeySerializer(new StringRedisSerializer());* a1 V- ?+ n! k
            //设置序列化Value的实例化对象0 q3 ~- K) f' r' p' e( {' b, a$ I
            redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    + M" K9 o! }) z; e5 J' n: ?6 f. w        return redisTemplate;
    . q  w2 b1 f# |    }
    & c9 d# \" ?# v
    ( b0 ], I0 G- g9 g7 T9 Z( C- y) W& _}
    # ]* L5 G# u* s1 A6 E. U) P2.2:redisTemplate模板工具类
    4 d7 B3 Q1 B' Z7 J' y  f( k* y
    : N5 d. w4 p6 C* N@Component
    ! X3 N/ Y( R8 ^public class RedisManager {, Z6 V) l8 N- U+ @% K
    2 K5 h! w# I( M; H' x
        private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
    . q4 J" h  a7 t' f
    5 K8 r8 o- ?' ?! z; l) l    @Autowired8 Y- D8 J! t/ j1 M; N
        private RedisTemplate redisTemplate;6 a6 B' T' L7 A  c
    - |% P7 c# P- K
        /**, x$ I! ]" n- p! }: G" G
         * 设置对象
    5 Y2 R$ }7 c1 ~! j; T     *' G, {1 [- a2 [2 X3 K( X
         * @param key key  b, Z; [; r" u# G) U, j, J
         * @param value value值
    6 x% |0 P2 \& H" v4 r" Z. b4 I     * @param <T> 返回值泛型
    9 M( t" I% S# f& l% E; a" p' l     * @return 正确的值:<T> 错误的值:null! ^6 z& k; \8 N2 H3 x  O
         */8 e: c3 o/ A3 |; }( L
        @SuppressWarnings("unchecked")
    & N6 a1 I% v% v4 g    public <T> ValueOperations<String, T> setObject(final String key, final T value) {
    6 s8 E% G7 k% l8 ^        final ValueOperations<String, T> operation = redisTemplate.opsForValue();- C* H" l: |8 q* B0 M
            operation.set(key, value);
    : N$ z1 A- s; a7 d" o* U' i        return operation;; G5 P4 ?8 l) O  P  C" Q7 o. p
        }
    " B9 C7 Q5 ~+ _6 J- K) D0 _
    2 q2 r4 o- J( x/ A% M    /**
    ! I( e# K6 w* a" H- U# {0 o) {     * 设置对象及失效时间 (单位:秒), H! z7 p& [: U# ?0 c
         *8 J3 R( z: _3 K  F) N; a* |
         * @param key key  y9 x+ l6 J% E1 q
         * @param value value值. ?7 ?, g7 p/ m: c
         * @param <T> 返回值泛型3 [/ m' O* G' J6 \% R1 W
         * @param time 秒值% k+ Z" q1 b1 I' p
         * @return 正确的值:<T> 错误的值:null
    7 [6 W9 b3 B* P2 F" i, C- }+ a0 k     */: T* Q  f2 A! p. u; h- y' N+ f
        @SuppressWarnings("unchecked")& S! I4 ~/ z, R1 O; T1 V, A
        public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {! F8 b+ R- b1 w1 k4 |) \1 l* l1 `
            final ValueOperations<String, T> operation = redisTemplate.opsForValue();  V$ x/ @6 D% i$ t6 B
            operation.set(key, value, time, TimeUnit.SECONDS);
    1 m5 [7 m3 R9 w3 S4 K3 }        return operation;
      h0 e, V, H, W    }
    " n* L" {# F* r) T& ^- R. Z! o* ~4 C3 g- S- E9 K3 e
    ; k% L1 Y  \9 k) e2 |) i5 |$ x6 z
        /**
      o. {; m2 w2 L+ l% {* T     * 设置对象及失效时间(单位:毫秒)
    $ a! t4 D1 F) w/ E& i+ S. T     *
    + \, n( r  w6 x) O( P; F# h4 J     * @param key key
    ! N: Y1 B0 V/ \' r6 U     * @param value value值
    ( d0 _9 _( @4 w" F2 v/ d; w     * @param <T> 返回值泛型
    * H8 ?! ~3 V- A8 z! e8 j     * @param time 秒值
    1 X" e+ U, H% x5 b9 d' ]  l     * @return 正确的值:<T> 错误的值:null& b" o( p; P, q6 R+ d  m" G0 T) _3 u
         */3 N4 R: j4 [1 p# n3 g) c* @# w, g
        @SuppressWarnings("unchecked")2 P/ X: u4 j9 d
        public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
    5 W: }3 y; H9 l; M        final ValueOperations<String, T> operation = redisTemplate.opsForValue();/ `, T  m+ R! L+ r% B" X5 m
            operation.set(key, value, time, TimeUnit.MILLISECONDS);& M7 `- x' g( W/ R- H. z$ ~6 x
            return operation;
    0 Q, G3 b$ s3 R. {+ D: b- j- h    }, u) U) ]: @5 {
    # E+ W% I3 p5 F' y
        /**. A# ]+ A  `, c) Q7 P
         * 获取对象6 L; b. A: P0 M
         *! h  X* \9 J# [
         * @param key 键6 \  Z  V6 m- b- G
         * @return 正确的值:Object值对象<br>
    9 b+ T6 m; A0 ?( F2 L$ N2 x     * 错误的值:null% T1 t: K1 z5 l  Y: B9 z8 @% E4 N
         */1 W7 x, T) D! L
        @SuppressWarnings("unchecked")
    + Z$ B# ^  j7 |9 U6 x# ?& L; d    public Object getObject(final String key) {" z" K: m- O7 T/ D. O) {
            final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();2 g- H) Q( Q7 t  J+ @
            if (valueOperations == null || !redisTemplate.hasKey(key)) {
      t7 v6 P1 V, f, w9 e0 R: x            return null;7 E- E; Q4 }8 H/ D9 W
            }, u. T! J* J$ B/ p1 a0 D) q" |) L
            final Object object = valueOperations.get(key);
    1 `$ c3 [" C* L. \0 d( W# R: M5 L& R        return object;
    . W3 d( G3 c/ u. d& L    }
    " Q1 B' {) r; @9 W2 j" M; o! o3 Z; S& B( y+ U. p! k
        /**
    $ D! w4 |! i+ c: l5 K     * 从缓存中获取string值6 n. o& [) w" _  f1 I( Y
         *
    " l) F% W# a& m/ t1 d& p     * @param key* x7 w( @0 C, ?& E6 t' [
         * @return*/4 X2 w. F/ Y6 R5 N" A
        @SuppressWarnings("unchecked")& Z: e% d: _7 h1 N7 X, m
        public String getString(final String key) {
    4 T' F( C, i) k& X, Z: `0 z6 h+ A        String value = "";
    # u" i! }2 Z& F        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();. E. D0 N. h4 g
            if (valueOperations != null && redisTemplate.hasKey(key)) {: _6 o. H3 P7 n" i5 c/ v
                final Object object = valueOperations.get(key);
    : ~# L$ U! c( [# A, q" j2 ?            if (null != object) {6 [. i! ]. [$ Q9 O9 s0 e) V
                    LOGGER.info("--getString--object not empty");
    0 A* }2 @# I- f& P                value = object.toString();
    . p) |7 M# j5 L3 ~# c- |; q& Z) U            } else {+ k4 J3 h. [; }% a
                    LOGGER.info("--getString--object empty");
    / |5 n( j" k! ~# t) A9 I            }: P0 l& _8 T8 ?) S6 b
            }" v8 V* Y0 C+ o0 d. T$ m
            return value;
    8 @' d$ `5 ^# M7 p/ ]    }% [; [) c  Q! I7 r7 X) `; B
    2.2:在redis中实现时间控制
    - l/ x# M. \" r* ]: ^& X0 q5 f" r! _& n1 @+ V
    2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
    2 C: X# L4 `# [4 ~. R( s5 J
      W. N# k, Z& F# ]( Z) A4 ~import com.youjia.orders.redis.RedisManager;; }' E# D5 a% B
    import org.junit.Test;
    7 \) S& r3 L) \/ l7 d0 e# z4 }import org.springframework.beans.factory.annotation.Autowired;+ b& u# v. e7 b
    1 T1 V) [) s4 `0 E% [7 C& z6 @
    import java.util.Objects;
    1 v2 E' M/ I+ ^5 h7 f8 p: h5 C3 K
    /**
    " q( S% N* |! \: l4 _ * @Auther: Yrion0 N( E4 N# m1 L
    * @Date: 2019-01-11 23:36
    ) P# e. ?6 Y; a4 C# S, i5 d) I */! i) K- I( h: y. n# ~0 b- {, A
    * F2 ~1 c1 {4 u; `
    public class RedisTest extends OrderProviderApplicationTests {  g& q# E$ x, P3 D; \: P

    9 j! L& M/ J" G* k" h& \    @Autowired/ J! N  J$ n/ V7 ?6 k; w: Z5 c4 r
        private RedisManager redisManager;
    / C; y/ H8 z2 l  B
    3 O; }8 c; ?; Y7 e7 `    @Test' l) a- z5 [- c$ M
        public void test() {
    3 X- v9 ?0 ~2 I" a" z5 p  x1 `% ?& |6 {+ k5 k        controlTime("10000001", 10L);
    0 ?  v# b9 ^% d" U. ?, g3 ~3 E    }
    % `3 {1 @5 I6 l/ b. e
    5 x. K' b- }4 F    public void controlTime(String requestId, Long timeOut) {3 b: ^, v3 @6 X) K0 {$ w* ^" w- z
    1 m! @' Q5 E% g# R8 ^4 t4 p7 n
            if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {' P* \. m; m( i( G# ?! Y) F: m
                return;4 H7 }8 |0 }' M4 f, H
            }
    : [3 y7 J% A4 L& L. X" w9 K        //something code$ o( J- z$ G% P3 C
            final String value = "value";2 g, c9 ]0 B' X' V& z7 w' n: t. O
            redisManager.setObject(requestId, value, timeOut);8 {8 H1 i% N/ g8 i8 j( N
            final long startTime = System.currentTimeMillis();
    $ \: i5 ]% R( P# g+ x        System.out.println("开始控制时间");
    0 |& Z' T. l% ^5 y  d        //start
    " S' x! q2 s5 _        for (; ; ) {( z) N) {- o: X* [4 ?
                if (Objects.isNull(redisManager.getObject(requestId))) {
    ! T+ D+ d& ~7 y                break;& R# x0 }' L0 K# q% L" o+ x
                }1 v/ _7 X9 ]8 X$ d- m9 t) m4 {4 L( y
            }
    ( \) e# H1 a1 o7 f% k0 b% s        final long endTime = System.currentTimeMillis();
    2 ~: c2 v9 Q8 N+ w/ l  R7 Z  f" b: E* V; }& X% l
            final long useTime = endTime - startTime;
    ! |8 L9 H- h$ _3 t5 D/ N0 Q6 ?. U+ e% K$ r' o
            System.out.println("一共耗费时间:" + useTime);
    % e( q$ R* w6 m5 m- X8 Q    }' e$ H7 k8 [+ S3 W$ X% F" q/ c
    }
    : \/ |5 X! ]1 zoutPut:
    3 W4 ^5 L+ I, A2 Q( I
    , C: B" `8 ]+ b: J6 d" A开始控制时间& h$ w3 ~7 P! }; K  i8 F
    一共耗费时间:10042
      c' p' G9 Q! `; Q0 Z5 ?, B三:总结/ `; r. `2 R( N: U$ j3 r

    . T% T6 w. l" s+ a9 v  j本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!4 N) T2 m4 b1 v0 i. q) x8 `' B
    ————————————————$ N: d- A2 a, f
    版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    . w2 `9 j8 |* t0 Y! y0 V5 `原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325
    * [# K' o/ `2 V; p" V& V) x. ?( [- W  b  N, L4 b$ h3 y
    + a" B( n8 o' M4 S8 ?0 f
    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-14 19:46 , Processed in 1.996409 second(s), 57 queries .

    回顶部