项目中需要调用第三方接口,调用时需要携带token;而token会两个小时失效一次.
原有的逻辑是调用三方接口时,如果返回token失效就先获取token后再调用三方接口;
对获取token操作进行加锁
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author kismet
* @since 2019-10-30 16:32
*/
public class HttpOperate {
private static String token;
private static ReentrantLock lock = new ReentrantLock();
public static void sendHttp() {
// 获取token
getToken();
System.out.printf("执行http请求操作,token:%s%n", token);
}
private static void getToken() {
// 模拟token失效情况
if (token == null) {
// 加锁 如果是集群模式则要使用分布式锁
if (!lock.tryLock()) {
return;
}
try {
getTokenFromHttp();
} catch (Exception e) {
System.out.println("获取token异常" + Thread.currentThread().getName());
throw new RuntimeException("获取token异常" + Thread.currentThread().getName());
} finally {
System.out.println("释放锁" + Thread.currentThread().getName());
// 避免远程调用失败锁一直不释放导致自旋一直执行
lock.unlock();
}
}
}
// 模拟远程调用获取token
private static void getTokenFromHttp() {
try {
// 模拟网络延时
Thread.sleep(1L);
// 模拟网络故障
// int a = 1 / 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s线程获取token中%n", Thread.currentThread().getName());
token = UUID.randomUUID().toString();
}
}
测试类
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*
* @author kismet
* @since 2019-10-30 16:49
*/
public class LockTest {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>());
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
threadPool.submit(HttpOperate::sendHttp);
}
}
测试结果::
执行http请求操作,token:null
执行http请求操作,token:null
执行http请求操作,token:null
执行http请求操作,token:null
执行http请求操作,token:null
pool-1-thread-1线程获取token中
执行http请求操作,token:null
执行http请求操作,token:null
释放锁pool-1-thread-1
执行http请求操作,token:ffa8e8a4-ab87-4f01-abbc-1249182ae180
如上面测试结果所示,在获取token期间的其他请求都会是操作失败;
假如要确保请求不丢失,同时允许线程适当等待,就可以使用自旋锁的方式来解决
import org.apache.commons.lang3.StringUtils;
import java.util.UUID;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author kismet
* @since 2019-10-30 16:32
*/
public class HttpOperate {
private static String token;
private static ReentrantLock lock = new ReentrantLock();
public static void sendHttp() {
// 获取token
getToken();
System.out.printf("执行http请求操作,token:%s%n", token);
}
private static void getToken() {
// 模拟token失效情况
if (token == null) {
// 加锁 如果是集群模式则要使用分布式锁
if (!lock.tryLock()) {
// 当其他线程在获取token时该线程自旋
while (StringUtils.isBlank(token) && lock.isLocked()) {
System.out.println("自旋中" + Thread.currentThread().getName());
}
System.out.println("自旋完成" + Thread.currentThread().getName());
if (token != null) {
return;
}
}
try {
getTokenFromHttp();
} catch (Exception e) {
System.out.println("获取token异常" + Thread.currentThread().getName());
throw new RuntimeException("获取token异常" + Thread.currentThread().getName());
} finally {
System.out.println("释放锁" + Thread.currentThread().getName());
// 避免远程调用失败锁一直不释放导致自旋一直执行
lock.unlock();
}
}
}
// 模拟远程调用获取token
private static void getTokenFromHttp() {
try {
// 模拟网络延时
Thread.sleep(1L);
// 模拟网络故障
// int a = 1 / 0;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s线程获取token中%n", Thread.currentThread().getName());
token = UUID.randomUUID().toString();
}
}
测试结果:
自旋中pool-1-thread-3
自旋中pool-1-thread-3
自旋中pool-1-thread-3
自旋中pool-1-thread-3
自旋中pool-1-thread-3
自旋中pool-1-thread-4
自旋中pool-1-thread-8
自旋中pool-1-thread-8
自旋中pool-1-thread-8
自旋中pool-1-thread-7
自旋中pool-1-thread-6
自旋中pool-1-thread-2
自旋中pool-1-thread-5
自旋完成pool-1-thread-2
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
释放锁pool-1-thread-1
自旋中pool-1-thread-4
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
自旋中pool-1-thread-3
自旋完成pool-1-thread-3
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
自旋完成pool-1-thread-4
自旋完成pool-1-thread-7
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
自旋完成pool-1-thread-6
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
自旋完成pool-1-thread-5
自旋完成pool-1-thread-8
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
执行http请求操作,token:6a908de8-8e43-47ca-804b-c4564661ee01
如代码所示,在获取token期间;其他调用第三方的请求就是自旋等待,
直到锁释放或者获取到token;
这样就避免了请求丢失的情况.