数学建模社区-数学中国

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

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

  ~! l5 {% b- f+ `& W4 fJava如何优雅的实现时间控制前言:需求是这样的,在与第三方对接过程中,对方提供了token进行时效性验证,过一段时间token就会失效.后台有定时任务在获取,但是偶尔会出现token失效,这是因为在获取的时候,定时任务正在跑,可能正在获取最新的token中,这个时候如何过一段时间(比如800毫秒之后)再请求呢?小王仰望天空45度,思考起来了。。。
7 `" ]# i8 L- \  {  ]; }, Z' N2 A2 J, D2 K) ~
一:时间控制的几种方案
% I7 K$ [6 R) ^7 L" _2 p4 Q  n" o9 i9 N
7 v: ~5 i+ y# }* h: I$ `9 n1.1: 从线程方面解决, ^, W# {/ b& [$ i

" B3 ^5 y( y6 [最简单粗暴的一种实现方案:Thread.sleep(800),但是很快就被小王给pass掉了。为什么呢?虽然这种方式可以,但是存在一个隐患,如果在多线程环境下,线程很容易被interrupt,这样代码就会抛出异常,这样线程就会挂起,导致整个线程异常结束。实在是不够优雅,违背了我们设计的初衷。/ n4 ?: G* X- s# }# K

* S8 g+ h4 u. g( I5 w" o( ]' G1.2:使用Timer
2 U& {3 C2 B, t) z4 j  \: A' J0 K
) [" l* j+ M+ k3 f  U2 w查阅了jdk,我发现有个实现定时的类,使用它是可以的,在jdk中提供了定时器类,这个类的主要作用就是控制一定的时间来简单的定时执行某个任务。有点简单的elasticJob的设计味道。接下来看一下,用timmer如何实现延时。。有点惊喜,我们来写一个最简单的例子来看一下如何实现定时任务:- t0 w/ ~4 J1 g& A2 W$ e
1 s8 L7 C; s& Y: Z  Q
public class TimmerTest {
1 R6 q/ [2 s2 M  P6 I   /**! q9 g) x7 R6 l- {2 `
     * 测试方法+ h3 |/ V' O, R3 D
     */+ r/ h" ^. l0 [/ t$ _4 v/ \
    public void test() {
0 ^, D$ @+ `* h4 f( p        Timer timer = new Timer();
2 s7 d0 @& F9 R; F0 L1 j        timer.schedule(new MyTask(), 800);8 t7 W2 i3 P" L- x! e7 l
    }7 l2 h: V+ B8 e9 S; D7 G7 K
: c% K# U" b; e+ R# q% k: ]
    public class MyTask extends TimerTask {! n* A1 Y; H: D" C( ~
, I9 p  `! p! O' Y" y+ n
        /**
. C. d' _. j% Y; K( T         * 运行方法7 D- g) P: m; L7 @! e6 j+ R
         */
9 G: ~: P+ V) R! V' ]3 E        @Override
1 p% b3 h$ N5 `$ f6 t2 v7 D7 `8 K        public void run() {; I" n+ P6 ]/ ]& V
            System.out.println("输出");5 i' j; K, h0 `  j  G
        }
5 |3 {$ F* l5 a( @- X    }5 v+ A; |6 D: |) m
}: ?; d/ x; |1 a  ?# O+ A
这是一个很简单的定时器实现,可以看出它只需要将方法对应的类继承自MyTask就可以实现定时执行,这种方法是可以实现延时的效果,但是它有一个致命的缺点:对代码的侵入性太大,为了实现定时我们不得已将对应的方法封装成一个类,然后放在定时器里执行。这样的、是可以的,但未免也有点太得不偿失了。为此我要更改整个类的结构,对于修改一个东西,我们要尽量按照最简单的方式最好的效果来实现,所以这种方案也应该pass掉。1 n& l' d; O. K* B: R) [
" d  k; {" M1 _* s  W# \
1.3:redis延时' M3 G2 B5 h* g$ ?' n5 g4 y5 _

! S8 w2 L4 K* B* G. L9 n( ^在redis中存在一个命令:EXPIRE,这个命令可以设置键存活的时间。一旦超过指定的时间,redis就会将键对应的值给删除掉,因此可以利用这一特性,我们来曲线实现延时功能。在redis的实际命令如下:
) P+ R6 B2 \7 z8 U& s  r6 ?6 x/ \5 ~' t, Z
3 \+ E. g1 X9 Q- y/ q* r0 i
; J' d) J' M" h% {" P
通过EXPIRE命令可以设置键的过期时间,一旦超过预设的时间,值就会变成(nil)。利用这一点,加入一些业务参数,我们就可以有效的实现延时的目的。通过redis的过期时间使用redis的好处有以下几点:
: q3 `4 H9 q4 K0 Z% R! u. E0 {+ U' ]' ~
1:对代码的侵入性低,不用额外起另外的线程来执行。只需要加入一个方法就可以对单流程的时间控制
) p# [( i! Q/ w* y- p8 \0 y/ Z) U6 S8 C$ M( Z. _
2:实现方便灵活,通过key设值可以加入一些唯一性的id来表示业务含义,从而保证业务的稳健实现
8 J1 P' u# i) K! ?1 L* c9 B8 I+ I: m+ I: `7 S
3:简单,真正的代码实现起来只有很少,下面会给出代码示范。7 {$ b/ N9 e3 q; f5 d/ J

& x7 \% o4 G3 j+ X$ R二:redis& ?- q6 v( d- H- u3 G- g6 J) S

5 f$ o3 D% P/ N) L' {2.1:maven中引入redis
" V% `; o9 v( `' M9 b
4 ^# X. G+ |, ~6 ]引入spring-boot-starter-data-redis,这是springboot专门针对redis出的整合依赖库,整合度要比jedis、和redssion都要好,所以推荐这个依赖库:有不少朋友问,如何深入学习Java后端技术栈,今天分享一个,互联网牛人整理出来的Java深入学习路线图,以及开发工具包,【戳我进入】学习裙。
! D4 O& h" W# U! M
2 T4 d9 |" F9 c) ]' p) |<dependency>
7 D7 ?9 {8 P8 C7 o6 }  <groupId>org.springframework.boot</groupId>
' ]1 e7 r1 q! L( j* ]    <artifactId>spring-boot-starter-data-redis</artifactId>- {/ L0 e" M+ r8 o. h' H( C
      <exclusions>+ \/ J' r) N& K7 ?5 _7 H) U
        <exclusion>' c7 N& }! k/ k7 t; V) b0 a8 i
          <groupId>io.lettuce</groupId>! ]2 C+ F5 d; d. i4 {+ |4 |
          <artifactId>lettuce-core</artifactId>+ m, T6 n3 A  V) T0 M/ l1 X
        </exclusion># v. a5 K/ z& n* m+ k( _2 f
      </exclusions>
3 O7 x3 ]8 P3 q/ J</dependency>
4 R  M7 N3 K8 P) }# \<dependency>
% k6 p* [3 I2 M! h* C" L6 B  <groupId>redis.clients</groupId>
+ g! \5 C- D  L/ x9 O) W# X  <artifactId>jedis</artifactId>
' C, V* P6 b+ T+ `</dependency>3 m0 d, V1 y6 `  z3 e' d* s
2.2: 在springboot中配置redis
' L- u4 ^" J# n, O3 P7 ^* B4 S2 T
: z4 z/ c7 I5 e: `8 \( E0 l; Kimport org.springframework.beans.factory.annotation.Autowired;# F6 j4 J/ b* Y
import org.springframework.context.annotation.Bean;& |8 s; \4 W" |9 Y* u1 J
import org.springframework.context.annotation.Configuration;  `6 u6 T8 l5 X% P1 t" K7 _( f1 ]
import org.springframework.data.redis.core.RedisTemplate;
! o  ^- \/ T' }3 {* rimport org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
5 n3 V- ~: {, S9 T5 U+ Eimport org.springframework.data.redis.serializer.StringRedisSerializer;
. ~" t. q  E* N: v+ Z
; Z1 F% c5 Q& p@Configuration5 [$ u3 H7 I' [; i' i2 X
public class RedisConfig {3 C! x/ @4 s1 X% ]" r$ y5 c& G' u
0 F8 r; \9 \% h& e4 u" r
    @Autowired7 }& I; X& i3 M3 w/ `
    private RedisTemplate redisTemplate;
" k2 W+ F& b( l4 U4 s% `6 {$ V1 Q' u# ^% [, L
    /**
  o; T8 i* N: L6 Y& r     * redisTemplate实例化
8 [9 D$ o/ G) {, s     *
6 m" l  L6 ~. u! b$ Q     * @return6 d: ^2 M" _0 z( @# M: {
     */
2 r3 Y4 r) P' h% L3 z4 i3 g. `( G    @Bean6 Q# D$ S5 E" E: H; L; D
    public RedisTemplate redisTemplateInit() {  ^2 e6 Y% W) ?# L3 c
        //设置序列化Key的实例化对象, @$ x8 X2 E- r- Y- {; F
        redisTemplate.setKeySerializer(new StringRedisSerializer());
; l3 n% d- s0 I- J2 G) z        //设置序列化Value的实例化对象) k" g3 W1 c6 Q# J5 H5 H+ J) l) i) Q
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
) H) ]$ W4 T# B8 z& S        return redisTemplate;1 b* X1 K9 D% x1 }4 M
    }5 V6 ]2 p9 g9 ~. z

# b8 D0 e  O/ y$ s& ?, @, v}9 J/ y0 R' O% Q
2.2:redisTemplate模板工具类
4 c: ]. l& D6 z# h. i5 L! g
: X# l/ S# d  Z% h1 n@Component
1 M" w* Y! E& B9 a/ p( Q6 \public class RedisManager {+ z0 r7 F4 {% A  V* g! m& ?7 K
& }- p9 ]! P9 Q+ s
    private static final Logger LOGGER = LoggerFactory.getLogger(RedisManager.class);* r& n; V) @, E* e+ g$ n
+ |# l3 F. C$ S" J# ?) b  b
    @Autowired1 z6 y" m9 L. R
    private RedisTemplate redisTemplate;5 b$ V5 J6 c& k" O) D

' n: f2 z* m- R    /**0 N1 o  h( D5 X2 V/ }
     * 设置对象) A+ g7 z; U1 Z5 @8 E% K% ^: P
     *
% N* l4 P* S4 C& N& K* D* ^     * @param key key+ L6 z4 B, U6 y9 m/ q2 Y
     * @param value value值; \% ~3 c  _( r/ h0 S, x6 o
     * @param <T> 返回值泛型: b* J$ s9 B! F$ r0 d
     * @return 正确的值:<T> 错误的值:null
# z. N. ^0 D; S$ O7 T' b     */: y  T$ ?- b& _
    @SuppressWarnings("unchecked")
1 S  x. y- I* i. b9 _" Y  M    public <T> ValueOperations<String, T> setObject(final String key, final T value) {
( k& y/ }$ _: X9 B* m        final ValueOperations<String, T> operation = redisTemplate.opsForValue();- c, w: f7 n: M+ t' j
        operation.set(key, value);) Z1 }5 R, h* J5 v* {8 V7 u$ g
        return operation;
# g! f  }! L) R: w3 A    }
) U, Q4 Y. F7 Z2 S& e0 ]. F0 R$ p
" n4 r9 M( ^! Y    /**
3 Y/ ]' m( @- b" v8 ~     * 设置对象及失效时间 (单位:秒)
7 f* G- ]: e, L( M7 p     *
; ~* d  w1 x% K- |- e- f( A( p     * @param key key
7 _$ ]9 c& [7 E% a( c/ k9 e+ z     * @param value value值
5 L$ j% R0 h/ n! J% n" @     * @param <T> 返回值泛型# `2 f9 X8 R' X( `, E3 C
     * @param time 秒值$ D8 H3 l" o5 e- G# F; |5 |# k
     * @return 正确的值:<T> 错误的值:null
1 @' i, {0 E/ D& |     */
' D7 P7 T0 R! V% \    @SuppressWarnings("unchecked")
; B% t# Z$ D( \0 l: W    public <T> ValueOperations<String, T> setObject(final String key, final T value, final long time) {
* S2 p* b' p/ m/ h/ O        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
) ?+ c# V7 X9 \        operation.set(key, value, time, TimeUnit.SECONDS);
0 F6 b* y3 j' l- a+ A        return operation;& I5 u6 R7 O( Q3 R8 r: F
    }
) X! U% a8 U$ r$ C1 J0 T  Y- h6 |1 A, {+ M" O
' V% `& K9 W2 U# t
    /**
* L- K5 _7 M# \0 L+ s# Q; O0 N     * 设置对象及失效时间(单位:毫秒)0 j3 s5 n$ N  m
     *
2 G. y- Q7 U1 Y     * @param key key0 q- `& w8 R2 Q
     * @param value value值
- l' r6 g; }7 g     * @param <T> 返回值泛型
+ i' A$ S1 L# _     * @param time 秒值
0 ?+ i: h. l- ~. l# L6 A0 _     * @return 正确的值:<T> 错误的值:null
! w; _9 |6 E; O- [1 d     */
6 r- |0 _% h1 h, U3 \4 ]    @SuppressWarnings("unchecked")! E: F+ U7 F4 C& M0 r, N2 U
    public <T> ValueOperations<String, T> setObjectForMillSeconds(final String key, final T value, final long time) {
1 v/ W$ b+ i* [* f" r        final ValueOperations<String, T> operation = redisTemplate.opsForValue();
- Z9 O" Z8 S; F! A+ D! h        operation.set(key, value, time, TimeUnit.MILLISECONDS);
( q) H4 g% T$ c) Z0 w        return operation;
9 c" w/ `, j5 i, k% Z1 c" O    }9 j. l$ f1 H7 y' T
5 P2 h1 M9 d8 ?
    /**
! K. [2 R$ X/ ^+ S' C     * 获取对象# B  c4 V0 b7 y' b
     *% ^  ^7 N, L7 w/ o, e4 F& }
     * @param key 键) K3 ^/ g# H+ w
     * @return 正确的值:Object值对象<br>4 ~8 h1 q+ Z+ L+ t
     * 错误的值:null
" c3 i  E% y4 Y0 Z8 H     */: P* F: r  {, s7 v
    @SuppressWarnings("unchecked")
9 v- k  ^, c8 p; O+ O) ?    public Object getObject(final String key) {+ \8 n0 f$ z! |* b. }+ N0 t
        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();$ M4 R0 ^" y* F( B
        if (valueOperations == null || !redisTemplate.hasKey(key)) {" k# w* Y: F! Q1 U3 c
            return null;, y  m" Q; a7 I7 s0 [8 b8 F
        }0 O, k( I1 _$ V+ O. l' N
        final Object object = valueOperations.get(key);
  O$ L+ V: G* P1 @: A6 l% y        return object;2 \9 `; E9 O' @+ m  N1 P$ ~
    }' H2 f+ |# G* c% d

. s* ]6 H" I: a. Q    /**
+ j& Q' Y4 I$ }     * 从缓存中获取string值. k3 e! l$ e0 \
     *
: N+ O/ k; J7 }4 W2 q* E% N2 h8 ]     * @param key: a- R9 j* L! q9 E4 z3 z. A
     * @return*/$ h+ X5 W' ?$ i$ N* p; Q. r
    @SuppressWarnings("unchecked")
2 q1 ^( _! e1 W0 _& w" m5 {    public String getString(final String key) {" v- z* X2 i5 x8 c- m% {& @- }
        String value = "";
" k$ x6 b, T9 X        final ValueOperations<String, Object> valueOperations = redisTemplate.opsForValue();+ o2 n2 b" D# Z
        if (valueOperations != null && redisTemplate.hasKey(key)) {
3 C. J6 |4 }. l% C# m; ?            final Object object = valueOperations.get(key);
+ d" l2 W3 B% ?! [! C8 \/ b            if (null != object) {
1 Q9 h$ _9 _8 ?7 g. _                LOGGER.info("--getString--object not empty");" ^2 H. S* Q& v% b
                value = object.toString();
8 ?) h8 \" ]  F- }8 V/ F9 z0 i  q$ p# U            } else {6 m6 d) h3 D+ h& ]0 ^
                LOGGER.info("--getString--object empty");
  p5 A9 r' ?) D# I  M; D0 ?: \            }1 ]' s4 Q/ y. g+ _+ a) ?- K
        }) K- T% J) H3 d& W2 e; ^
        return value;: w: L- i6 \6 l' F8 q
    }
  N9 q/ b3 [/ ?( X0 V: S8 ]' L2 H* o2.2:在redis中实现时间控制# ]* a* M. @6 J

7 N3 G: ]9 j( ~5 s, R1 a7 i2.2.1:在流程中停留一段时间,通过无限循环来不断的从redis取数值,一旦取到的值为null(redis的键值为null)就退出,这样的写法有点类似于以前CAS的些许味道,通过无限循环比较值。0 W* c# Y3 g/ t  [$ s$ c
$ }. w! I; |8 t6 q
import com.youjia.orders.redis.RedisManager;' b+ o% z+ U8 a; Y6 K1 L
import org.junit.Test;" R4 W; C5 ]1 K9 @) O
import org.springframework.beans.factory.annotation.Autowired;  s! I0 t  q! _' J7 w

6 i  o8 _- P+ B+ Uimport java.util.Objects;
$ M5 P8 a9 ?: O# X6 ~) w5 V
6 c: A$ z; c$ Q5 v6 ?! e/**8 A0 \4 D# }1 y" W) q& O
* @Auther: Yrion
8 Y( e- G2 A6 @& A" U * @Date: 2019-01-11 23:368 F( }8 o$ }! r% N) h% ]
*/
- a" x, ]$ ?2 A. n
! h4 r) b/ A  T  ^public class RedisTest extends OrderProviderApplicationTests {
& r: j. M( i+ Q' \# H
5 a8 T1 R5 s1 z  ~8 V    @Autowired
1 v7 u) I4 a: O2 i. s  K2 s' k    private RedisManager redisManager;
! f- u( ]6 A6 u$ L( z2 v& |; o
4 U+ c) J9 v( j: j2 F& e    @Test
( c' F$ ?! I! G1 G+ j    public void test() {7 ?6 n) J6 Y: W8 b5 y
        controlTime("10000001", 10L);$ f& ]' ~3 t" d# X2 G; ]# @  B
    }
8 k& Y, z! k3 O) |9 Y4 L) |3 G8 B- |5 Z) W3 j9 `8 M
    public void controlTime(String requestId, Long timeOut) {: F: ^! T3 x. s  M3 i' i3 N8 ^8 R
. ?7 o9 k' A  V0 f7 O/ M4 _' L
        if (Objects.isNull(requestId) || Objects.isNull(timeOut)) {+ P! }: r* }/ ^: s) m
            return;
" |& u/ y  _4 a        }
: b. C4 N  j7 e        //something code
% }! ^  ?' s# h        final String value = "value";
. o: P+ d  i  M4 c' Q        redisManager.setObject(requestId, value, timeOut);7 K2 T6 G1 k! N' C
        final long startTime = System.currentTimeMillis();
, V% Z. z' u3 m; q- |        System.out.println("开始控制时间");$ i. T( l" D) H8 l$ t
        //start/ `! G" g  Q9 _5 j$ E5 h' O% J% i
        for (; ; ) {
, c7 x, D0 D* q+ b5 s- H            if (Objects.isNull(redisManager.getObject(requestId))) {$ y! S; i1 Z! F7 U8 r
                break;
( K* Z0 U, X7 a$ I8 X% h            }
  ]) }+ J/ X( j$ J3 |        }
; d/ j1 `" b) S7 |, m. {" q        final long endTime = System.currentTimeMillis();
9 H2 U* M: e, i
5 h% J9 r& k1 _+ h6 @        final long useTime = endTime - startTime;
( ]5 O# Z  b2 P& W5 v% h
) l) c" L1 C4 T, m# x- y        System.out.println("一共耗费时间:" + useTime);
: Q+ q, F9 y* B) q+ W' C* J% I& D    }+ V7 d8 O0 {0 b
}( ~$ P+ }0 g4 p# o( u( ]! L/ _- a
outPut:
7 D: t% H, N/ J- r9 v+ i+ Q- e* X# v1 n8 f
开始控制时间* V3 X% j1 ^) ~" j6 s+ j& j8 E
一共耗费时间:10042
: G* D) |7 Q; j6 {) h三:总结
4 D1 U4 k: o8 O- b2 I% s3 c5 t
0 f8 x5 M5 E( |# \$ ?5 Q3 B9 q本篇博文讲述了在平时工作中,我们可能会遇到的一些关于时间控制的问题,在这个问题上我又进行了进一步的探讨,如何实现优雅的解决问题?我们解决问题不仅仅是要把这个问题解决了,而是要考虑如何更好更秒的解决,这就要善于利用一些中间件或者工具类提供的功能特性,善于发现、及时变通,把这种特性利用到我们的代码中,会对我们的开发起到推波助澜、如虎添翼的作用!
  P4 I  R) x* N2 K# x————————————————
% G* C6 R2 [8 g$ H4 Q版权声明:本文为CSDN博主「大数据架构师李旭」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。7 Y; [2 @. E& U" A* {8 @! O
原文链接:https://blog.csdn.net/dashujujiagoushi/article/details/1058933251 E4 ?; Q0 A# u* k
$ u) K1 x0 F& o; i7 S. H  I

$ c6 z& V% d' @6 {: x# [. H5 F4 n# T
作者: 1661888967    时间: 2020-5-6 09:57
学习一下,感谢分享8 V* {9 L. e8 V1 H





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