解决多线程下锁不住String问题
文章目录
业务场景
同一时间只能保证有一个线程在修改User信息因此加了Synchronized锁,锁住Student中name(String类型),但由于每个线程的name都不是同一个对象,因此锁不住
/**
* 修改用户信息
* @param student
* @return
*/
public int update(Student student){
synchronized (student.getName()){
try {
//模拟业务操作执行5秒
Thread.sleep(5000);
//打印当前时间,如果两条线程打印时间间隔没有超过5秒,证明没锁住
log.info(Thread.currentThread().getName()+"提交修改,当前时间为:"+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return 1;
}
解决思路
1. 用String的intern方法,
该调用该方法则会在String常量池中寻找值跟String一样,同时返回它的地址的String,如果没有找到,则创建一个地址,返回该地址String
/**
* 修改用户信息
* @param student
* @return
*/
public int update(Student student){
synchronized (student.getName().intern()){ //调用intern方法寻找同值常量
try {
//模拟业务操作执行5秒
Thread.sleep(5000);
//打印当前时间,如果两条线程打印时间间隔没有超过5秒,证明没锁住
log.info(Thread.currentThread().getName()+"提交修改,当前时间为:"+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return 1;
}
结果如图:
优点:
- 实现简单,方便
缺点:
- 很明显,调用一次intern方法,就进行一次String常量池进行扫描,效果可想而知
2. 利用其他常量池,例如Integer(-128~127)
取String的hash对127取模,锁住该Interger常量
/**
* 修改用户信息
* @param student
* @return
*/
public int update(Student student){
Integer hash = (student.getName().hashCode())%128; //计算hash
log.info(Thread.currentThread().getName()+"当前hash为:"+hash); //打印hash
synchronized (hash){ //hash必定是-127~127,因此地址是取自Integet常量池中,锁住它
try {
//模拟业务操作执行5秒
Thread.sleep(5000);
//打印当前时间,如果两条线程打印时间间隔没有超过5秒,证明没锁住
log.info(Thread.currentThread().getName()+"提交修改,当前时间为:"+new Date());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return 1;
}
结果:
优点:
- 不需要浪费其他的内存空间,直接使用Integet常量池
缺点:
-
常量池太小 只有254个,很容易造成不同hashCode 取模后碰撞情况,一次碰撞就需要等待一个业务执行的时间
经测试:随机500个字符串,最大碰撞个数为6个,执行所有线程时间为6次业务时长
3. 使用ConcurrentHashMap+信号量方式
public enum SaveDataLock {
APPLICANT
;
//静态map,存放各种需要锁的字段
private static final ConcurrentHashMap<String, ConcurrentHashMap<String, Semaphore>> lockMap = new ConcurrentHashMap<>();
private static final Object SYN_STR = new Object();
//为静态map赋值
static {
for(SaveDataLock lock : SaveDataLock.values()) {
SaveDataLock.lockMap.put(lock.name(), new ConcurrentHashMap<>());
}
}
public ConcurrentMap<String, Semaphore> getMap() {
return SaveDataLock.lockMap.get(this.name());
}
//获取锁
public Semaphore lock(String lockStr) {
ConcurrentHashMap<String, Semaphore> locks = SaveDataLock.lockMap.get(this.name());
Semaphore lock = locks.get(lockStr);
if(lock == null) {
synchronized (SYN_STR) {
lock = locks.get(lockStr);
if(lock == null) {
lock = new Semaphore(1);
locks.put(lockStr, lock);
}
}
}
//使该线程不允许被中断
lock.acquireUninterruptibly();
return lock;
}
//释放锁
public void release(String lockStr, Semaphore lock) {
if(lock == null) {
return ;
}
lock.release();
//判断是否有线程在等待该信号量被释放
if(!lock.hasQueuedThreads()) {
ConcurrentHashMap<String, Semaphore> locks = SaveDataLock.lockMap.get(this.name());
locks.remove(lockStr);
}
}
}
获取锁:
当调用获取锁方法时,将会传入一个string字符串参数作为key,在Map中进行查找,如果不为空,则等待别人释放,如果为空则申请信号量操作
释放锁:
当调用释放锁方法时,将锁释放,再判断是否有再等待的线程,如果没有,则删除该信号量
4. 利用Redis分布式锁解决
利用Redis操作的原子性,模拟获取锁和解锁,道理跟信号量差不多,网上大把分布式锁例子,在这里不做阐述。