欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

簡(jiǎn)單注解實(shí)現(xiàn)集群同步鎖(spring+redis+注解)

 更新時(shí)間:2017年01月20日 10:18:52   作者:partner4java  
本文主要介紹了簡(jiǎn)單注解實(shí)現(xiàn)集群同步鎖的步驟與方法。具有一定的參考價(jià)值,下面跟著小編一起來看下吧

互聯(lián)網(wǎng)面試的時(shí)候,是不是面試官常問一個(gè)問題如何保證集群環(huán)境下數(shù)據(jù)操作并發(fā)問題,常用的synchronized肯定是無法滿足了,或許你可以借助for update對(duì)數(shù)據(jù)加鎖。本文的最終解決方式你只要在方法上加一個(gè)@P4jSyn注解就能保證集群環(huán)境下同synchronized的效果,且鎖的key可以任意指定。本注解還支持了鎖的超時(shí)機(jī)制。

本文需要對(duì)Redis、spring和spring-data-redis有一定的了解。當(dāng)然你可以借助本文的思路對(duì)通過注解對(duì)方法返回?cái)?shù)據(jù)進(jìn)行緩存,類似com.google.code.simple-spring-memcached的@ReadThroughSingleCache。

第一步:  介紹兩個(gè)自定義注解P4jSyn、P4jSynKey

P4jSyn:必選項(xiàng),標(biāo)記在方法上,表示需要對(duì)該方法加集群同步鎖;

P4jSynKey:可選項(xiàng),加在方法參數(shù)上,表示以方法某個(gè)參數(shù)作為鎖的key,用來保證更多的坑,P4jSynKey并不是強(qiáng)制要添加的,當(dāng)沒有P4jSynKey標(biāo)記的情況下只會(huì)以P4jSyn的synKey作為鎖key。

package com.yaoguoyin.redis.lock; 
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
/** 
 * <b>同步鎖:</b><br/> 
 * 主要作用是在服務(wù)器集群環(huán)境下保證方法的synchronize;<br/> 
 * 標(biāo)記在方法上,使該方法的執(zhí)行具有互斥性,并不保證并發(fā)執(zhí)行方法的先后順序;<br/> 
 * 如果原有“A任務(wù)”獲取鎖后任務(wù)執(zhí)行時(shí)間超過最大允許持鎖時(shí)間,且鎖被“B任務(wù)”獲取到,在“B任務(wù)”成功貨物鎖會(huì)并不會(huì)終止“A任務(wù)”的執(zhí)行;<br/> 
 * <br/> 
 * <b>注意:</b><br/> 
 * 使用過程中需要注意keepMills、toWait、sleepMills、maxSleepMills等參數(shù)的場(chǎng)景使用;<br/> 
 * 需要安裝redis,并使用spring和spring-data-redis等,借助redis NX等方法實(shí)現(xiàn)。 
 * 
 * @see com.yaoguoyin.redis.lock.P4jSynKey 
 * @see com.yaoguoyin.redis.lock.RedisLockAspect 
 * 
 * @author partner4java 
 * 
 */ 
@Target({ ElementType.METHOD }) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface P4jSyn { 
 /** 
 * 鎖的key<br/> 
 * 如果想增加坑的個(gè)數(shù)添加非固定鎖,可以在參數(shù)上添加@P4jSynKey注解,但是本參數(shù)是必寫選項(xiàng)<br/> 
 * redis key的拼寫規(guī)則為 "RedisSyn+" + synKey + @P4jSynKey<br/> 
 * 
 */ 
 String synKey(); 
 /** 
 * 持鎖時(shí)間,超時(shí)時(shí)間,持鎖超過此時(shí)間自動(dòng)丟棄鎖<br/> 
 * 單位毫秒,默認(rèn)20秒<br/> 
 * 如果為0表示永遠(yuǎn)不釋放鎖,在設(shè)置為0的情況下toWait為true是沒有意義的<br/> 
 * 但是沒有比較強(qiáng)的業(yè)務(wù)要求下,不建議設(shè)置為0 
 */ 
 long keepMills() default 20 * 1000; 
 /** 
 * 當(dāng)獲取鎖失敗,是繼續(xù)等待還是放棄<br/> 
 * 默認(rèn)為繼續(xù)等待 
 */ 
 boolean toWait() default true; 
 /** 
 * 沒有獲取到鎖的情況下且toWait()為繼續(xù)等待,睡眠指定毫秒數(shù)繼續(xù)獲取鎖,也就是輪訓(xùn)獲取鎖的時(shí)間<br/> 
 * 默認(rèn)為10毫秒 
 * 
 * @return 
 */ 
 long sleepMills() default 10; 
 /** 
 * 鎖獲取超時(shí)時(shí)間:<br/> 
 * 沒有獲取到鎖的情況下且toWait()為true繼續(xù)等待,最大等待時(shí)間,如果超時(shí)拋出 
 * {@link java.util.concurrent.TimeoutException.TimeoutException} 
 * ,可捕獲此異常做相應(yīng)業(yè)務(wù)處理;<br/> 
 * 單位毫秒,默認(rèn)一分鐘,如果設(shè)置為0即為沒有超時(shí)時(shí)間,一直獲取下去; 
 * 
 * @return 
 */ 
 long maxSleepMills() default 60 * 1000; 
} 
package com.yaoguoyin.redis.lock; 
import java.lang.annotation.ElementType; 
import java.lang.annotation.Inherited; 
import java.lang.annotation.Retention; 
import java.lang.annotation.RetentionPolicy; 
import java.lang.annotation.Target; 
/** 
 * <b>同步鎖 key</b><br/> 
 * 加在方法的參數(shù)上,指定的參數(shù)會(huì)作為鎖的key的一部分 
 * 
 * @author partner4java 
 * 
 */ 
@Target({ ElementType.PARAMETER }) 
@Retention(RetentionPolicy.RUNTIME) 
@Inherited 
public @interface P4jSynKey { 
 /** 
 * key的拼接順序 
 * 
 * @return 
 */ 
 int index() default 0; 
} 

這里就不再對(duì)兩個(gè)注解進(jìn)行使用上的解釋了,因?yàn)樽⑨屢呀?jīng)說明的很詳細(xì)了。

使用示例:

package com.yaoguoyin.redis.lock; 
import org.springframework.stereotype.Component; 
@Component 
public class SysTest { 
 private static int i = 0; 
 @P4jSyn(synKey = "12345") 
 public void add(@P4jSynKey(index = 1) String key, @P4jSynKey(index = 0) int key1) { 
 i++; 
 System.out.println("i=-===========" + i); 
 } 
} 

第二步:切面編程

在不影響原有代碼的前提下,保證執(zhí)行同步,目前最直接的方式就是使用切面編程

package com.yaoguoyin.redis.lock; 
import java.lang.annotation.Annotation; 
import java.lang.reflect.Method; 
import java.util.SortedMap; 
import java.util.TreeMap; 
import java.util.concurrent.TimeUnit; 
import java.util.concurrent.TimeoutException; 
import org.aspectj.lang.ProceedingJoinPoint; 
import org.aspectj.lang.annotation.Around; 
import org.aspectj.lang.annotation.Aspect; 
import org.aspectj.lang.reflect.MethodSignature; 
import org.springframework.beans.factory.annotation.Autowired; 
import org.springframework.beans.factory.annotation.Qualifier; 
import org.springframework.data.redis.core.BoundValueOperations; 
import org.springframework.data.redis.core.RedisTemplate; 
/** 
 * 鎖的切面編程<br/> 
 * 針對(duì)添加@RedisLock 注解的方法進(jìn)行加鎖 
 * 
 * @see com.yaoguoyin.redis.lock.P4jSyn 
 * 
 * @author partner4java 
 * 
 */ 
@Aspect 
public class RedisLockAspect { 
 @Autowired 
 @Qualifier("redisTemplate") 
 private RedisTemplate<String, Long> redisTemplate; 
 @Around("execution(* com.yaoguoyin..*(..)) && @annotation(com.yaoguoyin.redis.lock.P4jSyn)") 
 public Object lock(ProceedingJoinPoint pjp) throws Throwable { 
 P4jSyn lockInfo = getLockInfo(pjp); 
 if (lockInfo == null) { 
  throw new IllegalArgumentException("配置參數(shù)錯(cuò)誤"); 
 } 
 String synKey = getSynKey(pjp, lockInfo.synKey()); 
 if (synKey == null || "".equals(synKey)) { 
  throw new IllegalArgumentException("配置參數(shù)synKey錯(cuò)誤"); 
 } 
 boolean lock = false; 
 Object obj = null; 
 try { 
  // 超時(shí)時(shí)間 
  long maxSleepMills = System.currentTimeMillis() + lockInfo.maxSleepMills(); 
  while (!lock) { 
  long keepMills = System.currentTimeMillis() + lockInfo.keepMills(); 
  lock = setIfAbsent(synKey, keepMills); 
  // 得到鎖,沒有人加過相同的鎖 
  if (lock) { 
   obj = pjp.proceed(); 
  } 
  // 鎖設(shè)置了沒有超時(shí)時(shí)間 
  else if (lockInfo.keepMills() <= 0) { 
   // 繼續(xù)等待獲取鎖 
   if (lockInfo.toWait()) { 
   // 如果超過最大等待時(shí)間拋出異常 
   if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) { 
    throw new TimeoutException("獲取鎖資源等待超時(shí)"); 
   } 
   TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills()); 
   } else { 
   break; 
   } 
  } 
  // 已過期,并且getAndSet后舊的時(shí)間戳依然是過期的,可以認(rèn)為獲取到了鎖 
  else if (System.currentTimeMillis() > getLock(synKey) && (System.currentTimeMillis() > getSet(synKey, keepMills))) { 
   lock = true; 
   obj = pjp.proceed(); 
  } 
  // 沒有得到任何鎖 
  else { 
   // 繼續(xù)等待獲取鎖 
   if (lockInfo.toWait()) { 
   // 如果超過最大等待時(shí)間拋出異常 
   if (lockInfo.maxSleepMills() > 0 && System.currentTimeMillis() > maxSleepMills) { 
    throw new TimeoutException("獲取鎖資源等待超時(shí)"); 
   } 
   TimeUnit.MILLISECONDS.sleep(lockInfo.sleepMills()); 
   } 
   // 放棄等待 
   else { 
   break; 
   } 
  } 
  } 
 } catch (Exception e) { 
  e.printStackTrace(); 
  throw e; 
 } finally { 
  // 如果獲取到了鎖,釋放鎖 
  if (lock) { 
  releaseLock(synKey); 
  } 
 } 
 return obj; 
 } 
 /** 
 * 獲取包括方法參數(shù)上的key<br/> 
 * redis key的拼寫規(guī)則為 "RedisSyn+" + synKey + @P4jSynKey 
 * 
 */ 
 private String getSynKey(ProceedingJoinPoint pjp, String synKey) { 
 try { 
  synKey = "RedisSyn+" + synKey; 
  Object[] args = pjp.getArgs(); 
  if (args != null && args.length > 0) { 
  MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 
  Annotation[][] paramAnnotationArrays = methodSignature.getMethod().getParameterAnnotations(); 
  SortedMap<Integer, String> keys = new TreeMap<Integer, String>(); 
 
  for (int ix = 0; ix < paramAnnotationArrays.length; ix++) { 
   P4jSynKey p4jSynKey = getAnnotation(P4jSynKey.class, paramAnnotationArrays[ix]); 
   if (p4jSynKey != null) { 
   Object arg = args[ix]; 
   if (arg != null) { 
    keys.put(p4jSynKey.index(), arg.toString()); 
   } 
   } 
  } 
  if (keys != null && keys.size() > 0) { 
   for (String key : keys.values()) { 
   synKey = synKey + key; 
   } 
  } 
  } 
  return synKey; 
 } catch (Exception e) { 
  e.printStackTrace(); 
 } 
 return null; 
 } 
 @SuppressWarnings("unchecked") 
 private static <T extends Annotation> T getAnnotation(final Class<T> annotationClass, final Annotation[] annotations) { 
 if (annotations != null && annotations.length > 0) { 
  for (final Annotation annotation : annotations) { 
  if (annotationClass.equals(annotation.annotationType())) { 
   return (T) annotation; 
  } 
  } 
 } 
 return null; 
 } 
 /** 
 * 獲取RedisLock注解信息 
 */ 
 private P4jSyn getLockInfo(ProceedingJoinPoint pjp) { 
 try { 
  MethodSignature methodSignature = (MethodSignature) pjp.getSignature(); 
  Method method = methodSignature.getMethod(); 
  P4jSyn lockInfo = method.getAnnotation(P4jSyn.class); 
  return lockInfo; 
 } catch (Exception e) { 
  e.printStackTrace(); 
 } 
 return null; 
 } 
 public BoundValueOperations<String, Long> getOperations(String key) { 
 return redisTemplate.boundValueOps(key); 
 } 
 /** 
 * Set {@code value} for {@code key}, only if {@code key} does not exist. 
 * <p> 
 * See http://redis.io/commands/setnx 
 * 
 * @param key 
 *  must not be {@literal null}. 
 * @param value 
 *  must not be {@literal null}. 
 * @return 
 */ 
 public boolean setIfAbsent(String key, Long value) { 
 return getOperations(key).setIfAbsent(value); 
 } 
 public long getLock(String key) { 
 Long time = getOperations(key).get(); 
 if (time == null) { 
  return 0; 
 } 
 return time; 
 } 
 public long getSet(String key, Long value) { 
 Long time = getOperations(key).getAndSet(value); 
 if (time == null) { 
  return 0; 
 } 
 return time; 
 } 
 public void releaseLock(String key) { 
 redisTemplate.delete(key); 
 } 
} 

RedisLockAspect會(huì)對(duì)添加注解的方法進(jìn)行特殊處理,具體可看lock方法。

大致思路就是:

1、首選借助redis本身支持對(duì)應(yīng)的setIfAbsent方法,該方法的特點(diǎn)是如果redis中已有該數(shù)據(jù)不保存返回false,不存該數(shù)據(jù)保存返回true;

2、如果setIfAbsent返回true標(biāo)識(shí)拿到同步鎖,可進(jìn)行操作,操作后并釋放鎖;

3、如果沒有通過setIfAbsent拿到數(shù)據(jù),判斷是否對(duì)鎖設(shè)置了超時(shí)機(jī)制,沒有設(shè)置判斷是否需要繼續(xù)等待;

4、判斷是否鎖已經(jīng)過期,需要對(duì)(System.currentTimeMillis() > getLock(synKey) && (System.currentTimeMillis() > getSet(synKey, keepMills)))進(jìn)行細(xì)細(xì)的揣摩一下,getSet可能會(huì)改變了其他人擁有鎖的超時(shí)時(shí)間,但是幾乎可以忽略;

5、沒有得到任何鎖,判斷繼續(xù)等待還是退出。

第三步:spring的基本配置

#*****************jedis連接參數(shù)設(shè)置*********************# 
 
#redis服務(wù)器ip # 
redis.hostName=127.0.0.1 
 
#redis服務(wù)器端口號(hào)# 
redis.port=6379 
 
#redis服務(wù)器外部訪問密碼 
redis.password=XXXXXXXXXX 
 
#************************jedis池參數(shù)設(shè)置*******************# 
 
#jedis的最大分配對(duì)象# 
jedis.pool.maxActive=1000 
 
jedis.pool.minIdle=100 
 
#jedis最大保存idel狀態(tài)對(duì)象數(shù) # 
jedis.pool.maxIdle=1000 
 
#jedis池沒有對(duì)象返回時(shí),最大等待時(shí)間 # 
jedis.pool.maxWait=5000 
 
#jedis調(diào)用borrowObject方法時(shí),是否進(jìn)行有效檢查# 
jedis.pool.testOnBorrow=true 
 
#jedis調(diào)用returnObject方法時(shí),是否進(jìn)行有效檢查 # 
jedis.pool.testOnReturn=true 
<?xml version="1.0" encoding="UTF-8"?> 
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee"xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:redis="http://www.springframework.org/schema/redis" xmlns:cache="http://www.springframework.org/schema/cache" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-4.2.xsd  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd  http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-4.1.xsd  http://www.springframework.org/schema/redis  http://www.springframework.org/schema/redis/spring-redis.xsd 
http://www.springframework.org/schema/cache  http://www.springframework.org/schema/cache/spring-cache.xsd"> 
 <!-- 開啟注解 --> 
 <aop:aspectj-autoproxy /> 
 <bean class="com.yaoguoyin.redis.lock.RedisLockAspect" /> 
 <!-- 掃描注解包范圍 --> 
 <context:component-scan base-package="com.yaoguoyin" /> 
 <!-- 引入redis配置 --> 
 <context:property-placeholder location="classpath:config.properties" /> 
 <!-- 連接池 --> 
 <bean id="poolConfig" class="redis.clients.jedis.JedisPoolConfig"> 
 <property name="minIdle" value="${jedis.pool.minIdle}" /> 
 <property name="maxIdle" value="${jedis.pool.maxIdle}" /> 
 <property name="maxWaitMillis" value="${jedis.pool.maxWait}" /> 
 </bean> 
 <!-- p:password="${redis.pass}" --> 
 <bean id="redisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory" p:host-name="${redis.hostName}" p:port="${redis.port}" 
 p:password="${redis.password}" p:pool-config-ref="poolConfig" /> 
 <!-- 類似于jdbcTemplate --> 
 <bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate" p:connection-factory-ref="redisConnectionFactory" /> 
</beans> 

redis的安裝本文就不再說明。

測(cè)試

package com.yaoguoyin.redis; 
import org.junit.runner.RunWith; 
import org.springframework.test.context.ContextConfiguration; 
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests; 
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; 
@RunWith(SpringJUnit4ClassRunner.class) 
@ContextConfiguration(locations = { "classpath:META-INF/spring/redis.xml" }) 
public class BaseTest extends AbstractJUnit4SpringContextTests { 
} 
package com.yaoguoyin.redis.lock; 
import java.util.concurrent.TimeUnit; 
import org.junit.Test; 
import org.springframework.beans.factory.annotation.Autowired; 
import com.yaoguoyin.redis.BaseTest; 
public class RedisTest extends BaseTest { 
 @Autowired 
 private SysTest sysTest; 
 @Test 
 public void testHello() throws InterruptedException { 
 for (int i = 0; i < 100; i++) { 
  new Thread(new Runnable() { 
  @Override 
  public void run() { 
   try { 
   TimeUnit.SECONDS.sleep(1); 
   } catch (InterruptedException e) { 
   e.printStackTrace(); 
   } 
   sysTest.add("xxxxx", 111111); 
  } 
  }).start(); 
 } 
 TimeUnit.SECONDS.sleep(20); 
 } 
 @Test 
 public void testHello2() throws InterruptedException{ 
 sysTest.add("xxxxx", 111111); 
 TimeUnit.SECONDS.sleep(10); 
 } 
} 

你可以對(duì)

void com.yaoguoyin.redis.lock.SysTest.add(@P4jSynKey(index=1) String key, @P4jSynKey(index=0) int key1)

去除注解@P4jSyn進(jìn)行測(cè)試對(duì)比。

ps:本demo的執(zhí)行性能取決于redis和Java交互距離;成千山萬單鎖并發(fā)建議不要使用這種形式,直接通過redis等解決,本demo只解決小并發(fā)不想耦合代碼的形式。

以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來一定的幫助,同時(shí)也希望多多支持腳本之家!

相關(guān)文章

  • 詳解SpringBoot如何統(tǒng)一處理返回的信息

    詳解SpringBoot如何統(tǒng)一處理返回的信息

    現(xiàn)在的項(xiàng)目是前后端開發(fā)的居多,那么我們?cè)趺炊x接口返回的數(shù)據(jù),怎么使用?Spring?Boot?來統(tǒng)一處理返回的信息呢,本文就來和大家簡(jiǎn)單講講
    2023-06-06
  • @FeignClient?path屬性路徑前綴帶路徑變量時(shí)報(bào)錯(cuò)的解決

    @FeignClient?path屬性路徑前綴帶路徑變量時(shí)報(bào)錯(cuò)的解決

    這篇文章主要介紹了@FeignClient?path屬性路徑前綴帶路徑變量時(shí)報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • Spring Boot配置AOP打印日志的全過程

    Spring Boot配置AOP打印日志的全過程

    這篇文章主要給大家介紹了關(guān)于Spring Boot配置AOP打印日志的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Spring Boot具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • 詳解JVM 中的StringTable

    詳解JVM 中的StringTable

    這篇文章主要介紹了JVM 中的StringTable,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • springboot讀取配置文件中的參數(shù)具體步驟

    springboot讀取配置文件中的參數(shù)具體步驟

    在本篇文章里小編給大家分享了關(guān)于springboot讀取配置文件中的參數(shù)的相關(guān)知識(shí)點(diǎn)內(nèi)容,有需要的朋友們跟著學(xué)習(xí)下。
    2019-06-06
  • Java數(shù)據(jù)結(jié)構(gòu)之順序表篇

    Java數(shù)據(jù)結(jié)構(gòu)之順序表篇

    順序表,全名順序存儲(chǔ)結(jié)構(gòu),是線性表的一種。線性表用于存儲(chǔ)邏輯關(guān)系為“一對(duì)一”的數(shù)據(jù),順序表自然也不例外,不僅如此,順序表對(duì)數(shù)據(jù)物理存儲(chǔ)結(jié)構(gòu)也有要求。順序表存儲(chǔ)數(shù)據(jù)時(shí),會(huì)提前申請(qǐng)一整塊足夠大小的物理空間,然后將數(shù)據(jù)依次存儲(chǔ)起來,存儲(chǔ)時(shí)數(shù)據(jù)元素間不留縫隙
    2022-01-01
  • 以Java代碼為例講解設(shè)計(jì)模式中的簡(jiǎn)單工廠模式

    以Java代碼為例講解設(shè)計(jì)模式中的簡(jiǎn)單工廠模式

    簡(jiǎn)單來說,工廠模式就是按照需求來返回一個(gè)類型的對(duì)象,使用工廠模式的意義就是,如果對(duì)象的實(shí)例化與代碼依賴太大的話,不方便進(jìn)行擴(kuò)展和維護(hù),使用工廠的目的就是使對(duì)象的實(shí)例化與主程序代碼就行解耦.來具體看一下:
    2016-05-05
  • JAVA判斷兩個(gè)時(shí)間之間的差

    JAVA判斷兩個(gè)時(shí)間之間的差

    經(jīng)常會(huì)遇到需要判斷兩個(gè)時(shí)間之間的差異的情況,本文主要介紹了JAVA計(jì)算兩個(gè)時(shí)間之間的差,具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-12-12
  • springboot 整合 freemarker代碼實(shí)例

    springboot 整合 freemarker代碼實(shí)例

    這篇文章主要介紹了springboot 整合 freemarker代碼實(shí)例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-10-10
  • Java并發(fā)編程之原子操作類詳情

    Java并發(fā)編程之原子操作類詳情

    這篇文章主要介紹了Java并發(fā)編程之原子操作類詳情,文章基于Java并發(fā)編程展開相關(guān)內(nèi)容,具有一定的參考價(jià)值,需要的小伙伴可以參考一下
    2022-04-04

最新評(píng)論