多线程——解决多线程锁不住String问题

解决多线程下锁不住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;
}

结果如图:

image-20200923094029018

优点:

  1. 实现简单,方便

缺点:

  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;
}

结果:

image-20200923094802908

优点:

  1. 不需要浪费其他的内存空间,直接使用Integet常量池

缺点:

  1. 常量池太小 只有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操作的原子性,模拟获取锁和解锁,道理跟信号量差不多,网上大把分布式锁例子,在这里不做阐述。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值