java多线程下,并发更新数据时存在的安全问题,以及解决方案。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/zhanglei082319/article/details/88027484

       在实际项目中,往往会存在许多并发安全问题,以及并发访问效率问题。

       比如,在多个用户同时更新同一条数据的时候,往往会出现线程安全问题,实际保存的数据和预期的并不相符。面对这种问题很多人第一时间会想到内置锁---->synchronized关键字,将方法体或者代码块锁住保证线程执行的同步。这样就能从根本上解决并发的线程安全问题。

        但是,如果直接使用synchronized锁住代码块。会对程序访问性能产生很大的影响。比如:多个线程同时访问欲要更新的数据并不相同,不同的数据更新之间是不会存在线程安全问题的,完全可以非阻塞执行。但是使用synchronized关键字之后,被锁住的代码块一定会阻塞执行。

        那么我们就会想,可不可以拥有一种在操作同一数据时可以阻塞执行,不同数据时非阻塞执行的方案呢?

        有!

        请看如下代码

   1)显示锁

        

public class LockValue {

    /**
     * 显示锁集合
     */
    private static  ConcurrentHashMap<Object,ReentrantLock> lockMap = new ConcurrentHashMap<>();


    private static ReentrantLock  getLock(Object code){
        ReentrantLock lock = new ReentrantLock();
        //如果同步集合中已经存在锁,则直接返回已经存在的锁
        ReentrantLock preLock = lockMap.putIfAbsent(code,lock);
        //如果添加成功则使用新锁
        return preLock == null ? lock : preLock;
    }


    public static void lock(Object code){
        getLock(code).lock();
    }


    public static void unlock(Object code){
        ReentrantLock lock = getLock(code);
        if(lock.isHeldByCurrentThread()){
            lock.unlock();
        }
    }
}

       这是一个由ConcurrentHashMap为容器的对显示锁的一种封装。我们可以看到在getLock(code)方法中,该方法的逻辑是这样的:

        如果ConcurrentHashMap容器中可以通过code(键)找到对应的ReentrantLock(锁),我们就用找到的锁。反之,如果找不到,则创建一把新锁。简单的说通过这个工具我们可以保证,同一个code获取到的锁都是同一把锁,不同的code获取的锁都不相同。

       2)内置锁

          synchronized(value){}

        那么问题就很简单了,我们都知道java中同一把锁一旦被线程使用,如果不释放,其他线程是不可以使用该锁的,这就可以保证线程的同步阻塞。而不同的锁相当于不同的对象,如果每个线程所持有的锁都是不同的,那么自然不存在不可以使用的问题,这样就可以保证非阻塞执行了。

       但是

       这种解决方案只可以在单实例服务情况下解决并发线程安全问题,也就是说如果是多服务负载均衡的情况下,这种在代码层次上控制方案是无效的。

        那么在多服务实例下,有方案可以解决这种问题吗?

       1)从数据库方面入手,如果是更新操作,则对数据库表添加一个version字段,每次执行更新时以该字段为条件并且对该字段递增,这样下一个语句如果version不同,就不会执行成功。(这也是常说的数据库乐观锁)。如果是新增操作,为了避免数据重复,可以添加主键索引。

       2)采用redis缓存,使用java锁机制将缓存中的键锁住,如果缓存中没有键,则将键添加到缓存中,在代码块执行完毕,删除缓存。这样可以保证数据的安全性,缓存技术也可以保证执行的高效率。

 

     但是

      这种解决方案只是在集群下可以适用,但是在分布式下也是无效的,那么怎么办来?

     这里要说声抱歉,分布式情况下的线程安全问题目前我也不知道怎么解决,真要说的话只能从项目的业务逻辑调度入手,尽量避免针对同一条数据访问不同的分布式数据库的情况。或者可以利用hash的一致性算法,获取需要操作数据的hash值,寻找离它最近的数据库服务器。这样也是可以避免的。

       

展开阅读全文

没有更多推荐了,返回首页