数学建模社区-数学中国

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

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

5 a1 J4 t6 [4 i6 n1 v# \0 FJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。* h, G5 a5 Q4 j/ G# A
( y8 `6 k, G1 P6 l
一:时间控制的几种方案4 i6 [. ^8 p) o% t5 u# r

! Y( q0 ^0 }( o# U( _  E9 I, {1.1: 从线程方面解决, a  W. d$ |& p0 V2 @# _7 s/ V( M
7 f3 m5 I1 P% ^1 C7 P
最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。
: t) y$ Z9 p* a9 [+ V" T3 X, T+ z7 X5 v6 I( V: o
1.2:使用Timer0 Y5 S; i* ^3 Q9 y0 i

: d. o5 M. e1 y+ I* H( C# q# J查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:% h8 y& ^! e0 N% J1 U* d  h7 {1 G
7 x8 W  P# Q% v7 }# l# u6 b5 l
public class TimmerTest {
4 _* H4 y3 s, g9 G  |1 {; O   /**, g0 ~3 i- Y. C! \: W4 C
     * 测试方法% ]3 r+ B, H# y" M3 z# C
     */
8 ]4 b. K4 U& Q3 _, u4 @    public void test() {& Y  [; h6 {# _$ D
        Timer timer = new Timer();
+ S% w) n( B7 j2 @* k& P- f        timer.schedule(new MyTask(), 800);3 P+ x! `5 m( z% S0 k
    }
5 o8 ?4 J! n4 S2 o6 [
7 H1 y. x; G5 q5 }! I+ x    public class MyTask extends TimerTask {6 S; |) r4 W" l1 N" A- z5 {

7 L0 j/ T5 J( C/ a        /**
! F4 E5 @  ^7 q  S         * 运行方法6 _# r, Q3 C; U: ^& Q; w, Q
         */* v/ H  y+ R: K( h  p$ }! v0 b! X
        @Override6 z# E2 L( q: m1 Y! x3 S
        public void run() {" V/ z) w0 Q1 y% o# X% u: r: e; {
            System.out.println("输出");
' a1 u& `- D5 m7 P        }' ?' n- i. `" T6 r
    }' m3 P/ G" C) J* Y* i
}9 s' B0 N# f' [* I! Y3 |" {9 q
这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。
. I9 Y$ z+ {% c4 R4 V' u8 J/ C6 C* S3 Q4 ]+ z1 V
1.3:redis延时8 T) R. I: Q- v- O- m" U5 L/ A
, U  z" A# B7 F' R5 R* I$ V" a
在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:+ O' d# k/ a3 P
5 |0 ~2 n3 \( ^6 j
: S9 _7 @# H2 m& E% o

8 T& |2 n# L& L5 D通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
  k" x( r4 H! H5 w- @# G8 j! |. @7 b/ c6 J8 C" |! N
1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制/ `7 q# T2 B$ U& o* q1 _0 c; M" f

! Y! o* V0 O, e' W: F2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
7 s$ ~: }0 x) D$ _8 f3 `9 W7 X1 s8 L  b
: k' c% F5 S3 X7 \* \3:简单,真正的代码实现起来只有很少,下面会给出代码示范。, h; [+ b( n0 Y+ `* d; {
4 ~4 r* }5 `2 ~- T! y9 }  W3 i
二:redis
; U; A& n! ^6 Q* W
" |9 t" b4 e3 s9 e2.1:maven中引入redis
0 z1 L* L5 S* {6 x9 ?+ v/ \. k7 M
引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。3 ]4 d/ k1 S* _: y

0 v/ x) {( \4 Z<dependency>
- \8 y( e+ e/ I5 o  J! e  <groupId>org.springframework.boot</groupId>  E9 e3 L* a+ y
    <artifactId>spring-boot-starter-data-redis</artifactId>
! K: U+ g: j" h6 }+ D- M( {% _      <exclusions>
- J" D5 y( N, M" ]        <exclusion>
( }# V" q7 [8 h' b& b( S  n4 ]          <groupId>io.lettuce</groupId>
( N- ]5 E3 a9 D+ J+ {          <artifactId>lettuce-core</artifactId>1 A- z$ d# \4 ^
        </exclusion>
5 I: |' I4 T' C8 `5 y' Y. n" U      </exclusions>
, m$ g: {* W/ X</dependency>
+ q( F- w/ a9 Y  X<dependency>
; j% t( d! y! C5 N+ [- V% F  V  <groupId>redis.clients</groupId>
5 }. q/ ^% N$ @5 K5 @  <artifactId>jedis</artifactId>
+ S  \5 Q2 }0 J9 K</dependency>8 E3 X: T# `* H4 L9 |5 d9 q7 E
2.2: 在springboot中配置redis
9 R- R1 @; ]- D" M: H9 U# H5 w$ f
& m! i0 ^* ]& k3 E: I0 y; oimport org.springframework.beans.factory.annotation.Autowired;
2 v) C2 T( S. `import org.springframework.context.annotation.Bean;- v: t* E7 v% H1 T% V6 ~6 W) I
import org.springframework.context.annotation.Configuration;
/ l+ s  f( _( Kimport org.springframework.data.redis.core.RedisTemplate;5 N0 U; Z$ _. M4 V7 }  H$ O0 W
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
; G" D1 k3 o& X" t( z6 I5 [import org.springframework.data.redis.serializer.StringRedisSerializer;
7 H- d, j# g$ {9 k; L6 ]+ u
6 z/ K  a5 t$ V@Configuration( W: `( \; ?3 A! w/ I( o8 W
public class RedisConfig {% {: I' ?1 n) T/ u. @
- T6 x) ^; e! K
    @Autowired4 a% _8 r3 Z3 j: {9 n2 [) n
    private RedisTemplate redisTemplate;
1 k* b3 W$ L! f' F* K; r
" _" Y8 R9 j# V    /**
$ H7 D3 {1 t# N8 C9 x     * redisTemplate实例化
6 s( q* e9 |5 g- z     *: H8 U/ d! a6 L; ?
     * @return
6 v* {6 O6 r. j# |& B     */9 h3 [# k1 K" q# l  j7 c
    @Bean
' T' v! t, H* u1 ?" @! _    public RedisTemplate redisTemplateInit() {! p; I- y9 M: c, E
        //设置序列化Key的实例化对象1 c$ v8 D( i$ d0 R1 d& m- w: r
        redisTemplate.setKeySerializer(new StringRedisSerializer());
8 _% ]! n1 _( n- m$ X        //设置序列化Value的实例化对象- K7 W% {3 t2 F. \
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
& b8 ~9 V( i6 V" c        return redisTemplate;. S3 z9 N5 R+ t5 m, T  |3 _9 [
    }1 u. s0 G& m4 ]# H

& ?8 S2 p, [% a6 D% v  {# r}. p3 q' ]! n5 e: J% P' X, X% \6 ^
2.2:redisTemplate模板工具类
! m+ e. o* Y  M7 t6 o$ j
9 x2 M1 U& J: G@Component, r( b. a* O/ ?
public class RedisManager {0 V2 \# P: f! D, q) n" ]" C

7 F/ m$ G* L: F, f$ n0 `    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);. U( C* o1 C/ u

8 g: I9 t: I  i( I/ h    @Autowired
/ E; `2 S( |8 _- J9 n( f& {    private RedisTemplate redisTemplate;, u! @" J; K* V* r) R7 a' p

6 T" ]3 m5 z" q% @# A    /**
: V: W/ N7 B1 Q$ l: B: f/ m: i     * 设置对象7 t' F6 M; _$ l2 k5 `
     *
; T  B% R$ S2 ]1 }. O% C0 `9 N  J     * @param key key: u9 @% v0 q8 b0 ?4 c
     * @param value value值
% T- a+ |  M, h/ q$ A     * @param <T> 返回值泛型
: S7 P! R+ `/ H, ^8 }, w! c9 e     * @return 正确的值:<T> 错误的值:null
9 w! a+ Z& Y/ t     */6 Z  A/ ]6 `* V' L$ h- s
    @SuppressWarnings("unchecked")3 @  |5 X$ g6 c# d
    public <T> ValueOperations<String, T> setObject(final String key, final T value) {
1 M( Y( ]; ^$ w        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
, H% w" S" M5 k% i9 o% n/ i( Q5 G* S        operation.set(key, value);
! g2 B$ I7 k2 H" X' H! g% c        return operation;" o% `9 W! k; _1 _- N: a  p# w
    }
0 g/ ]: s. C( V' M7 Q0 `, j! Q/ s& [# H9 O
    /**7 ?+ C. r6 A6 ?1 v7 g* m
     * 设置对象及失效时间 (单位:秒)
$ S  n3 W5 X7 b, J0 f& `8 L9 v% G     *4 ?' K; l( t4 X3 B. ^& |/ x
     * @param key key) b& l3 r9 c/ M, t* a4 H
     * @param value value值
9 ^) d% v9 o6 V, E" T     * @param <T> 返回值泛型0 q' Z1 D2 `+ g' ^, t
     * @param time 秒值  n9 |/ d# K: T2 Y) h
     * @return 正确的值:<T> 错误的值:null% x- z+ O: I# [8 |& F4 |
     */2 h' r2 ~2 h% Q7 W6 q; z  K& a
    @SuppressWarnings("unchecked"); [& N( @+ b# ~8 J+ ]% y
    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {* D, |- X) t+ N; O3 M7 E
        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
. b0 Q+ T) e& L; m+ L; s% a, V& ~        operation.set(key, value, time, TimeUnit.SECONDS);
2 a) S% h: I' Q, [1 l) i  ]- Q% T        return operation;* D3 q+ Q  x6 @0 Y
    }  M% @3 V; ]3 ?& j
5 ]0 L1 V6 [$ t: N

5 `9 `4 V* l. v    /**
( ~" L3 Y3 i1 X) K2 f0 z* d     * 设置对象及失效时间(单位:毫秒)8 h$ K" Q# G7 z& Y
     *: Q( \! p/ \7 v5 w" z+ V$ J
     * @param key key
4 o8 k, a8 p. w* q- z3 X4 K/ a     * @param value value值
; v/ D+ e" C. V( f6 k     * @param <T> 返回值泛型
4 \$ {* M3 ~0 h     * @param time 秒值# p! q; Y+ l/ j9 v" k8 _+ L! W
     * @return 正确的值:<T> 错误的值:null$ ~+ n/ F! E" X) x
     */2 y4 F0 j( M& v+ h% i
    @SuppressWarnings("unchecked")
6 }/ y. \; o* u9 c8 Q9 e    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
0 ~; k/ Y$ V2 ]: T; e* ]+ F        final ValueOperations<String, T> operation = redisTemplate.opsForValue();+ j% b, b& d. J6 r. J( A
        operation.set(key, value, time, TimeUnit.MILLISECONDS);
9 G3 h! \$ d$ @& z- m; ^. \; ?( P        return operation;
' O6 j+ s+ ^& {* m    }
3 U, w0 z" @4 `2 x
; i- }. e* O$ m" \5 m    /**8 Y- r4 o. d3 H: R% u( V8 y
     * 获取对象
# V9 b% N' o/ @2 h$ h! [     *
* P% z# q! o& \3 u     * @param key 键
  B- U4 A4 J0 ~; n' x& `     * @return 正确的值:Object值对象<br>$ O# L1 l2 \: E8 M9 {. g+ e8 Y3 X
     * 错误的值:null
" h+ |  b) A5 G/ M- }' k     */
3 |' G2 b5 z3 a    @SuppressWarnings("unchecked")! X6 U( Z# z# D
    public Object getObject(final String key) {; g) \: \( E+ [. R& D4 @" |5 A
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();. P* X1 K: d3 b" q6 @) s
        if (valueOperations == null || !redisTemplate.hasKey(key)) {
4 c0 ~( K; F" n% x2 f, |1 G; V- h; h            return null;; y) o' p; i! ?- F
        }. t% A( F# B2 B* ^1 @3 p$ g: E
        final Object object = valueOperations.get(key);0 b1 q3 ~# m% U: i
        return object;
, N; s& a. {2 ?2 j! R7 v$ u  t7 Q    }
1 [9 Y0 R7 r& q/ l- {& Q. g+ ~) G5 T) I4 P5 b6 V* y
    /**) t9 |& I! ^& _* x
     * 从缓存中获取string值
( `; Z$ u; Y1 ]" i! I; A     *
! ]' C% c# D; L$ ]/ u     * @param key
/ e) Z+ V# o  b+ P8 Y$ S     * @return*/
8 W6 p* b% M+ u  F+ s: c    @SuppressWarnings("unchecked")
% _* b; h* Q3 @    public String getString(final String key) {
& c* E/ L1 Q6 C' i# M        String value = "";8 ?7 r* L. Z2 P/ Y  f- u$ J, {
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();
% N! i" O& k; h( H        if (valueOperations != null && redisTemplate.hasKey(key)) {
7 w5 N) q8 `1 d- c! b) f$ ]6 F: e            final Object object = valueOperations.get(key);
0 D5 r1 U2 O" U) _& P8 r' q6 C+ L            if (null != object) {! ^& t* m% c& V1 ~9 ^& [
                LOGGER.info("--getString--object not empty");& Z# j( O& T  T" x
                value = object.toString();
0 s2 t% N2 u& q$ F* E            } else {
, b# l* l3 E0 E# }- F9 k                LOGGER.info("--getString--object empty");
1 j% H/ }* q5 p, _4 q1 q6 I            }! r$ K+ x; E0 M; E: q% m8 q  w
        }6 S3 B4 e# \% `/ P
        return value;/ V, x8 h4 l, N
    }5 f3 O/ }4 _# v5 x3 x6 u
2.2:在redis中实现时间控制
' {- j' [+ t* C$ x; \
1 X0 w4 y+ ~( A! W, I: m2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。
$ N0 z, ?: r$ Y' y$ Q, L" M) a7 ]) \4 O* Q. p, ~0 \9 l; D
import com.youjia.orders.redis.RedisManager;
' ?6 s% O/ h# x' yimport org.junit.Test;# @) p7 l$ ]$ U
import org.springframework.beans.factory.annotation.Autowired;3 i6 z5 P3 G% y  L
( n# h$ `) Q4 K
import java.util.Objects;  l0 y' f$ @1 Y# s* q0 S  E
1 q5 P8 O, B! B# Z- S, d
/**9 N2 c$ H- _# x( A  q- a+ M
* @Auther: Yrion' m& v9 r7 \  V( l* r
* @Date: 2019-01-11 23:36$ _; m0 \1 p$ S1 T- E2 m
*/  K0 Z1 A! Q. h' z& Q
( g7 ^+ I; `. N) L2 W
public class RedisTest extends OrderProviderApplicationTests {
9 h( T8 H% G4 Z& x  E! W
, K7 P8 [8 p' `+ ^; G7 S: m    @Autowired
$ D. N$ B" z; Q5 C2 }1 R    private RedisManager redisManager;* T+ U9 w2 i8 d0 x; M

8 h1 {5 Y% N- b8 {$ @1 P) Z" t    @Test" I% F# z) @' {# U3 f( G- N1 d
    public void test() {9 W! t' s: P1 X
        controlTime("10000001", 10L);
% F7 B2 v& }$ }    }& i4 x. Z* d" C* a' O  @6 l# f

$ u  a! U* V" E% `    public void controlTime(String requestId, Long timeOut) {
! k) @: N* |8 j3 d# Y) T9 F- T* y! Z: K! a0 x8 G" U
        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {
1 H6 e. L9 N2 f& Q) Y2 A5 n            return;
5 s6 c- o0 J! @3 ~        }6 I! G, N* e5 P  C
        //something code+ m# I5 J, X! h: S. z8 p
        final String value = "value";" n# U  D/ E" Z& U" ]- n) g
        redisManager.setObject(requestId, value, timeOut);; c7 U/ o% m; [0 Z
        final long startTime = System.currentTimeMillis();$ J1 A7 t6 f8 g: p, P1 f
        System.out.println("开始控制时间");
( @+ l2 p, X! d$ y: P  _        //start. X: O) A1 n- b3 }
        for (; ; ) {* W6 c2 p# v, _8 Y- |/ ~
            if (Objects.isNull(redisManager.getObject(requestId))) {
+ [# o1 o, D3 }# _  U5 f                break;
1 {. x0 t9 }* G! V( I            }0 w* b2 R/ _/ d9 r
        }% {' _( y3 s) m% Y" Q: d
        final long endTime = System.currentTimeMillis();' _8 R7 \+ K+ K1 r, Q- Q3 e
. _4 @$ l3 K& d: }8 h/ g6 c" A
        final long useTime = endTime - startTime;, H" Q/ f  b( k
( v: b" j5 k2 e& E' K$ l
        System.out.println("一共耗费时间:" + useTime);( q5 d- `# k7 U2 }5 K7 K1 x
    }
+ p( ~" u! k5 }1 o9 v' `( E( w}
5 w# a- O! A0 E9 p+ qoutPut:
+ N! s& i  g' ^2 P8 _
' E& N8 B6 Y1 a. c5 o: {* W开始控制时间" _' W+ U6 l3 Y/ I# L
一共耗费时间:10042
4 \9 X2 x( p5 u# j# Z+ L* z三:总结7 f3 h% b3 T$ K* d9 N1 L' k" N
: t( e# U. `6 L: U+ F5 r
本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
; L) C7 b4 J" t* \5 ~( c2 p1 d2 g————————————————
+ E  T. A' u& o% {+ X9 |7 e版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。) L" G+ e6 G' |* t6 U9 l
原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/105893325: m7 Z' j9 G4 \. N) V* s9 t
0 D/ ~+ u1 _* c* c  T* @

. I+ D2 {7 r0 d. t; N0 v% }2 K8 ^
作者: 1661888967    时间: 2020-5-6 09:57
学习一下,感谢分享$ K3 J$ B  U' C1 N





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