java开发中锁的一些概念简述

排他锁(独占锁)

锁独占,使用排他锁 则只能由获取锁的一个线程 执行该语句块,其他线程只能等待锁释放后竞争锁

共享锁

共享锁,能由多个线程同时获取锁,并发执行;(可能会问 多个线程并发执行了还加锁干嘛,例如读锁 和写锁,读锁是共享锁,可以多个线程同时获取锁,读锁是为了排斥写锁,当有线程获取读锁的时候 其他线程不能获取写锁)

读写锁

读锁: 也就是共享锁, 多个线程可以同时获取读锁,当有线程在读数据的时候,其他线程不能写
写锁: 排他锁,线程获取写锁后,其他线程不能读
读读可以多线程并发, 读写 , 写读, 写写 都是 互斥的

自旋锁

自旋锁:是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环;
减少了 线程上下文切换的性能消耗,但是如果某个线程持有锁的时间过长,就会导致其它等待获取锁的线程进入循环等待,消耗CPU。使用不当会造成CPU使用率极高;
如果获取锁自选的时间过长就会一直造成cpu空转,消耗资源,所以,我们需要给自旋加一个限定次数,不能让它这样一直的这样转下去。如果超过了这个次数还没获得就要被挂起,但是很有可能会出现一种问题,就是我们刚刚被挂起的时候占用锁的线程刚好释放了锁(这就是特别的缘分),那这样就非常尴尬了。所以在JDK6的时候有了自适应自旋锁

自适应自旋锁

** 把以前定死的自选次数给变成动态的,让自旋的次数不再是固定的了,而是根据上一次在同一个锁的自旋时间和锁的拥有者来判断。
线程如果自旋成功了,那么下一次自旋的次数会更多,因为我们会认为,既然上次已经等待成功了,那么这次的自旋也很有可能成功,于是会增加自选的次数
线程如果自旋失败了,那么下次这个锁就会对这个锁的自旋次数会减少,如果很多线程获取这个锁都自旋失败了很可能导致线程在下一次可能根本就不会去自旋,直接跳过。以免浪费CPU的资源**

偏向锁

偏向锁 是为了 在线程竞争非常少 或者只有一个线程在获取锁释放锁的时候 造成的加锁释放锁的性能消耗的一种解决办法;为了减少锁的获取和释放的资源浪费。
偏向锁只有初始化时需要一次CAS,如果当前线程获取锁偏向锁后则以后可以零成本直接获取锁,而不用参与锁竞争;如果有其他线程竞争,则会升级为轻量级锁

轻量级锁

采用CAS算法进行数据操作,不会加锁,如果出现线程竞争问题 才加锁升级为重量级锁;
举例:b线程在锁竞争时,发现锁已经被a线程占用,则b线程不进入内核态,让b线程自旋,执行空循环,等待a线程释放锁。如果,完成自旋策略还是发现a线程没有释放锁,或者让c线程占用了。则b线程试图将轻量级锁升级为重量级锁。
优点: 竞争的线程不会阻塞,提高了程序的响应速度
缺点: 如果线程一直竞争不到锁,就会一直存于自旋空执行状态,消耗cpu

重量级锁

内置锁在Java中被抽象为监视器锁(monitor)。在JDK 1.6之前,监视器锁可以认为直接对应底层操作系统中的互斥量(mutex)。这种同步方式的成本非常高,包括系统调用引起的内核态与用户态切换、线程阻塞造成的线程切换等。因此,后来称这种锁为“重量级锁。
举例:synchronized使用的就是内置锁,重量级锁

死锁

死锁是多线程竞争条件下,线程之间互相等待释放锁而造成的线程假死状态,线程一直处于等待状态;
产生死锁的必要条件

  1. 互斥条件;即存在多线程对共享资源的竞争且 在一段时间内某资源仅为一进程所占用
  2. 请求和保持条件: 当线程阻塞时,对获取的资源保持不放
  3. 不剥夺条件: 加锁后资源未使用完毕不释放,不可剥夺,只能时使用完毕后自己释放资源
  4. 环路等待条件: 当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。
    解决死锁的基本方法
    1. 就是破坏其中的一个或者多个产生死锁的条件
    2. 顺序获取锁
    3. 超时放弃 例如使用 tryLock(long time, TimeUnit unit)

公平锁和非公平锁

公平和非公平锁的队列都基于锁内部维护的一个双向链表,表结点Node的值就是每一个请求当前锁的线程。公平锁则在于每次都是依次从队首取值。非公平锁,线程在等待锁的过程中, 如果有任意新的线程妄图获取锁,都是有很大的几率直接获取到锁的获取锁是随机的 而不是按照队列中的顺序的。

其他

  • 活锁: 线程之间 互相让步,导致的 谁都执行不下去

  • 线程饿死:线程长时间获取不到CPU的执行时间片,而导致的一直不能执行的现象

  • 线程饥饿: 由于线程优先级 或其 出现线程独占资源不释放,其他线程只能一直处于等待阻塞状态

  • 锁消除: 是jvm在编译时对锁进行的一种优化,比如有时候我们写的代码其实不用加锁,却执行了加锁操作 比如时对局部变量的同步加锁操作;
    也就是说对非共享对象上进行同步是无效的,因此runtime可以啥也不做。同步有可能是不需要的,这为优化提供了机会。
    因此,如果逃逸分析发现对象是非逃逸的,编译器就可以自行消除同步。

  • 锁粗化: 通常情况下并发编程中,我们会把锁的力度尽量缩小范围,但是在某些情况下 高频率的加锁和释放锁 会严重影响 性能问题,所以出现了 把多个锁合并成一个 把原来每个小范围的加锁操作 合并成能覆盖到原来各个锁的范围的一个锁,扩大了锁范围 不用频繁的加锁释放锁操作而带来的 性能损耗。
    锁粗化就是告诉我们任何事情都有个度,有些情况下我们反而希望把很多次锁的请求合并成一个请求,以降低短时间内大量锁请求、同步、释放带来的性能损耗

  • 锁升级: 例如轻量级锁升级为 重量级锁;或者读锁升级为写锁 ,

  • 锁降级: 写锁降级为读锁,这只是开发中可能会用到的。并不是jvm的锁优化涉及的

猜你喜欢

转载自blog.csdn.net/xiaodujava/article/details/101529342