「如何防止java线程锁」java线程锁有几种实现方式
今天给各位分享如何防止java线程锁的知识,其中也会对java线程锁有几种实现方式进行解释,如果能碰巧解决你现在面临的问题,别忘了关注本站,现在开始吧!
本文目录一览:
- 1、java 线程池原理怎样避免线程死锁
- 2、JAVA程序设计,多线程且避免死锁
- 3、JAVA多线程死锁问题
- 4、java怎么避免多线程的死锁? 复制粘贴党请勿回答,谢谢
- 5、如何避免Java多线程中的死锁
- 6、如何避免Java线程死锁
java 线程池原理怎样避免线程死锁
Java线程死锁需要如何解决,这个问题一直在我们不断的使用中需要只有不断的关键。不幸的是,使用上锁会带来其他问题。让我们来看一些常见问题以及相应的解决方法: Java线程死锁 Java线程死锁是一个经典的多线程问题,因为不同的线程都在等待那些根本不可能被释放的锁,从而导致所有的工作都无法完成。假设有两个线程,分别代表两个饥饿的人,他们必须共享刀叉并轮流吃饭。他们都需要获得两个锁:共享刀和共享叉的锁。 假如线程 “A”获得了刀,而线程“B”获得了叉。线程“A”就会进入阻塞状态来等待获得叉,而线程“B”则阻塞来等待“A”所拥有的刀。这只是人为设计的例子,但尽管在运行时很难探测到,这类情况却时常发生。虽然要探测或推敲各种情况是非常困难的,但只要按照下面几条规则去设计系统,就能够避免Java线程死锁问题: 让所有的线程按照同样的顺序获得一组锁。这种方法消除了 X 和 Y 的拥有者分别等待对方的资源的问题。 将多个锁组成一组并放到同一个锁下。前面Java线程死锁的例子中,可以创建一个银器对象的锁。于是在获得刀或叉之前都必须获得这个银器的锁。 将那些不会阻塞的可获得资源用变量标志出来。当某个线程获得银器对象的锁时,就可以通过检查变量来判断是否整个银器集合中的对象锁都可获得。如果是,它就可以获得相关的锁,否则,就要释放掉银器这个锁并稍后再尝试。 最重要的是,在编写代码前认真仔细地设计整个系统。多线程是困难的,在开始编程之前详细设计系统能够帮助你避免难以发现Java线程死锁的问题。 Volatile 变量,volatile 关键字是 Java 语言为优化编译器设计的。以下面的代码为例: 一.class VolatileTest { 二.public void foo() { 三.boolean flag = false; 四.if(flag) { 5.//this could happen 陆.} 漆.} 吧.} 一个优化的编译器可能会判断出if部分的语句永远不会被执行,就根本不会编译这部分的代码。如果这个类被多线程访问, flag被前面某个线程设置之后,在它被if语句测试之前,可以被其他线程重新设置。用volatile关键字来声明变量,就可以告诉编译器在编译的时候,不需要通过预测变量值来优化这部分的代码。 无法访问的Java线程死锁有时候虽然获取对象锁没有问题,线程依然有可能进入阻塞状态。在 Java 编程中IO就是这类问题最好的例子。当线程因为对象内的IO调用而阻塞时,此对象应当仍能被其他线程访问。该对象通常有责任取消这个阻塞的IO操作。造成阻塞调用的线程常常会令同步任务失败。如果该对象的其他方法也是同步的,当线程被阻塞时,此对象也就相当于被冷冻住了。 其他的线程由于不能获得对象的Java线程死锁,就不能给此对象发消息(例如,取消 IO 操作)。必须确保不在同步代码中包含那些阻塞调用,或确认在一个用同步阻塞代码的对象中存在非同步方法。尽管这种方法需要花费一些注意力来保证结果代码安全运行,但它允许在拥有对象的线程发生阻塞后,该对象仍能够响应其他线程。 编辑推荐: 一. Java多线程优化之偏向锁原理分析 二. Java多线程实现异步调用的方法 三. 使用Java多线程机制实现下载的方法介
JAVA程序设计,多线程且避免死锁
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虚拟机无关的操作。 最重要的是,多线程设计虽然是困难的,但在开始编程之前详细设计系统能够帮助你避免难以发现死锁的问题。死锁在语言层面上不能解决,就需要一个良好设计来避免死锁。
JAVA多线程死锁问题
1. Java中导致死锁的原因
Java中死锁最简单的情况是,一个线程T1持有锁L1并且申请获得锁L2,而另一个线程T2持有锁L2并且申请获得锁L1,因为默认的锁申请操作都是阻塞的,所以线程T1和T2永远被阻塞了。导致了死锁。这是最容易理解也是最简单的死锁的形式。但是实际环境中的死锁往往比这个复杂的多。可能会有多个线程形成了一个死锁的环路,比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1,这样导致了一个锁依赖的环路:T1依赖T2的锁L2,T2依赖T3的锁L3,而T3依赖T1的锁L1。从而导致了死锁。
从这两个例子,我们可以得出结论,产生死锁可能性的最根本原因是:线程在获得一个锁L1的情况下再去申请另外一个锁L2,也就是锁L1想要包含了锁L2,也就是说在获得了锁L1,并且没有释放锁L1的情况下,又去申请获得锁L2,这个是产生死锁的最根本原因。另一个原因是默认的锁申请操作是阻塞的。
2. Java中如何避免死锁
既然我们知道了产生死锁可能性的原因,那么就可以在编码时进行规避。Java是面向对象的编程语言,程序的最小单元是对象,对象封装了数据和操作,所以Java中的锁一般也是以对象为单位的,对象的内置锁保护对象中的数据的并发访问。所以如果我们能够避免在对象的同步方法中调用其它对象的同步方法,那么就可以避免死锁产生的可能性。如下所示的代码,就存在死锁的可能性:
public class ClassB {
private String address;
// ...
public synchronized void method1(){
// do something
}
// ... ...
}
public class ClassA {
private int id;
private String name;
private ClassB b;
// ...
public synchronized void m1(){
// do something
b.method1();
}
// ... ...
}
上面的ClassA.m1()方法,在对象的同步方法中又调用了ClassB的同步方法method1(),所以存在死锁发生的可能性。我们可以修改如下,避免死锁:
public class ClassA {
private int id;
private String name;
private ClassB b;
// ...
public void m2(){
synchronized(this){
// do something
}
b.method1();
}
// ... ...
}
这样的话减小了锁定的范围,两个锁的申请就没有发生交叉,避免了死锁的可能性,这是最理性的情况,因为锁没有发生交叉。但是有时是不允许我们这样做的。此时,如果只有ClassA中只有一个m1这样的方法,需要同时获得两个对象上的锁,并且不会将实例属性 b 溢出(return b;),而是将实例属性 b 封闭在对象中,那么也不会发生死锁。因为无法形成死锁的闭环。但是如果ClassA中有多个方法需要同时获得两个对象上的锁,那么这些方法就必须以相同的顺序获得锁。
比如银行转账的场景下,我们必须同时获得两个账户上的锁,才能进行操作,两个锁的申请必须发生交叉。这时我们也可以打破死锁的那个闭环,在涉及到要同时申请两个锁的方法中,总是以相同的顺序来申请锁,比如总是先申请 id 大的账户上的锁 ,然后再申请 id 小的账户上的锁,这样就无法形成导致死锁的那个闭环。
public class Account {
private int id; // 主键
private String name;
private double balance;
public void transfer(Account from, Account to, double money){
if(from.getId() to.getId()){
synchronized(from){
synchronized(to){
// transfer
}
}
}else{
synchronized(to){
synchronized(from){
// transfer
}
}
}
}
public int getId() {
return id;
}
}
这样的话,即使发生了两个账户比如 id=1的和id=100的两个账户相互转账,因为不管是哪个线程先获得了id=100上的锁,另外一个线程都不会去获得id=1上的锁(因为他没有获得id=100上的锁),只能是哪个线程先获得id=100上的锁,哪个线程就先进行转账。这里除了使用id之外,如果没有类似id这样的属性可以比较,那么也可以使用对象的hashCode()的值来进行比较。
上面我们说到,死锁的另一个原因是默认的锁申请操作是阻塞的,所以如果我们不使用默认阻塞的锁,也是可以避免死锁的。我们可以使用ReentrantLock.tryLock()方法,在一个循环中,如果tryLock()返回失败,那么就释放以及获得的锁,并睡眠一小段时间。这样就打破了死锁的闭环。
比如:线程T1持有锁L1并且申请获得锁L2,而线程T2持有锁L2并且申请获得锁L3,而线程T3持有锁L3并且申请获得锁L1
此时如果T3申请锁L1失败,那么T3释放锁L3,并进行睡眠,那么T2就可以获得L3了,然后T2执行完之后释放L2, L3,所以T1也可以获得L2了执行完然后释放锁L1, L2,然后T3睡眠醒来,也可以获得L1, L3了。打破了死锁的闭环。
这些情况,都还是比较好处理的,因为它们都是相关的,我们很容易意识到这里有发生死锁的可能性,从而可以加以防备。很多情况的场景都不会很明显的让我们察觉到会存在发生死锁的可能性。所以我们还是要注意:
一旦我们在一个同步方法中,或者说在一个锁的保护的范围中,调用了其它对象的方法时,就要十而分的小心:
1)如果其它对象的这个方法会消耗比较长的时间,那么就会导致锁被我们持有了很长的时间;
2)如果其它对象的这个方法是一个同步方法,那么就要注意避免发生死锁的可能性了;
最好是能够避免在一个同步方法中调用其它对象的延时方法和同步方法。如果不能避免,就要采取上面说到的编码技巧,打破死锁的闭环,防止死锁的发生。同时我们还可以尽量使用“不可变对象”来避免锁的使用,在某些情况下还可以避免对象的共享,比如 new 一个新的对象代替共享的对象,因为锁一般是对象上的,对象不相同了,也就可以避免死锁,另外尽量避免使用静态同步方法,因为静态同步相当于全局锁。还有一些封闭技术可以使用:比如堆栈封闭,线程封闭,ThreadLocal,这些技术可以减少对象的共享,也就减少了死锁的可能性。
java怎么避免多线程的死锁? 复制粘贴党请勿回答,谢谢
都有人问过这问题了,为什么不能复制粘贴?
产生死锁的原因:一是系统提供的资源数量有限,不能满足每个进程的使用;二是多道程序运行时,进程推进顺序不合理。
产生死锁的必要条件是:1、互斥条件;2、不可剥夺条件(不可抢占);3、部分分配;4、循环等待。
根据产生死锁的四个必要条件,只要使其中之一不能成立,死锁就不会出现。为此,可以采取下列三种预防措施:
1、采用资源静态分配策略,破坏"部分分配"条件;
2、允许进程剥夺使用其他进程占有的资源,从而破坏"不可剥夺"条件;
3、采用资源有序分配法,破坏"环路"条件。
死锁的避免不严格地限制死锁的必要条件的存在,而是系统在系统运行过程中小心地避免死锁的最终发生。最著名的死锁避免算法是银行家算法。死锁避免算法需要很大的系统开销。
解决死锁的另一条途径是死锁检测方法,这种方法对资源的分配不加限制,即允许死锁的发生。但系统定时地运行一个"死锁检测"程序,判断系统是否已发生死锁,若检测到死锁发生则设法加以解除。
解除死锁常常采用下面两种方法:1、资源剥夺法;2、撤消进程法
如何避免Java多线程中的死锁
死锁产生的必要条件
产生死锁必须同时满足以下四个条件,只要其中任一条件不成立,死锁就不会发生。
互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某 资源仅为一个进程所占有。此时若有其他进程请求该资源,则请求进程只能等待。
不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能 由获得该资源的进程自己来释放(只能是主动释放)。
请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源 已被其他进程占有,此时请求进程被阻塞,但对自己已获得的资源保持不放。
循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被 链中下一个进程所请求。即存在一个处于等待状态的进程集合{Pl, P2, ..., pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的资源被P0占有
如何避免Java线程死锁
Lock接口: 出现替代了同步代码块或者同步函数。将同步的隐式锁操作变成现实锁操作。
同时更为灵活。可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,通常需要定义finally代码块中。
Condition接口:出现替代了Object中的wait notify notifyAll方法。
将这些监视器方法单独进行了封装,变成Condition监视器对象。
可以任意锁进行组合。
关于如何防止java线程锁和java线程锁有几种实现方式的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。
发布于:2022-11-27,除非注明,否则均为
原创文章,转载请注明出处。