Hibernate 悲观锁,乐观锁

1.悲观锁

对于时常开Web的Coder们,经常会产生这么的要求,就是以多机的分布式环境下,有时候要限制多玉机器上之求修改及同卖资源。对于单机的环境下,我们常见可以就此一道还是锁去避免多线程下的竞态条件。以java也例,我们好为此synchronized或者ReentrantLock,去开资源访问的一道。但这是JVM和操作系统提供被我们的表征,但是对分布式环境下我们无这些方便条件。所以我们得引入一个标的Observer去贯彻这样的一个分布式锁,Zookeeper是一个比较好之缓解方案,但是Zookeeper还是比重的,我们好就此Redis实现如此一个锁。
开朗锁基于CAS思想,是无抱有互斥性,不会见产生锁等而消耗资源,但是急需反复的重试,但为是因重试的编制,能比快之应。在促成CAS之前,需要了解一下Redis的作业机制。
Redis事务:
咱俩可为此Mysql事务机制来理解Redis的事情机制,但为大相径庭,Mysql的事体之花样如下:
openSession()
update()
insert()
commit()
假定以update和insert之间出现谬误,那么会触发rollback(),Redis的作业用到了MULTI和EXEC命令,事务的花样如下:
MULTI
SET
HSET
EXEC
和Mysql的工作不同,Redis会将所有EXEC命令之前的通令放入一个QUEUE中,当遇到EXEC时批量尽QUEUE中的命,但是
Redis的事务是勿支持回滚的,它只有是各个的执行命令,并批量回到结果,但是对于极端情况下,事务在没了执行完时宕机,导致事情日志只写副部分,这样在还开时见面有错误,用aof的修补工具修复后好开展启动。
每当了解了工作机制后,我们还不足以实现乐观锁,还需了解一个指令——Watch,Watch命令可以监控Redis中的一个key,当Key发生变化时停下事务的付出。先看一个不错的例子:

它们借助的凡针对性数码被外面修改持保守态度。假定任何时刻存取数据时,都或发生其他一个客户为正存取同一笔数额,为了保持数据被操作的一致性,于是对数据以了数据库层次的锁定状态,依靠数据库提供的锁机制来实现。
根据jdbc实现的数据库加锁如下:

127.0.0.1:6379> set locktest 1
OK
127.0.0.1:6379> get locktest
"1"
127.0.0.1:6379> watch locktest
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set locktest 3
QUEUED
127.0.0.1:6379> exec
1) OK
127.0.0.1:6379> get locktest
"3"
127.0.0.1:6379> 
select * from account where name="Erica" for update

然于multi的历程遭到使locktest的值发生变化又会什么?

在创新的进程遭到,数据库处于加锁状态,任何其他的对此数据的操作都将为延缓。本次事务提交后解锁。
如若hibernate悲观锁的现实性实现如下:

127.0.0.1:6379> set locktest 1
OK
127.0.0.1:6379> get locktest
"1"
127.0.0.1:6379> watch locktest
OK
127.0.0.1:6379> multi
OK
127.0.0.1:6379> set locktest 3
QUEUED
127.0.0.1:6379> exec
(nil)
127.0.0.1:6379> get locktest
"2"
127.0.0.1:6379> 
String sql="查询语句";
Query query=session.createQuery(sql);
query.setLockMode("对象",LockModel.UPGRADE);

此我们就此另外一个Client在Multi以后以locktest修改也2,课件在实践工作之时段回来吗nil,表示执行破产。
那么我们即便得用上述两栽命令实现一个乐观主义锁,代码如下:

说及这里,就涉及了hibernate的加锁模式:

package com.redis.lock;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
import redis.clients.jedis.Transaction;
/**
 * topic:利用redis的事务,实现一个乐观锁
 * 
 * @author zhiming
 *
 */
public class RedisWatchLock {

    private static final String redisHost = "10.0.5.86";

    private static final int port = 6381;

    private static JedisPoolConfig config;

    private static JedisPool pool;

    private static ExecutorService service;

    private static int ThLeng=10;

    private static CountDownLatch latch;

    private static AtomicInteger Countor = new AtomicInteger(0);
    static{
        //利用Redis连接池,保证多个线程利用多个连接,充分模拟并发性
        config = new JedisPoolConfig();
        config.setMaxIdle(10);
        config.setMaxWaitMillis(1000);
        config.setMaxTotal(30);
        pool = new JedisPool(config, redisHost, port);
        //利用ExecutorService 管理线程
        service = Executors.newFixedThreadPool(10);
        //CountDownLatch保证主线程在全部线程结束之后退出
        latch = new CountDownLatch(ThLeng);
    }

    public static void main(String args[]){
        int ThLeng = 10;
        String ThreadNamePrefix = "thread-";
        Jedis cli = pool.getResource();
        cli.del("redis_inc_key");//先删除既定的key
        cli.set("redis_inc_key", String.valueOf(1));//设定默认值
        for(int i =0;i<ThLeng;i++){
            Thread th = new Thread(new TestThread(pool));
            th.setName(ThreadNamePrefix+i);
            System.out.println(th.getName()+"inited...");
            service.submit(th);
        }
        service.shutdown();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("all sub thread sucess");
        System.out.println("countor is "+Countor.get());
        String countStr = cli.get("redis_inc_key");
        System.out.println(countStr);
    }

    public static class TestThread implements Runnable {
        private String incKeyStr = "redis_inc_key";
        private Jedis cli;
        private JedisPool pool;
        public TestThread(JedisPool pool) {
            cli = pool.getResource();
            this.pool = pool;

        }
        public void run() {
            try{

                for (int i = 0; i < 100; i++) {
                    actomicAdd();
                }
            }catch(Exception e){
                pool.returnBrokenResource(cli);
            }
            finally{
                pool.returnResource(cli);
                latch.countDown();
            }
        }

        public void actomicAdd(){
            cli.watch(incKeyStr);
            boolean flag =true;
            while(flag){
                String countStr = cli.get("redis_inc_key");
                int countInt = Integer.parseInt(countStr);
                int expect = countInt+1;
                Transaction tx = cli.multi();                   
                tx.set(incKeyStr, String.valueOf(expect));
                List<Object> list = tx.exec();
                //如果事务失败了exec会返回null
                if(list==null){
                    System.out.println("multi shut down");
                    continue;
                }
                else{
                    //如果达到期望值那么结束while循环
                    flag=false;
                }
                System.out.println("my expect num is "+expect);         
                System.out.println("seting....");   
            }
            Countor.incrementAndGet();  
        }

    }

}

LockMode.NONE:无锁机制。
LockMode.WRITE:Hibernate在Insert和Update记录的下会自行取。
LockMode.READ:Hibernate在读取记录的时候会活动获取。

这么咱们尽管采用Redis实现了一个类于Java
的原子类的效益。在事实上的Web开发中,我们得以动用redis来解决资源重复修改或者争用的题目。

立刻三栽加锁模式是供hibernate内部采用的,与数据库加锁无关:

LockMode.UPGRADE:利用数据库的for update字句加锁。

以此地我们要小心的是:只有在询问开始前(也就算是hiernate生成sql语句之前)加锁,才会真的通过数据库的锁机制加锁处理。否则,数据都经过非分包for updata子句的sql语句加载进来,所谓的数据库加锁也即无从谈起。
唯独
是,从网的习性上考虑,对于单机或稍系统而言,这并不成问题,然而如果是以网达到的系统,同时间会有为数不少联袂,假设来巨额或上千竟然又多的连发访
问出现,我们该怎么处置?如果当及数据库解锁我们再开展下的操作,我们浪费的资源是多少?–这为就造成了乐观锁的发出。

2.乐观锁

开展锁定(optimistic locking)则有望的觉得资料之存取很少发同时存取的题目,因而不作数据库层次上之锁定,为了掩护正确的数目,乐观锁定以应用程序上之逻辑实现版本控制的不二法门。

譬如说若发生少单客户端,A客户先念博了账户余额100首,之后B客户也读取了账户余额100长之数码,A客户提取了50冠,对数据库作了改动,此时频繁
据库中之余额吗50处女,B客户呢只要提取30首先,根据其所抱的素材,100-30以为70余额,若此时再次针对数据库进行反,最后的余额就会见无科学。

当匪执行悲观锁定策略的状下,数据未均等的情一模一样而来,有几乎单缓解之法门,一栽是预先更新为主,一种植是后更新的为主,比较复杂的便是检查出转移的多少来促成,或是检查有着属性来实现乐观锁定。

Hibernate 中经版本号检查来促成后更新为主,这吗是Hibernate所推荐的法子,在数据库被在一个VERSON栏记录,在读取数
据时会同版本号并读取,并以创新数据时递增版本号,然后于对版本号与数据库被的版本号,如果超出数据库中之本号则给更新,否则就报错误。

坐刚才之例子,A客户读取账户余额1000长,并系读取版本号为5的言辞,B客户这呢读取账号余额1000正,版本号为为5,A客户以领款后账户
余额为500,此时用本号加1,版本号目前为6,而数据库中版本号为5,所以与更新,更新数据库后,数据库此时余额吗500,版本号为6,B客户领款
后要转移数据库,其版本号为5,但是数据库的版本号为6,此时反对更新,B客户数量更读取数据库中新的数额并更展开业务流程才转移数据库。

因为Hibernate实现版本号决定锁定的话,我们的靶子中益一个version属性,例如:

public class Account {
private int version;
....
public void setVersion(int version) {
this.version = version;
}
public int getVersion() {
return version;
}
....
}

而当映像文件中,我们利用optimistic-lock属性设定version控制,<id>属性栏之后多一个<version>标签,如下:

<hibernate-mapping>
<class name="onlyfun.caterpillar.Account" talble="ACCOUNT"
optimistic-lock="version">
<id...../>
<version name="version" column="VERSION"/>
....
</class>
</hibernate-mapping>

设定好版本控制之后,在上例中假如B 客户准备更新数据,将见面吸引StableObjectStateException例外,我们得捕捉这个例
外,在拍卖着再度读取数据库中之数据,同时将 B客户时的数量和数据库被之数据秀出来,让B客户发出机遇比对匪一样的数码,以控制使转的部份,或者你可
以设计程式自动读取新的资料,并重新扣款业务流程,直到数据可创新了,这总体得在背景执行,而未用为您的客户理解。

唯独乐观锁也发免可知缓解之题材是:上面都关系过乐观锁机制的实现多次因系统遭到之数存储逻辑,在我们的体系受贯彻,来自外部系统的用户余额更
新不给我们系的决定,有或致地下数据为更新至数据库。因此我们以做电子商务的当儿,一定要是小心的注目这项在的题目,采用比较合理的逻辑验证,避免
数据实施错误。

也可于以Session的load()或是lock()时指定锁定模式为开展锁定。

比方数据库不支持所指定的锁定模式,Hibernate会选择一个宜的锁定替换,而不是丢出一个差。

相关文章

发表评论

电子邮件地址不会被公开。 必填项已用*标注