JAVA中几种常见锁及对策:
解决锁没有简单的方法,这是因为线程产生锁都各有各的原因,而且往往具有很高的负载。大多数软件测试产生不了足够多的负载,所以不可能暴露所有的线程错误。在这里中,下面将讨论开发过程常见的4类典型的锁和解决对策。
(1)数据库锁
在数据库中,如果一个连接占用了另一个连接所需的数据库锁,则它可以阻塞另一个连接。如果两个或两个以上的连接相互阻塞,则它们都不能继续执行,这种情况称为数据库锁。
数据库锁问题不易处理,通常数据行进行更新时,需要锁定该数据行,执行更新,然后在提交或回滚封闭事务时释放锁。由于数据库平台、配置的隔离级以及查询提示的不同,获取的锁可能是细粒度或粗粒度的,它会阻塞(或不阻塞)其他对同一数据行、表或数据库的查询。基于数据库模式,读写 *** 作会要求遍历或更新多个索引、验证约束、执行触发器等。每个要求都会引入更多锁。此外,其他应用程序还可能正在访问同一数据库模式中的某些对象,并获取不同应用程序所具有的锁。
所有这些因素综合在一起,数据库锁几乎不可能被消除了。值得庆幸的是,数据库锁通常是可恢复的:当数据库发现锁时,它会强制销毁一个连接(通常是使用最少的连接),并回滚其事务。这将释放所有与已经结束的事务相关联的锁,至少允许其他连接中有一个可以获取它们正在被阻塞的锁。
由于数据库具有这种典型的锁处理行为,所以当出现数据库锁问题时,数据库常常只能重试整个事务。当数据库连接被销毁时,会抛出可被应用程序捕获的异常,并标识为数据库锁。如果允许锁异常传播到初始化该事务的代码层之外,则该代码层可以启动一个新事务并重做先前所有工作。
当出现问题就重试,由于数据库可以自由地获取锁,所以几乎不可能保证两个或两个以上的线程不发生数据库锁。此方法至少能保证在出现某些数据库锁情况时,应用程序能正常运行。
(2)资源池耗尽锁
客户端的增加导致资源池耗尽锁是由于负载而造成的,即资源池太小,而每个线程需要的资源超过了池中的可用资源。假设连接池最多有10个连接,同时有10个对外部并发调用。这些线程中每一个都需要一个数据库连接用来清空池。现在,每个线程都执行嵌套的调用。则所有线程都不能继续,但又都不放弃自己的第一个数据库连接。这样,10个线程都将被锁。
研究此类锁,会发现线程存储中有大量等待获取资源的线程,以及同等数量的空闲且未阻塞的活动数据库连接。当应用程序锁时,如果可以在运行时检测连接池,就能确认连接池实际上已空。
修复此类锁的方法包括:增加连接池的大小或者重构代码,以便单个线程不需要同时使用很多数据库连接。或者可以设置内部调用使用不同的连接池,即使外部调用的连接池为空,内部调用也能使用自己的连接池继续。
(3)单线程、多冲突数据库连接锁
对同一线程执行嵌套的调用有时出现锁,此情形即使在非高负载系统中通常也会发生。当第一个(外部)连接已获取第二个(内部)连接所需要的数据库锁,则第二个连接将永久阻塞第一个连接,并等待第一个连接被提交或回滚,这就出现了锁情形。因为数据库没有注意到两个连接之间的关系,所以数据库不会将此情形检测为锁。这样即使不存在并发,此代码也将导致锁。此情形有多种具体的变种,可以涉及多个线程和两个以上的数据库连接。
(4)Java虚拟机锁与数据库锁冲突
这种情形发生在数据库锁与Java虚拟机锁并存的时候。在这种情况下,一个线程占有一个数据库锁并尝试获取Java虚拟机锁。同时,另一个线程占有Java虚拟机锁并尝试获取数据库锁。此时,数据库发现一个连接阻塞了另一个连接,但由于无法阻止连接继续,所以不会检测到锁。Java虚拟机发现同步的锁中有一个线程,并有另一个尝试进入的线程,所以即使Java虚拟机能检测到锁并对它们进行处理,它还是不会检测到这种情况。
总而言之,JAVA应用程序中的锁是一个大问题——它能导致整个应用程序慢慢终止,还很难被分离和修复,尤其是当开发人员不熟悉如何分析锁环境的时候。
五 锁的经验法则
笔者在开发中总结以下锁问题的经验。
(1) 对大多数的Java程序员来说最简单的防止锁的方法是对竞争的资源引入序号,如果一个线程需要几个资源,那么它必须先得到小序号的资源,再申请大序号的资源。可以在Java代码中增加同步关键字的使用,这样可以减少锁,但这样做也会影响性能。如果负载过重,数据库内部也有可能发生锁。
(2)了解数据库锁的发生行为。假定任何数据库访问都有可能陷入数据库锁状况,但是都能正确进行重试。例如了解如何从应用服务器获取完整的线程转储以及从数据库获取数据库连接列表(包括互相阻塞的连接),知道每个数据库连接与哪个Java线程相关联。了解Java线程和数据库连接之间映射的最简单方法是向连接池访问模式添加日志记录功能。
(3)当进行嵌套的调用时,了解哪些调用使用了与其它调用同样的数据库连接。即使嵌套调用运行在同一个全局事务中,它仍将使用不同的数据库连接,而不会导致嵌套锁。
(4)确保在峰值并发时有足够大的资源池。
(5)避免执行数据库调用或在占有Java虚拟机锁时,执行其他与Java虚拟机无关的 *** 作。
最重要的是,多线程设计虽然是困难的,但在开始编程之前详细设计系统能够帮助你避免难以发现锁的问题。锁在语言层面上不能解决,就需要一个良好设计来避免锁。
你思路有问题。其实这个问题很好解决。就是一个缓存和数据库同步的问题。有两个方案供你参考,当然具体代码自己去写,我只提供你思路。
一、SQL
SERVER2005以上版本的依赖式缓存,这个解决方案就是当数据库的数据有更新的时候会自动更新到缓存里。具体怎么配置,网上查找下,教程很多。
二、自己写数据库和缓存的数据同步代码。当客户端把更新数据库的数据 *** 作的时候同时更新缓存里的数据。毕竟写比读少了很多。但是在写缓存的时候,必须注意一个问题,那就是公共缓存对象并发控制的问题,别出现死锁,或者脏读,幻读这类的线程锁出错的问题。。。记住一定要在写缓存的时候锁住它。。。数据库其实在写的时候也是排它锁的。只不过被数据库系统自身处理掉了。你写数据库数据时感觉不到而已。
一般来说,就是如果系统不是严格要求缓存+数据库必须一致性的话,缓存可以稍微的跟数据库偶尔有不一致的情况,最好不要做这个方案,读请求和写请求串行化,串到一个内存队列里去,这样就可以保证一定不会出现不一致的情况
串行化之后,就会导致系统的吞吐量会大幅度的降低,用比正常情况下多几倍的机器去支撑线上的一个请求。
同步的原理是在oracle上面更新的数据会通过trigger捕获记录下来,然后通过cache agent定期来获取这些信息同步到TT。
而TT上的同步则是通过分析TT的 *** 作日志来获得做了那些DML *** 作,然后通过cache agent同步到oracle
二者数据同步的关键在于mysql数据库中主键,方案是在redis启动时区mysql读取所有表键值存入redis中,往redis写数据是,对redis主键自增并进行读取,若mysql更新失败,则需要及时清除缓存及同步redis主键。
参考代码如下:
String tbname = "login";
//获取mysql表主键值--redis启动时
long id = MySQLgetID(tbname);
//设置redis主键值--redis启动时
redisServiceset(tbname, StringvalueOf(id));
Systemoutprintln(id);
long l = redisServiceincr(tbname);
Systemoutprintln(l);
Login login = new Login();
loginsetId(l);
loginsetName("redis");
redisServicehmset(StringvalueOf(logingetId()), login);
boolean b = MySQLinsert("insert into login(id,name) values(" + logingetId()
+ ",'" + logingetName() + "')");
/
队列处理器更新mysql失败:
清除缓存数据,同时主键值自减
/
if (!b)
{
redisServicedelKeyAndDecr
(tbname, "Login:"+StringvalueOf(logingetId()));
// redisServicedelete("Login:"+StringvalueOf(logingetId()));
//redisServicedecr(tbname);
}
Systemoutprintln(redisServiceexists("Login:"+StringvalueOf(logingetId())));
Systemoutprintln(redisServiceget(tbname));
以上就是关于java中保证数据缓存的情况下使用读写锁还是同步好些全部的内容,包括:java中保证数据缓存的情况下使用读写锁还是同步好些、.net中用了缓存,如何判断数据库的数据是否有变动,然后更新缓存、如何保证缓存与数据库双写时的数据一致性等相关内容解答,如果想了解更多相关内容,可以关注我们,你们的支持是我们更新的动力!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)