对用户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的属性或引用

当然了,如有不足之处,请指出

  • 5
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
Java中,可以使用synchronized关键字来实现加锁。synchronized可以用来修饰方法或代码块,当一个线程进入一个被synchronized修饰的方法或代码块时,该线程会自动获取锁,其他线程则必须等待该线程释放锁后才能进入该方法或代码块。以下是一个使用synchronized关键字进行加锁的示例: ``` public class Test { private Object lock = new Object(); private int count = 0; public void increment() { synchronized (lock) { count++; } } } ``` 在数据库中,可以使用事务来实现加锁。事务是指一系列的数据库操作,这些操作要么全部执行成功,要么全部失败回滚。在事务中,可以使用锁机制来保证数据的一致性和完整性。以下是一个使用事务进行加锁的示例: ``` Connection conn = null; try { conn = dataSource.getConnection(); conn.setAutoCommit(false); // 开启事务 PreparedStatement stmt = conn.prepareStatement("UPDATE account SET balance = balance - 100 WHERE id = ?"); stmt.setInt(1, accountId); stmt.executeUpdate(); stmt = conn.prepareStatement("UPDATE account SET balance = balance + 100 WHERE id = ?"); stmt.setInt(1, targetAccountId); stmt.executeUpdate(); conn.commit(); // 提交事务 } catch (SQLException e) { conn.rollback(); // 回滚事务 } finally { if (conn != null) { conn.close(); } } ``` 在上面的示例中,使用了conn.setAutoCommit(false)来开启事务,并且使用了conn.commit()来提交事务,使用了conn.rollback()来回滚事务。在事务中,通过使用PreparedStatement来执行SQL语句,从而实现对数据库的加锁操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值