对用户ID相同进行加锁的几种方法

目录

对用户ID相同进行加锁的几种方法


对用户ID相同进行加锁的几种方法

package com.nft.service.lock;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 锁优化
 * <br>SoftReference 软引用
 * <br>WeakReference 弱引用
 * @Description:LockUtils
 * @author:callen
 * @date:2022年3月15日 上午9:36:59
 */
public class LockUtils {
	// 对其相同的玩家ID进行加锁
	private static Map<Long, Long> locks = new ConcurrentHashMap<>();
	// 用户登录的时候,防止同一邮箱注册出现错误
	private static Map<String, String> locks_login = new ConcurrentHashMap<>();
	// 当玩家进入系统的时候,需要锁住机器人的ID,防止相同的玩家获取到同一个机器人信息
	private static Map<Integer, Integer> locks_replace_reboot = new HashMap<>();
	// 卡牌的体力
	private static Map<Long, Long> locks_power = new ConcurrentHashMap<>();
	// 同步srar系统中的玩家信息
	private static Map<String, String> LOCKS_SYN_STAR = new ConcurrentHashMap<>();
	//给玩家发放奖励的时候,防止玩家写入同一个类型的奖励ID
	private static Map<Long, Long> LOCKS_GRANT_REWARD = new ConcurrentHashMap<>();
	//用于防止玩家的积分丢失
	private static Map<Long, SoftReference<ReentrantLock>> LOCKS_USER_INTEGRAL = new ConcurrentHashMap<>();
	
	public static void getLockByUserIntegral(final long userId) {
		ReentrantLock lock=null;
		SoftReference<ReentrantLock> softReference=LOCKS_USER_INTEGRAL.get(userId);
		if(softReference==null) {
			synchronized (LOCKS_USER_INTEGRAL) {
				softReference=LOCKS_USER_INTEGRAL.get(userId);
				if(softReference==null) {
					lock=new ReentrantLock();
					softReference = new SoftReference<ReentrantLock>(lock);
					LOCKS_USER_INTEGRAL.put(userId, softReference);
				}
			}
		}
		softReference.get().lock();
	}

	public static void unLockByUserIntegral(final long userId) {
		SoftReference<ReentrantLock> lock=LOCKS_USER_INTEGRAL.get(userId);
		lock.get().unlock();
	}
	
	public static Object getLockByGrantReward(final long userId) {
		LOCKS_GRANT_REWARD.putIfAbsent(userId, userId);
		return LOCKS_GRANT_REWARD.get(userId);
	}

	public static void unLockByGrantReward(final long userId) {
		LOCKS_GRANT_REWARD.remove(userId);
	}
	
	public static Object getLockBySynStar(final String synUserId) {
		LOCKS_SYN_STAR.putIfAbsent(synUserId, synUserId);
		return LOCKS_SYN_STAR.get(synUserId);
	}

	public static void unLockBySynSstar(final String synUserId) {
		LOCKS_SYN_STAR.remove(synUserId);
	}

	public static synchronized boolean getLockByReplaceReboot(final Integer rebootId) {
		if (locks_replace_reboot.get(rebootId) == null) {
			locks_replace_reboot.put(rebootId, rebootId);
			return true;
		}
		return false;
	}

	public static void unLockByReplaceReboot(final Integer rebootId) {
		locks_replace_reboot.remove(rebootId);
	}

	public static Object getLockByLogin(final String email) {
		locks_login.putIfAbsent(email, email);
		return locks_login.get(email);
	}

	public static void unLockByLogin(final String email) {
		locks_login.remove(email);
	}

	public static Object getLockByPower(final long id) {
		locks_power.putIfAbsent(id, id);
		return locks_power.get(id);
	}

	public static void unLockByPower(final long id) {
		locks_power.remove(id);
	}

	// 对其相同的玩家ID进行加锁
	public static Object getCacheSyncObject(final long id) {
		locks.putIfAbsent(id, id);
		return locks.get(id);
	}

	public static void unLock(final long id) {
		locks.remove(id);
	}
}

用于解决我们在项目中对同一个用户的资源进行加锁的时候


比如:在项目开发过程中,由多中情况在对同一个邮箱进行注册的时候,我们只对单个邮箱进行加锁,而不对所有的邮箱字符串加锁,再或者用户在支付的时候,不能多个浏览器进行同时支付吧,这个时候,就需要进行锁操作了,对单独的一个用户进行上锁,进行一次性的支付,预防用户的资产不足,而对其他用户不受操作影响
 

第一种方案:

	private static Map<Long, Long> LOCKS_GRANT_REWARD = new ConcurrentHashMap<>();
	public static Object getLockByGrantReward(final long userId) {
		LOCKS_GRANT_REWARD.putIfAbsent(userId, userId);
		return LOCKS_GRANT_REWARD.get(userId);
	}

	public static void unLockByGrantReward(final long userId) {
		LOCKS_GRANT_REWARD.remove(userId);
	}

如何使用呢?

synchronized(LockUtils.getLockByGrantReward(userId)) {
	try {
		grantReward(rewardGoodsPO, userId);
	}finally {
		LockUtils.unLockByGrantReward(userId);
	}
}

        ConcurrentHashMap java8对其进行了优化,分段锁机制,当然了,使用HashMap也是可以的,但这边对其移除的时候,没有做更多的代码操作。使用 ConcurrentHashMap 的 putIfAbsent 进行操作放入数据,因为 如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null。所以 putIfAbsent 使用的还是之前的数据,从而达到synchronized 锁住相同的数据。

第二种方案

使用的是String的常量池,但这里有个问题是,如果用户ID非常庞大,则不是很适合。

synchronized("XX".intern()) {//防止重复的文件放到相同目录下
	// TODO
});

String 的 intern 返回的是常量池的数据,所以使用Synchronized 的时候,使用的还是相同的数据。
 

第三种方案

使用hashmap方式

	private static Map<Integer, Integer> locks_replace_reboot = new HashMap<>();
	public static synchronized boolean getLockByReplaceReboot(final Integer rebootId) {
		if (locks_replace_reboot.get(rebootId) == null) {
			locks_replace_reboot.put(rebootId, rebootId);
			return true;
		}
		return false;
	}

	public static void unLockByReplaceReboot(final Integer rebootId) {
		locks_replace_reboot.remove(rebootId);
	}

使用方法

try {
	if(LockUtils.getLockByReplaceReboot(homeRebootId)){
		// TODO
	}
}finally {
	LockUtils.unLockByReplaceReboot(homeRebootId);			
}

第四种方案

// SoftReference 软引用 ,在内存不足的情况下,进行垃圾回收
private static Map<Long, SoftReference<ReentrantLock>> LOCKS_USER_INTEGRAL = new ConcurrentHashMap<>();
	public static void getLockByUserIntegral(final long userId) {
		ReentrantLock lock=null;
		SoftReference<ReentrantLock> softReference=LOCKS_USER_INTEGRAL.get(userId);
		if(softReference==null) {
			synchronized (LOCKS_USER_INTEGRAL) {
				softReference=LOCKS_USER_INTEGRAL.get(userId);
				if(softReference==null) {
					lock=new ReentrantLock();
					softReference = new SoftReference<ReentrantLock>(lock);
					LOCKS_USER_INTEGRAL.put(userId, softReference);
				}
			}
		}
		softReference.get().lock();
	}

	public static void unLockByUserIntegral(final long userId) {
		SoftReference<ReentrantLock> lock=LOCKS_USER_INTEGRAL.get(userId);
		lock.get().unlock();
	}

使用方法
这种方法出现异常信息,空指针异常,不推荐使用

try {
	LockUtils.getLockByUserIntegral(userId);
	updateOrInsertUserIntergral(userId, intergral, plan);
}finally {
	LockUtils.unLockByUserIntegral(userId);
}

第五种方法

使用redission的分布式锁进行操作,缺点是需要引入redis服务端,推荐使用

private RedissonClient redissonClient;
/**
	 * 当前锁存在的时间,默认时间为5秒
	 */
	private static final int LEASE_TIME=5;
	
	/**
	 * 再次获取锁的周期
	 */
	private static final int GET_LOCK_CYCLE=1;
	
    public boolean lock(String lockKey) throws InterruptedException {
        RLock lock = redissonClient.getLock(lockKey);
        //等待0秒,进行获取锁,获取锁失败,则后面代码不进行执行
        boolean flag=lock.tryLock(GET_LOCK_CYCLE, LEASE_TIME, TimeUnit.SECONDS);
        if(!flag) {//获取锁失败
        	log.error("get lock is error in five second,lock resource:{}",lockKey);
        }
        return flag;
    }
    
    public void unlock(String lockKey) {
        RLock lock = redissonClient.getLock(lockKey);
        lock.unlock();
    }


总之,在使用的时候,应该避免在synchronized ()中使用对象.属性这种操作,否则会出现空指针异常的错误信息,应该在外面定义一个final的属性或引用。

在Spring WebFlux中使用锁对数据库数据进行加锁,可以借助数据库的事务和悲观锁机制来实现。 一种常见的方式是使用Spring的声明式事务管理来控制数据库操作,并在事务中使用悲观锁(Pessimistic Locking)来保证数据的一致性和并发性。 首先,确保你的应用配置了数据库事务管理器。可以使用Spring Boot自动配置,或者手动配置`PlatformTransactionManager`的实例。 然后,在需要进行数据加锁方法上添加`@Transactional`注解,表示该方法需要在一个事务中进行操作。同时,通过使用数据库的特定语法来进行悲观锁的加锁操作。 以下是一个示例,演示了在Spring WebFlux中使用锁对数据库数据进行加锁方法: ```java @Service public class DataService { private final UserRepository userRepository; public DataService(UserRepository userRepository) { this.userRepository = userRepository; } @Transactional public User getUserByIdWithLock(Long id) { User user = userRepository.findById(id); if (user != null) { userRepository.lockUserById(id); // 使用数据库特定语法进行悲观锁的加锁操作 } return user; } @Transactional public void updateUser(User user) { userRepository.save(user); } } ``` 在上面的示例中,`getUserByIdWithLock`方法通过调用`userRepository.findById(id)`获取用户对象,并在获取到用户对象后调用`userRepository.lockUserById(id)`方法加锁。`lockUserById`方法是一个自定义的方法,使用数据库特定的语法来进行悲观锁的加锁操作。 另外,`updateUser`方法使用了`@Transactional`注解,确保在更新用户对象时也处于同一个事务中。 请注意,具体的悲观锁语法取决于你使用的数据库类型和版本,可以参考对应数据库的文档进行使用。 通过使用事务和悲观锁机制,你可以在Spring WebFlux中对数据库数据进行加锁,以实现并发控制和数据一致性的需求。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值