适用场景
在一些场景下,我们需要对某一个值进行加锁,比如支付的订单id,同一时间一个订单只允许一个线程进行操作。
以下是本篇文章正文内容,下面案例可供参考
一、synchronized(this)
@GetMapping("thread")
public void thread(String id) {
synchronized (this) {
System.out.println(id + "进来了");
try {
//三秒后
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(id + "走了");
}
}
示例:通过synchronized(this)进行加锁,可以实现同一时间只能有一个线程进入到代码中来,实现了对id的锁,但是有一个问题,不同的id都不能被同时执行,肯定是不满足高并发场景的。
1进来了
1走了
2进来了
2走了
二、synchronized(Object)
把synchronized(this)修改为synchronized(id.intern())就可以实现对某一个值进行加锁了(注意这里需要使用Object.intern(),不然每个线程的id都是不同的对象)
1进来了
2进来了
1走了
2走了
这样就可以实现每个多个线程同时执行了,但是因为默认string.intern是jni调用了c++接口,常量池是类似于hashmap的存在,默认大小是1009,所以当数据量越大,hash冲突越严重,链表越长,所以也不推荐这种写法
三、借用对象加锁
代码如下(示例):
private static final Map<String, Object> lockMap = new ConcurrentHashMap<>();
@GetMapping("thread")
public void thread(String id) {
Object o = lockMap.computeIfAbsent(id, k -> new Object());
synchronized (o) {
System.out.println(id + "进来了");
try {
//执行业务
Thread.sleep(1);
} finally {
lockMap.remove();
}
System.out.println(id + "走了");
}
}
经验证后发现,当并发小于等于200时没问题,大于200时就有几率出现问题,原因是Spring默认的线程数量最大是200,超过200就会先处理200,完成一个再放一个进来,新放进来的会创建一个新的锁对象,导致并发执行。
四、最终版
private static Map<String, ReentrantLock> lockMap = new ConcurrentHashMap<>();
@GetMapping("thread")
public void thread(String id) {
ReentrantLock o = null;
do {
//开始执行先unlock
if (o != null) {
o.unlock();
}
o = lockMap.computeIfAbsent(id, k -> new ReentrantLock());
//加锁
o.lock();
//新创建的被上一个线程remove掉了,或者新创建的对象和lockMap中已有的不是同一个对象,重试
} while (lockMap.get(id) == null || o != lockMap.get(id));
System.out.println(id + "进来了");
try {
//执行业务
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
//释放锁
lockMap.remove(id);
}
System.out.println(id + "走了");
}