Skip to content

Latest commit

 

History

History
127 lines (102 loc) · 6.12 KB

什么是并发问题.md

File metadata and controls

127 lines (102 loc) · 6.12 KB

多个进程或线程同时(或着说在同一段时间内)访问同一资源会产生并发问题。

银行两操作员同时操作同一账户就是典型的例子。比如A、B操作员同时读取一余额为1000元的账户,A操作员为该账户增加100元,B操作员同时为该账户减去 50元,A先提交,B后提交。 最后实际账户余额为1000-50=950元,但本该为 1000+100-50=1050。这就是典型的并发问题。

2 如何处理。

1、synchronized

​ synchronized是Java中的关键字,在Java中,所有实例对象都自动含有单一的锁(也称监视器)。所以,当使用synchronized修饰的方法的时候,该方法会自动给实例加锁,这个时候,其他含synchronized的方法必须等到该方法调用结束并释放锁之后,该方法才能够被调用。简单来说,就是同一时间内,只能有一个线程访问 synchronized修饰的方法或者代码块,保证了原子性、有序性和可视性。

synchronized (this) {
    Map<String, Object> map = jdbcTemplate.queryForMap("select * from test limit 1");
    int value = Integer.parseInt(map.get("value").toString());
    if (value > 0) {
        value--;
        int update = jdbcTemplate.update("update test set value=?", value);
        if (update>0){
            System.err.println("成功");
        }
    }
}

2、悲观锁

​ for update是一种行级锁,又叫排它锁,一旦用户对某个行为加了行级加锁,则该用户可以查询也可以更新被加锁的数据行,其它用户只能查询但不能更新被加锁的数据行.如果其它用户想更新该表中的数据行,则也必须对该表施加行级锁.即使多个用户对一个表均使用了共享更新,但也不允许两个事务同时对一个表进行更新,真正对表进行更新时,是以独占方式锁表,一直到提交或复原该事务为止。行锁永远是独占方式锁。

/**
 * for update 数据库锁 仅适用于InnoDB,并且必须开启事务,在begin与commit之间才生效,会阻塞其他事务
 */
@Transactional(rollbackFor = Exception.class)
public void buy1() throws InterruptedException {
    Map<String, Object> map = jdbcTemplate.queryForMap("select * from test limit 1 for update");
    int value = Integer.parseInt(map.get("value").toString());
    if (value > 0) {
        value--;
        int update = jdbcTemplate.update("update test set value=?", value);
        if (update>0){
            System.err.println("成功");
        }
    }
}

3、乐观锁

​ 乐观锁( Optimistic Locking ) 相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。那么我们如何实现乐观锁呢,一般来说有以下2种方式:

​ 1.使用数据版本(Version)记录机制实现,这是乐观锁最常用的一种实现方式。何谓数据版本?即为数据增加一个版本标识,一般是通过为数据库表增加一个数字类型的 “version” 字段来实现。当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

Map<String, Object> map = jdbcTemplate.queryForMap("select * from test limit 1");
int value = Integer.parseInt(map.get("value").toString());
int version = Integer.parseInt(map.get("version").toString());
if (value > 0) {
    value--;
    int newVersion = version+1;
    int update = jdbcTemplate.update("update test set value=?,version=? where version=?", value, newVersion,version);
    if (update > 0) {
        System.err.println("成功");
    }
}

​ 2.乐观锁定的第二种实现方式和第一种差不多,同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp), 和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。

Map<String, Object> map = jdbcTemplate.queryForMap("select * from test limit 1");
int value = Integer.parseInt(map.get("value").toString());
Long nanoTime = Long.parseLong(map.get("nano_time").toString());
if (value > 0) {
    value--;
    long newNanoTime = System.nanoTime();
    int update = jdbcTemplate.update("update test set value=?,nano_time=? where nano_time=?", value, newNanoTime,nanoTime);
    if (update > 0) {
        System.err.println("成功");
    }
}

4、Redis分布式锁

​ * 分布锁满足两个条件,一个是加有效时间的锁,一个是高性能解锁

​ * 采用redis命令setnx(set if not exist)、setex(set expire value)实现

img

RLock lock=redisson.getLock(key);
try {
    // 1. 最常见的使用方法
    //lock.lock();
    // 2. 支持过期解锁功能,10秒钟以后自动解锁, 无需调用unlock方法手动解锁
    //lock.lock(10, TimeUnit.SECONDS);
    // 3. 尝试加锁,最多等待2秒,上锁以后8秒自动解锁
    boolean result = lock.tryLock(2, 8, TimeUnit.SECONDS);
    if (!result) {
        System.err.println("失败");
        return "error";
    } else {
        Map<String, Object> map = jdbcTemplate.queryForMap("select * from test limit 1");
        int value = Integer.parseInt(map.get("value").toString());
        if (value > 0) {
            value--;
            int update = jdbcTemplate.update("update test set value=?", value);
            if (update > 0) {
                System.err.println("成功");
            }
        }
    }
} catch (InterruptedException e) {
    e.printStackTrace();
} finally {
    lock.unlock();
}