1.redis
setnx lock 1
如果lock 有值,就会返回0,反之返回1.假如100个人同时执行这个命令,因为redis是单线程的,顺序执行,只有一个人会操作成功并返回1,其他人都是0. 获取锁的线程,执行完del lock 释放锁.
如何防止死锁?
1.redis宕机,阻塞后怎么办,一直del不掉,别人也获取不到lock,在setnx 时设定时间,有效期,超时后自动删除.别人就能获取锁了
2.在当前线程中又是一段同步代码,又会来执行setnx lock 一直都是0.这样线程会一直不释放.这样这个锁就是一个不可重入锁,是一个互斥锁.当前线程拿到了锁,在线程内部的同步代码块中又去找锁,拿到锁找锁,这就阻塞了,死锁了.
在setnx lock 的时候value可以设置成自己的ip.线程中进行判断,如果是自己的ip,说明自己拿到了锁,继续执行下去,此时就是可重入锁.
redission这个分布式框架已经完全实现了分布式锁,直接调用就可以了.
2.ZooKeeper
ZooKeeper是靠节点来存储,而每个节点又是唯一的,很多线程去创建同一个节点,只有一个能创建成功,用创建节点来代替setnx,这样谁创建成功,谁获取锁.结束后删除锁节点就行了.
如何防止死锁呢,创建ZooKeeper的临时节点,服务器宕机,那就与ZooKeeper断开连接,锁节点会自动删除.
如何让锁可重入呢,将自己的唯一标识,或者说ip存入到节点中,获取锁的时候判断是不是自己已经拿到了锁,if true,就继续执行.
悲观锁:在写代码时,就判断线程安全问题一定会发生,所以在设计代码之处就给线程上了锁.让线程间串行执行.
乐观锁:举个例子,线程中去操作数据库,在商品下单成功后去减少库存,如果多个线程同时操作,必然会出现线程安全问题,而此时又不给线程加锁,如何处理呢.乐观锁有很多种解决方案,这里说一种,就是在sql操作的时候,加上一条AND判断条件.
UPDATE table_stock SET stock = stock -1 WHERE good_id = ? AND stock = 100
在操作数据库的时候去查询最新的库存,判断,当前库存是多少,只有当库存是指定数量时,才能操作成功.加入库存已经被别人操作过了,不是100,而是99了,这条语句自然会失败.
UPDATE table_stock SET stock = WHEN stock > 1 THEN stock -1 ELSE stock END
另一种情况,只有当库存大于1的时候,才能去进行减1操作,否则走原本的值.
所以乐观锁本质就是在操作数据时,给当前操作的数据一个版本version,就相当于上文中说道了stock.读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
乐观锁相比于悲观锁的好处就是不用给线程加上锁,保持线程的独占性,使用悲观锁的时候,因为线程的独占性,其他所有线程都要阻塞,无法操作数据库中的数据,乐观锁很好的解决了这一问题.如果查询出的版本号不同,就需要重新读取别人修改过的数据后的版本号.只有当版本号正确才成正确操作数据.