数学建模社区-数学中国

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

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

' l3 D' B. _1 M# C( \' A; ^9 KJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
( w( Z1 M  Q7 g+ b5 [% ^3 x$ O4 w( v" P8 z0 h' A
一:时间控制的几种方案
, p) R1 O- t8 |% S+ e; \7 L8 M6 p' v1 ?" ^* Z3 x' I7 w
1.1: 从线程方面解决
( |; q: X  X) f* V/ h
2 A" r" _& G, U最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
" A$ E% Z2 v- W- O2 P- s, h/ m! n" c1 U5 o) J
1.2:使用Timer! A! k3 b  O; G- }4 ^

9 p7 |7 [" x- U# }/ x7 s查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:
  s6 F5 ^, X# D% E2 K5 b2 m; c8 }# [  i
public class TimmerTest {) t3 ~2 U3 X; r' B
   /**3 d0 T  ^9 e4 \" a
     * 测试方法; }5 ?( t4 _' b
     */  g; c4 N4 U4 ]6 ?+ n3 J5 ^
    public void test() {
) ^2 F( G2 m- e$ q0 \( T- b        Timer timer = new Timer();0 k9 o; O* X1 @0 f, y) y0 ~
        timer.schedule(new MyTask(), 800);
% r! Z2 e5 y7 s8 {: X$ d8 \    }+ g* ~/ F, t* q9 _9 ~" u! m' @

) }" D  D- `2 f! Y% o2 V0 l9 X$ |    public class MyTask extends TimerTask {( g1 D" p/ z: [* ?/ H

6 S* O+ u: r9 }7 Y0 t: `; _0 e        /**
0 F1 ^* H8 G" U( L  ^; H% g         * 运行方法
3 E$ }, P2 t7 N; \- D         */( E$ w/ S+ g$ A% S4 ?1 U# t) g
        @Override
9 ^9 w7 z2 X3 q* C' E; X8 N        public void run() {% c' c- r4 x. M& V
            System.out.println("输出");
) x, @$ u; v2 I% M- O        }
6 b! q  M4 A+ d( D! F    }" d6 l' ^& E2 ^9 b$ r$ r; [, q1 Q
}6 |4 I8 I- c1 c, A) ~- {! ]7 l
这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
- b0 G+ A  s# M$ K. B
5 ~( I7 f! Q5 _& v1.3:redis延时0 U0 S" z7 w$ ~& r+ c

' C& F% q- Y) x2 |, \, d在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:& |1 G$ z* Z9 o: }* y# c' v# D

: y& v8 `  d( x2 e9 @# M# R( p$ _! l7 C1 X% I) t! I

- M, H/ a9 o5 F2 q# I% |% s+ o- ?通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
" H/ O9 k6 g1 H" e* p+ R! M# C( ]8 Z4 w0 j) ^
1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制& F7 D6 m: X0 s" I

" Q+ a9 |1 h+ K7 j, \) V: N2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
1 c$ w# X+ X* i' m6 H% b
% H  u, [  \5 e. A3:简单,真正的代码实现起来只有很少,下面会给出代码示范。
) C( A8 p" B- w1 Q0 A, d
% @" P4 h- O3 H7 M) Y* h, R; l5 ?0 H二:redis& c7 K9 g, L0 P( h! l6 O0 x
* }) d1 R& o2 Z5 s8 Q" s) V
2.1:maven中引入redis) \( _4 r$ _. b+ F7 l. `2 h

5 S' C2 M: S' B+ T# L引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。# B: ^4 f+ N9 Y- w" N
8 q$ N5 c  y1 J0 |, q! o" U
<dependency>
, [+ H, t3 J/ A% U  <groupId>org.springframework.boot</groupId>: W) M( p3 S+ T5 i& [/ C
    <artifactId>spring-boot-starter-data-redis</artifactId>
6 E2 B# J& H) Q      <exclusions>& {# v9 j. s, A
        <exclusion>
3 t7 a7 n( t( b# s          <groupId>io.lettuce</groupId>
9 B7 k- J) _! \- `  b9 ~, w9 C1 ?          <artifactId>lettuce-core</artifactId>
1 L: r: d5 Q* J- i  ~        </exclusion>
( p+ o( S- d3 P3 ^6 j, X. j; ?      </exclusions>
" p" D) b/ Q" `( C</dependency>, E* w- v. F- t* _0 o( w2 G
<dependency>
: t, y4 x" [; {  <groupId>redis.clients</groupId>* K! M' B  [9 n' D
  <artifactId>jedis</artifactId>
% @7 e2 M! {/ ~' I</dependency>
4 \4 o7 l2 ]7 {& l% U5 K( B2.2: 在springboot中配置redis
" O2 w" v, `0 A7 v* e2 r6 C8 `2 V
import org.springframework.beans.factory.annotation.Autowired;
7 G$ W6 Z  J$ F% L* D( s) Wimport org.springframework.context.annotation.Bean;/ D1 [0 z- p% A
import org.springframework.context.annotation.Configuration;/ D# }- g! x; I; I0 a
import org.springframework.data.redis.core.RedisTemplate;
! e) `: f+ C5 f# K, Z- s4 X0 {import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
- k( Y/ R; A$ K4 jimport org.springframework.data.redis.serializer.StringRedisSerializer;0 V2 N" g: r; h9 y  C8 f% r) m
9 P, |' g5 d$ c$ ]  q0 ]! C
@Configuration  A  U) \/ O* ?* u( `% [
public class RedisConfig {; R; J/ s" Z3 T5 K- \
. u% A1 u7 J6 I5 b0 ?; W
    @Autowired3 S) P/ K0 {" o; |1 y/ v
    private RedisTemplate redisTemplate;
6 [8 [1 e3 }& H* v$ D$ h9 ~9 S& h) O3 e0 N$ K1 s, o+ a
    /**7 X( ?" t6 b7 F: o" a
     * redisTemplate实例化9 Q& g6 V  ?: H
     *
* }: @3 @# V. N% }) e/ h$ N: @, R# W     * @return
0 }) Z2 }. x0 g     */8 Y2 N. k& J, H4 q. }- l
    @Bean( k9 S& b. Y/ s5 y4 c9 B
    public RedisTemplate redisTemplateInit() {8 ]* ?' D( p6 D% N
        //设置序列化Key的实例化对象/ W* v& P9 w; b2 \9 W0 W& m. r
        redisTemplate.setKeySerializer(new StringRedisSerializer());
) }) B: S/ g' |: y  A' b        //设置序列化Value的实例化对象- S. [6 I. s. [% q4 E) O  ^' Y; B: i+ q
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());/ H- t$ r: c$ J3 u1 k% l" @
        return redisTemplate;: L& g% E$ |' `* O! [$ A' r; B3 p' Z
    }
7 E# X1 w9 @# X' B% Y$ e# K
* L$ z: P8 e& f}
  ]+ s, X+ _' V$ y2.2:redisTemplate模板工具类
# Y) m. d- g" j$ \& C$ |/ \; q: h5 i/ k2 O
@Component
" ?3 G$ S1 h6 ~, ]$ l7 V. p4 qpublic class RedisManager {
" D' Z8 K3 ?* t5 J! l3 s* q5 \) E* s' ^% j( c$ t$ ?  B& i
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);
! E# B& n6 t( q4 h2 S* Y8 L  G9 q0 v4 D6 Q
    @Autowired
( N) `: H- p# h5 ?# L* K. `. ?    private RedisTemplate redisTemplate;
/ c* p5 @0 e8 m
6 V% \. L2 o/ H' V1 r$ @$ Z: D    /**" _( V6 a$ }! H8 v; @
     * 设置对象
* b9 A3 Z! ]% o: V0 I     *
( D! b( H" T" _1 f/ s* f# U     * @param key key8 V  u' h% Q# r9 ^; z
     * @param value value值1 B; J9 G3 V5 S
     * @param <T> 返回值泛型
5 ~: t; H2 ]& d  z     * @return 正确的值:<T> 错误的值:null
& V. J: v# h( o1 ]  [6 m" j% _8 F     */6 o; h$ s3 ~0 d& H
    @SuppressWarnings("unchecked")( C- i1 A& a* e- _, a
    public <T> ValueOperations<String, T> setObject(final String key, final T value) {# p& Z+ ~- L, g% ~6 N: A
        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
  m. `( ~6 A8 h( d* _        operation.set(key, value);% N" i! H2 a8 v! Y9 |
        return operation;) O7 [' W% }5 R+ ~: n
    }" b! P3 K' G& r/ q2 Q. A: e
; E5 ?% Y0 E. F8 h% r# S
    /**! M: i8 V5 U( u$ @" u7 c
     * 设置对象及失效时间 (单位:秒)* z5 u1 m( f+ c4 E
     *6 \# O$ I+ m- }( Y' X, ?" B
     * @param key key
, h! s4 h  s8 \     * @param value value值
0 ?& o& \/ y* \% g( s     * @param <T> 返回值泛型# D( @- e! ]$ y5 \+ A
     * @param time 秒值
% w% z5 d; Y, A+ L) z2 O1 U     * @return 正确的值:<T> 错误的值:null: _; i4 G5 U( w& b7 y& O+ y% G
     */& W+ b0 J# ]# K3 l, ]
    @SuppressWarnings("unchecked")
/ Z0 I' Z* ^) {1 L# Q# E1 Z' x    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {$ r4 I0 U3 ]9 R# J6 Q
        final ValueOperations<String, T> operation = redisTemplate.opsForValue();2 o" G8 d* [  E' ?4 x; g/ k
        operation.set(key, value, time, TimeUnit.SECONDS);2 D( D$ R5 k& a8 W) i
        return operation;
: T7 X0 E2 z- g! K' ^0 V! }! d    }, {: T2 Y/ n5 t" x' H

2 Q5 \( z6 B0 L+ ^* x3 R$ w' y+ z9 N; @) i
    /**
) K+ L$ F" b  W; R% A& P     * 设置对象及失效时间(单位:毫秒)! J+ i% u' G& N1 [
     *8 Q, r- E! L4 ?
     * @param key key; q0 x5 b+ h6 y6 `2 O
     * @param value value值* U1 A; {: C8 u
     * @param <T> 返回值泛型
5 p/ c1 M) n3 U     * @param time 秒值
/ y* P: D) O: P+ a     * @return 正确的值:<T> 错误的值:null4 ]- @: j/ c* Q
     */
& V0 v$ s* O3 g; ^& Y/ c2 q    @SuppressWarnings("unchecked")
6 C$ o2 G/ T1 j3 u8 s& |& ~6 w* P    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
& G3 \6 ^0 S0 `% ?% R' e        final ValueOperations<String, T> operation = redisTemplate.opsForValue();) D+ W# s1 f+ O6 J) b# R( Y. b" p
        operation.set(key, value, time, TimeUnit.MILLISECONDS);0 l3 B! i- {! x/ H8 t/ @8 S) b
        return operation;4 \2 U/ k" J7 w* o
    }
3 n4 K2 s1 Z! v0 t2 T4 F' M3 q  Q) Z7 y) @- s4 W  \
    /**. F8 j7 U1 e% G# a
     * 获取对象
( |3 h; g+ z! U  G2 q     *
9 r& o7 Y  D; W, p. \0 K9 E3 \! B     * @param key 键
% b% Y& \( ]: L& n     * @return 正确的值:Object值对象<br>
$ z6 h/ }( [( Q* L     * 错误的值:null# U- w  l; Y. t9 R! O) `" \
     */
4 H# l+ B( K3 d+ x$ m% `    @SuppressWarnings("unchecked")- K- g+ V# P( H' C3 c
    public Object getObject(final String key) {
' P% q9 a3 w% G        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
2 v% u3 S4 b) n' ~        if (valueOperations == null || !redisTemplate.hasKey(key)) {; [. i! ?9 n0 I
            return null;5 x$ m2 G& L' A  [- P! k+ E
        }
' N6 N" M" Z) H& v& h" i        final Object object = valueOperations.get(key);; q8 e' @2 ?& f: M- G% b5 g
        return object;
/ K9 M, q6 ]* Y) i7 r* q" m" A    }
2 s; |/ A* ^( S9 [4 ]: B! {
4 n" x& W+ q+ [) N    /**
" y; `" I9 Q1 E6 U# g6 o# P     * 从缓存中获取string值" I/ {- o& \' y0 A' A! j; X2 F
     *. l: ~( Q9 \" s6 V7 A$ h
     * @param key& J7 H/ n0 P5 T2 n
     * @return*/7 x# ^6 ?; E' b" N( s
    @SuppressWarnings("unchecked")$ w' r5 S& l, @8 t
    public String getString(final String key) {. T6 Q1 T3 ~! G  F
        String value = "";1 e$ U$ s% h! A
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
) u3 H9 a8 S# L        if (valueOperations != null && redisTemplate.hasKey(key)) {  i. T/ s+ n% `, t, _2 E& W/ U
            final Object object = valueOperations.get(key);4 [; C, _. J* `7 E2 m* E
            if (null != object) {
. ]8 n/ Z/ r) M$ n* H0 r7 r+ S0 t                LOGGER.info("--getString--object not empty");
4 Y  S# [( P/ |. U/ k0 n' r2 V                value = object.toString();
" G! a5 J2 L' F) Y            } else {' d# c7 a* i" p* A
                LOGGER.info("--getString--object empty");
( a$ Z& I- R8 |            }$ R) W" e( M/ ?+ ^
        }# P$ b0 k3 s+ }5 _
        return value;% t+ i- g5 n+ R, L% q3 @" }/ R
    }
; ?8 ?6 U, q( b  }3 c" F0 S- ]2.2:在redis中实现时间控制6 @. }+ z; T" \/ U& Z4 t5 v" Z

/ E- U. M4 \4 H# ^) x- \2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
; d, F/ F# H2 |) {" G# A" l2 H: C. v
% c& w, F* h8 k, P  Yimport com.youjia.orders.redis.RedisManager;) N0 T3 w1 E$ p# S3 q; A
import org.junit.Test;
( j9 z: w2 M) p- `import org.springframework.beans.factory.annotation.Autowired;
6 v. @1 _7 {: Z
, x/ E2 `$ ?! x# `import java.util.Objects;
" V* j0 ?, x+ g/ S6 v5 C- N( n- ?" J, a+ W3 F
/**
7 l$ b3 M- ?( p * @Auther: Yrion, K3 R: o# I4 V  R2 Y# s
* @Date: 2019-01-11 23:364 I' D4 a4 p6 q% _3 ^5 r2 F9 v
*/
; s* K" ^/ y! D* g" _) u# a+ g" f2 Q8 |4 e* \  r0 H4 z) \: w
public class RedisTest extends OrderProviderApplicationTests {$ x$ p- V7 Z" O" B4 E
7 }0 q1 H, @0 k4 p6 [* o5 p, D
    @Autowired
0 M% E  [6 a2 z9 z* F2 D8 h    private RedisManager redisManager;. W! k! n0 G9 }: M) V5 X

7 k3 k/ K% P* T6 j& ~    @Test
9 _2 c, {( ]" F9 q: f  X5 w    public void test() {- W: P/ u& _' I$ M$ B0 y* D6 S
        controlTime("10000001", 10L);
7 n0 O! o$ P; t. Y! V. _7 s1 l    }
' l6 a* K; K' f' x- a9 ]
9 D% T( l% W: B6 _- Y, L# M    public void controlTime(String requestId, Long timeOut) {
* p! t, L& ~/ }( J9 P7 _0 E4 L/ D. e8 h5 v  k
        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {9 `" y: X6 u$ D; d3 ~, s" R9 _
            return;
( `) }6 V. u1 V/ |% f5 @/ X        }
, @  k3 h( `% t3 v4 W# k        //something code) ~- \) T8 E/ K  A* x
        final String value = "value";  r6 e; V) K% r! C  o5 U# \& ?0 y
        redisManager.setObject(requestId, value, timeOut);
$ E& _5 F' j; \  G; b9 `4 a& q        final long startTime = System.currentTimeMillis();
. X' |/ p  L4 r! U( Z        System.out.println("开始控制时间");  T0 }& ?: M  F  a9 a, R; h1 w
        //start
+ n8 H( ?; I2 \% a4 c% }- ]* G& U        for (; ; ) {
! j1 a' l' {- U% T% r, R- m, i            if (Objects.isNull(redisManager.getObject(requestId))) {& O& z9 {9 h0 P4 R
                break;' i) n; f! ~) `9 \3 S8 ]8 k
            }
/ n, V' `- n; M5 g# h/ B        }* {, F; ]1 R0 t, i/ L
        final long endTime = System.currentTimeMillis();! p4 R& |* t+ ?1 P4 R. V/ T( g( u

+ `+ S8 q4 c, ^        final long useTime = endTime - startTime;
# R6 E9 M7 f  f! Y( u
* r% E( K; w5 w/ Y0 \        System.out.println("一共耗费时间:" + useTime);5 ~+ L( k2 F, g
    }: @5 ~3 F  ?. P, t( ~
}
7 g: o( t2 Y1 G7 S3 i. D  }" Z# youtPut:
% F" N( l; _+ b* r6 [0 t$ Q1 Q+ k8 w; T. u. O8 m  x
开始控制时间0 b+ i1 `: f7 c' m
一共耗费时间:10042$ ?4 t2 Z1 S/ b) |2 m( F: c6 W# v# z. F
三:总结$ `4 |) m" p8 X% U: U: D, |" Z
6 a+ F# Z9 q4 X' j3 u) V5 z
本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!) T' I% S* g' k4 I! D
————————————————& d, N- j9 R) C- P+ M& ]
版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
9 q; R+ D# `) Q$ p% ?' S: l  ~原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325$ V5 p* `* n- S, C
+ M) a  R- ]) |- J8 ^; s# g9 W6 }

" t. b2 Z$ v* M+ u% |
作者: 1661888967    时间: 2020-5-6 09:57
学习一下,感谢分享" H' s9 _4 B- o. Y2 ~





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