数学建模社区-数学中国

标题: Java如何优雅的实现时间控制 [打印本页]

作者: 杨利霞    时间: 2020-5-3 16:03
标题: Java如何优雅的实现时间控制

2 `! @- r( ~! }& \2 a) ]) GJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。0 H$ H* y* X2 `* `+ f% e4 S6 L! x
! U5 C  y4 u) P" H8 h4 k- \' T3 u9 j
一:时间控制的几种方案
: r8 P- M+ d" A9 W0 l, }* b# q& J) @& X/ N8 L
1.1: 从线程方面解决
1 p+ k% t* V& e$ P! z- @
! v- @" @- }" u) D3 F/ B6 M最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
! }* N( z+ q8 z/ \; L( Z. U* U( B
. n# o% p, R. N- g3 o1.2:使用Timer
8 \+ J& C3 f) ?7 n
2 y) t  \. G8 y- f& g/ W( A查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
6 l% b. u# x9 u' W9 E2 ^5 |4 |; r
public class TimmerTest {# s, @/ k6 C% d- h( L, T
   /**
* d9 D- z1 e: h     * 测试方法
* t2 D7 i+ t  u     */& B' g. ?2 ]7 m6 w( X
    public void test() {
6 T' h2 L8 |" K! v        Timer timer = new Timer();
: \7 U) Y5 }- i4 v- u- z& S+ |4 n' o0 R        timer.schedule(new MyTask(), 800);
& u; r, F1 {# [* w    }4 `5 `* T5 x/ _: X. @+ c1 c( j- j

( @/ J5 D) z* \3 k0 [# l3 p    public class MyTask extends TimerTask {
2 E/ p) x  o0 a. X4 f# P# b* j( W4 Z) U$ _
        /**! s7 Q: {4 f6 c+ H3 R
         * 运行方法
& l8 g) S, N9 O6 y) Z. A, q! C# _         */
4 D/ J! a: \' d* ~: N$ o! `: s" P        @Override
$ T$ f* T0 U' t" l* R        public void run() {
5 c0 `* ~/ g, s8 j+ o- D; \$ }. U8 h            System.out.println("输出");
2 z0 o' r! e* F) U/ N+ J        }
1 X0 P( X4 {1 X    }
% M& H- \/ f4 _- f  u( f}
/ O8 K0 T# e! ?' {7 G' g这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
6 B% A$ k: _9 R0 }1 B+ ?
( D/ j9 E+ G/ a" u( U/ e1.3:redis延时2 o3 r- D1 \, k

& p& F1 r7 g+ x0 [2 R在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:: K/ Z# p2 w  `% }1 J9 _
& U% \0 Q2 |8 w2 }3 ~6 Q5 A
8 q( }' H6 [; V' h
- L/ J; Q/ v8 Y2 ^( n$ _
通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:5 O/ w. z! V) w7 s4 Q9 |
3 R, m7 s# T0 o/ H+ D) U' Z
1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制1 D* Q2 N9 `, ?+ Z. V6 W

1 I+ z5 H5 W6 Z- A3 b+ H2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现4 f* a1 O" @- W$ F' _

- e# ~) z1 P# }3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
8 [! ^1 Y5 [! w2 i1 ~
! P( c2 Z) G4 T# F! Z/ E/ n! N% i5 [二:redis7 ^- i' B  m  t8 H5 Y

  k8 g) W# w; @9 m2 y2.1:maven中引入redis
6 L# R- y  O+ m# T; f; X) R; ^& D7 \9 N# N2 `1 G
引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。% I, ~! f4 }+ y! Z

& f/ t  \& g1 C6 R7 \<dependency>2 r- Z3 M* I: Q6 @. E1 X: H
  <groupId>org.springframework.boot</groupId>
2 U7 P* e& ?1 E; @9 h. h" V    <artifactId>spring-boot-starter-data-redis</artifactId>
- s# Q* }4 f* u      <exclusions>- R2 D( V5 y: Q* P4 n
        <exclusion>
/ L$ S  ^# |; N2 m3 n4 w$ p& C          <groupId>io.lettuce</groupId>! O, C: Q6 z# c$ D' l
          <artifactId>lettuce-core</artifactId>
; @& b& d1 N( l. c' a* G+ }& K# C! A        </exclusion>
% ?( ], r+ {5 n4 `0 r& Z* F5 i      </exclusions>
) d! e) C- D' E</dependency>
' K2 f4 V  _! b* M- l3 T  T# h<dependency>
0 m7 L" O8 g% n  <groupId>redis.clients</groupId>/ Q: l  W7 u) P
  <artifactId>jedis</artifactId>
7 H. U, h! t- o* M# C8 [2 U</dependency>) H" l; t& S8 q# \- t% b3 q
2.2: 在springboot中配置redis
! U" {0 {  K3 ?1 c: B
9 s. Z2 J. \: L* B' G7 ^4 Timport org.springframework.beans.factory.annotation.Autowired;$ r& U( g  h! H* d3 t. `
import org.springframework.context.annotation.Bean;
+ y) B+ y1 X/ S$ pimport org.springframework.context.annotation.Configuration;
- m  Q* Q) ?  z& Mimport org.springframework.data.redis.core.RedisTemplate;* `+ l+ A5 W+ e( O- j. a7 ?
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;5 s, i$ \5 v1 h; p
import org.springframework.data.redis.serializer.StringRedisSerializer;
( n3 c+ D6 B, V. j; g+ M: c1 m* F; ~6 a; c& f; g
@Configuration
/ R6 x8 |& i% y& ppublic class RedisConfig {
9 ^% `3 {' Z1 k  _# R8 w3 Z( y: l# A: v
    @Autowired3 }7 z4 `6 V1 M9 A" E. U( d
    private RedisTemplate redisTemplate;
- X" G/ T% a6 C
: j4 a5 o. V2 ]8 o3 s3 w7 K    /**8 y+ z3 \1 l6 k$ r# |; ?4 a* d
     * redisTemplate实例化
1 |8 I: `6 M( D' J     *
: q7 F) l) h/ E( e; T     * @return! A. Q1 X" ~6 q' H) \8 n4 s
     */  K0 w( F3 u! u. U8 W( i
    @Bean
/ y) i: ^7 J& D1 r& M" ?1 m+ W    public RedisTemplate redisTemplateInit() {
* T- U# |/ a( D4 d6 n) Q        //设置序列化Key的实例化对象
+ o8 u1 w) F# S8 ]        redisTemplate.setKeySerializer(new StringRedisSerializer());2 s# h6 _0 `% h7 G
        //设置序列化Value的实例化对象0 Y, g' l8 G2 u' \% G
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());: E" R/ l" p# r: j
        return redisTemplate;
4 f) c/ W8 g8 c4 ]7 Z9 R    }. o% E, l! j4 s0 `0 ?. z) @

: e. r. V0 x+ A, t}
4 ?2 L7 E& N+ W2.2:redisTemplate模板工具类+ [2 [: ~! F7 B% `/ A
! `- n/ q1 P# Z2 @7 C: S4 Z; h
@Component1 _0 A: c5 G7 h( Y  G
public class RedisManager {
. G/ _( P# L/ \5 x* U
/ r/ z0 u9 ]8 b9 r    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
3 {. q/ [: t) C9 z
. Z( ~) q% W/ |; F. K# O# Y    @Autowired
. u( M6 Q3 d, C3 j* O    private RedisTemplate redisTemplate;
! K* X3 F* k3 C& k9 q: O' S, p( |; t4 r6 D, O" R. [1 K' a' i
    /**
) u# E, B, z$ X2 l) n. M     * 设置对象3 o/ w% R% h( ]4 C" T; _
     *( C0 l5 y5 D+ V6 F
     * @param key key
2 P( C0 b! K2 s. _6 D! a     * @param value value值1 p9 o1 {9 J5 ~- p
     * @param <T> 返回值泛型
( S, H/ \1 m+ x. U     * @return 正确的值:<T> 错误的值:null) ~2 h3 _' z6 v5 w2 }
     */
8 z+ t2 G5 V8 X' X9 O, j    @SuppressWarnings("unchecked")
# n; P4 g* c1 }: W- z- M# e    public <T> ValueOperations<String, T> setObject(final String key, final T value) {' B2 e6 _+ |* `) g
        final ValueOperations<String, T> operation = redisTemplate.opsForValue();! [0 T4 Z1 [& G: h3 l: a) t) T, K
        operation.set(key, value);5 `" |- T* y+ M. e7 q
        return operation;% j+ |8 g' |7 @5 C$ F- Q, w# L( Z
    }
2 V4 o2 j+ y  ?' y
0 E: p2 K% c+ V1 F/ Q+ a    /**# y. o& Z. ]2 j4 v# I
     * 设置对象及失效时间 (单位:秒)
4 I- E' l. A! d( b- R" p2 x     *) J' L8 u$ Y' c: f& J; I
     * @param key key7 U! J3 k% B/ [8 h2 u+ m+ P9 G
     * @param value value值
  E* E# n1 h. E3 L     * @param <T> 返回值泛型& }: M  `" b; [  m
     * @param time 秒值
- p3 z! g6 O: {" ^     * @return 正确的值:<T> 错误的值:null( `. a) R$ N7 j6 W# I
     */1 B$ M% b& U* f, o% r
    @SuppressWarnings("unchecked")
$ C# r0 f# ^/ U1 b5 ^    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {
/ K. C3 P$ q7 F        final ValueOperations<String, T> operation = redisTemplate.opsForValue();5 P+ e% R; X) F/ p7 }9 p
        operation.set(key, value, time, TimeUnit.SECONDS);
5 V0 U: f3 u% c  B! W9 l( ?        return operation;* ~: w" V  f2 V# ?
    }+ K) ~( E8 X, ^6 K4 t

9 A8 b8 ?! ]: }2 p+ c
/ y0 ]7 _3 d% v+ r6 }0 v. M, q7 H7 F% Y    /**) w8 n% ]/ O4 P3 e7 R5 U
     * 设置对象及失效时间(单位:毫秒)
" W2 y# f/ X% L     *2 X- `" g1 R, D
     * @param key key. S# g# r) Q7 r3 v8 o+ Q' q
     * @param value value值2 p7 V# p  C, D) [7 `; V
     * @param <T> 返回值泛型
) u% g! O$ Z9 j     * @param time 秒值" o5 R2 ^4 x7 W  U
     * @return 正确的值:<T> 错误的值:null
0 g6 V" m9 `. J+ c( ~" t     */: Z8 h/ ]% }( h- z; [# _
    @SuppressWarnings("unchecked")
  }( l# x* ~3 _6 S+ S: R    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {6 A7 S9 q1 Y' A8 k  e; S
        final ValueOperations<String, T> operation = redisTemplate.opsForValue();/ L3 m$ z4 h+ h# G7 r' f+ @. i
        operation.set(key, value, time, TimeUnit.MILLISECONDS);5 v7 O8 C, O7 n5 ?
        return operation;# I' n9 z( [: I% O
    }
' ]  `1 ]' {& G+ x2 F. [3 ?5 D/ r" ?8 K% P, h( o) ]. w: l# e
    /**: B4 V' m5 c  o
     * 获取对象
3 s. x) w) V5 K9 {; r) c     *
" X1 ^0 A4 T" [  `) v- a0 k  g     * @param key 键0 x; a) |- I' ?, W' _
     * @return 正确的值:Object值对象<br>
+ L* P1 [* P7 p( c' C( u" [     * 错误的值:null6 S9 _3 |, k3 Y: E. n( k
     */
5 o, h+ c  f, k. N' k    @SuppressWarnings("unchecked")
' x5 @% C! o' u    public Object getObject(final String key) {' d6 L2 O0 E" s; |: l- h
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
* P0 q) `- ]/ y; D        if (valueOperations == null || !redisTemplate.hasKey(key)) {; J+ [& v4 g) o1 A# a, S
            return null;' i0 j4 [" ?% p' x
        }
, r$ h2 d% U( d, m% g) R; U        final Object object = valueOperations.get(key);
; w8 p4 j3 w' y2 i" j        return object;
& n0 _+ W  y/ H# T% ]  G    }- D6 c# d1 C7 f( U" E+ A( g

# a0 S7 p& A1 e% h    /**
% _' s" N" x/ u     * 从缓存中获取string值  B. z( q3 k" a& V/ }, A* `
     *8 b# v& a9 u5 D" t4 F
     * @param key9 V$ d. a5 v  @
     * @return*/3 F5 v2 v( s. v' a* }* i) s
    @SuppressWarnings("unchecked")& d' q8 z) \) [" C# [6 ^( E
    public String getString(final String key) {# x- Y- N' r. U* r6 b+ h, F
        String value = "";6 C  u4 C4 V4 m# U
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();3 g# o( V% M) I& z
        if (valueOperations != null && redisTemplate.hasKey(key)) {
$ P! U- n; b# g( ~; S            final Object object = valueOperations.get(key);
- l+ v0 }7 P$ j  y            if (null != object) {( S, n" e7 }5 Q& D: @
                LOGGER.info("--getString--object not empty");/ I/ e- D5 @1 R3 ]8 Z: {
                value = object.toString();# Q" x- G) V' C( Q1 S
            } else {5 T' ~0 N, D9 ?6 a" p$ w4 R
                LOGGER.info("--getString--object empty");
+ F" y: v# v1 b1 h, R            }8 B, h7 D( I( ^- ~
        }8 Q4 z; I) {% q+ T
        return value;( D0 `% {# Y8 S2 z, ^
    }
" ~$ Z  v5 l3 ?3 C2.2:在redis中实现时间控制7 A  j; i& b: b: ~+ {: R

; C! q5 l0 w3 K) D+ p2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
) `/ J0 B; n4 K
; L8 \& S; T! ^* Nimport com.youjia.orders.redis.RedisManager;
4 \) w- ]1 d+ J! O5 t3 G4 ~5 V- himport org.junit.Test;
1 i' p& P5 N& E" ~import org.springframework.beans.factory.annotation.Autowired;
% ?. ~1 Y9 ?* M3 _# X! d; g+ b
/ G0 E8 J/ ]7 m, Y/ {+ x8 b' g% x% _import java.util.Objects;
2 o0 |) K- k; K6 C( i# T
4 z! M8 N0 H! ]4 ]4 Z/**) N2 K9 M4 O; `4 @
* @Auther: Yrion
& n7 v1 C4 t& w3 q" C# ]/ ` * @Date: 2019-01-11 23:36
" c  p8 A4 e, [& O */
  r) D9 m; I+ T$ E* R) {9 p! Q( c7 c4 f
public class RedisTest extends OrderProviderApplicationTests {6 T& \8 @4 G8 E7 ^% k6 V. ]
5 {9 t. M( b, o: J, Z9 c" d
    @Autowired
6 z, V/ g6 p) U; Z: y    private RedisManager redisManager;
0 d: [# r6 e) C, u
! ]6 _2 A4 g8 Y- P& E5 G& y    @Test
$ s; I. d4 j3 n, x    public void test() {  @6 |+ S  @0 u2 K
        controlTime("10000001", 10L);
8 Y* y, k$ S4 i+ s" I$ Y% K# T    }
5 E7 t% o  N, g$ Y
0 X0 z4 c/ s; r4 ]- Q4 |    public void controlTime(String requestId, Long timeOut) {$ H$ u- G6 ]4 K& P

' b4 w9 u7 S5 Y! K7 [  S5 P        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {/ y; z7 w( t, H
            return;0 `) n) w" `, T2 w: [$ x) Y7 O) h0 P
        }0 @( F8 G* B# ^1 M
        //something code# q& n2 x) r( s9 S4 j0 k
        final String value = "value";
9 y4 p  L$ }: a+ H7 ?: h        redisManager.setObject(requestId, value, timeOut);( }8 o6 M0 E. j
        final long startTime = System.currentTimeMillis();
) a2 D8 a: g1 m: c" u  f5 G: @* g        System.out.println("开始控制时间");
) N! X# E. y9 Y3 p        //start
6 W& F& \8 f- u$ x6 j        for (; ; ) {
) o1 i3 o3 b. n9 E2 c2 U; M. b            if (Objects.isNull(redisManager.getObject(requestId))) {" b1 ^( N( b; a' j7 R; K; P
                break;$ ?7 t5 g4 q! p$ I
            }$ t/ w! m# b  u& |: E1 f
        }
- ~4 C! B& d" y0 v        final long endTime = System.currentTimeMillis();
8 X9 U" H, D( z# E# I. g6 c1 x' ^# d0 f9 U; U6 e
        final long useTime = endTime - startTime;. n; k9 p, U/ g  h

) `6 y/ {/ {) ]        System.out.println("一共耗费时间:" + useTime);
& @! E& q  {7 n: B8 D    }2 Z4 R# i# R- {; Z  m! B7 `6 D; A0 \
}
( v/ W2 z! T- ZoutPut:2 \% j4 _6 U. K% r: F- [

$ a# K! n4 p1 r3 T3 g开始控制时间
6 k" ~! g3 K0 i$ c# @一共耗费时间:100422 E# {% Q! W2 Q  l% o0 m$ h
三:总结1 K! [  l7 m0 ]

! ]! F7 r" P/ B% l( D9 K: q本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
* h4 X$ p9 N+ K" r————————————————
" {- S! j6 `; Q. l9 x/ B版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。% W2 `7 j" j6 B* N; k! k' y
原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325
8 z3 S) s/ N; z! A0 R: z1 t/ ?  z2 M8 k5 i) D4 ?7 f
/ E7 N* b4 R, Y, g- I8 O

作者: 1661888967    时间: 2020-5-6 09:57
学习一下,感谢分享
. v( g+ D% a& p8 M




欢迎光临 数学建模社区-数学中国 (http://www.madio.net/) Powered by Discuz! X2.5