Java死锁、活锁,悲观锁、乐观锁

简介: Java死锁、活锁,悲观锁、乐观锁

1、死锁与活锁的区别,死锁与饥饿的区别?

死锁:是指两个或两个以上的进程(或线程)在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。


产生死锁的必要条件:


互斥条件:所谓互斥就是进程在某一时间内独占资源。


请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。


不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。  


循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。


活锁:任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试,失败,尝试,失败。


活锁和死锁的区别在于,处于活锁的实体是在不断的改变状态,所谓的“活”, 而处于死锁的实体表现为等待;活锁有可能自行解开,死锁则不能。


饥饿:一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行的状态。


Java中导致饥饿的原因:


高优先级线程吞噬所有的低优先级线程的CPU时间。


线程被永久堵塞在一个等待进入同步块的状态,因为其他线程总是能在它之前持续地对该同步块进行访问。


线程在等待一个本身也处于永久等待完成的对象(比如调用这个对象的wait方法),因为其他线程总是被持续地获得唤醒。


2、乐观锁和悲观锁的理解及如何实现,有哪些实现方式?

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。


乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。


乐观锁的实现方式:


使用版本标识来确定读到的数据与提交时的数据是否一致。提交后修改版本标识,不一致时可以采取丢弃和再次尝试的策略。


java中的Compare and Swap即CAS ,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。 CAS 操作中包含三个操作数 —— 需要读写的内存位置(V)、进行比较的预期原值(A)和拟写入的新值(B)。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置值更新为新值B。否则处理器不做任何操作。


CAS缺点:  


ABA问题:


比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且two进行了一些操作变成了B,然后two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后one操作成功。尽管线程one的CAS操作成功,但可能存在潜藏的问题。从Java1.5开始JDK的atomic包里提供了一个类AtomicStampedReference来解决ABA问题。


循环时间长开销大:


对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。


只能保证一个共享变量的原子操作:


当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁。


3、SynchronizedMap和ConcurrentHashMap有什么区别?

SynchronizedMap一次锁住整张表来保证线程安全,所以每次只能有一个线程来访为map。


ConcurrentHashMap使用分段锁来保证在多线程下的性能。ConcurrentHashMap中则是一次锁住一个桶。ConcurrentHashMap默认将hash表分为16个桶,诸如get,put,remove等常用操作只锁当前需要用到的桶。这样,原来只能一个线程进入,现在却能同时有16个写线程执行,并发性能的提升是显而易见的。


另外ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。


相关文章
|
4月前
|
Java 程序员
Java程序员-你真的了解死锁吗
Java程序员-你真的了解死锁吗
37 0
|
5月前
|
存储 安全 算法
java 悲观锁、乐观锁
java 悲观锁、乐观锁
30 0
|
7月前
|
存储 Java
java之线程死锁和ThreadLocal的使用
java之线程死锁和ThreadLocal的使用
|
7月前
|
算法 Java 数据库
Java 最常见的面试题:说一下乐观锁和悲观锁
Java 最常见的面试题:说一下乐观锁和悲观锁
|
3月前
|
Java
Java并发编程中的死锁问题及解决方法
【2月更文挑战第9天】在Java并发编程中,死锁是一种常见但又令人头疼的问题。本文将深入探讨死锁产生的原因,以及针对不同情况所提供的解决方法,帮助读者更好地理解和应对死锁。
|
2月前
|
Java
Java并发编程中的死锁问题及解决方法
【2月更文挑战第11天】 在Java并发编程中,死锁是一个常见但又非常棘手的问题。本文将深入探讨死锁的概念、产生原因以及常见的解决方法,帮助读者更好地理解并发编程中的挑战,并提供实用的解决方案。
40 6
|
3天前
|
Java
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
【Java多线程】分析线程加锁导致的死锁问题以及解决方案
11 1
|
15天前
|
算法 Java 开发者
【专栏】在Java并发编程中,死锁是一个常见且棘手的问题
【4月更文挑战第27天】本文探讨了Java并发编程中的死锁问题,阐述了死锁的基本概念、产生原因及常见场景,并提出了五种解决死锁的策略:避免嵌套锁、设置超时时间、制定锁顺序、检测与恢复死锁以及使用高级并发工具。理解死锁原理和采取相应措施能有效提升并发程序的效率和安全性。在实践中,注重线程协作、选择合适并发工具及框架,有助于降低死锁风险,实现高效并发系统。
|
1月前
|
监控 算法 安全
Java并发编程案例分析:死锁的检测与解决
【4月更文挑战第6天】Java并发编程中的死锁导致线程僵持,资源无法释放,影响程序性能。死锁涉及互斥、请求与保持、不剥夺和循环等待四个条件。案例分析展示了银行转账场景下的死锁可能。检测死锁可通过日志、代码审查和使用工具。解决策略包括避免死锁(如设定锁顺序、超时机制)和处理死锁(如终止线程、资源抢占)。理解死锁原理并采取预防措施对构建稳定、高效的多线程应用至关重要。
|
1月前
|
Java
Java并发-如何避免死锁
一般在Java项目里用到锁的场景不多,有朋友调侃说用到锁的次数还没有面试被问到的次数多,哈哈!
Java并发-如何避免死锁