在Java的应用中,或多或少的都会接触到一些锁,那么问题就来了,在Java中,常见的锁有哪些,都有什么样的作用??

这里给大家简单的简述一下这些学常见的锁。

本文件所涉及到的锁:

1.公平锁 / 非公平锁

2.可重入锁 / 不可重入锁

3.独享锁 / 共享锁

4.互斥锁 / 读写锁

5.乐观锁 / 悲观锁

6.分段锁

7.偏向锁 / 轻量级锁 / 重量级锁

详情如下。

1.1 公平锁 / 非公平锁
1.1.1 公平锁
公平锁是指多个线程按照申请锁的顺序来获取锁。

1.1.2 非公平锁
非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能,会造成优先级反转或者饥饿现象。

对于JavaReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized而言,也是一种非公平锁。由于其并不像ReentrantLock是通过AQS的来实现线程调度,所以并没有任何办法使其变成公平锁。

1.2 可重入锁 / 不可重入锁
1.2.1 可重入锁
广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁

表1

synchronized void setA() throws Exception{

Thread.sleep(1000);

setB();

}

synchronized void setB() throws Exception{

Thread.sleep(1000);

}

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB可能不会被当前线程执行,可能造成死锁。

1.2.2 不可重入锁
不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。看到一个经典的讲解,使用自旋锁来模拟一个不可重入锁,代码如下

表2

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

public void lock() {

   Thread current = Thread.currentThread();for (;;) {if (!owner.compareAndSet(null, current)) {return;}}

}

public void unlock() {

   Thread current = Thread.currentThread();owner.compareAndSet(current, null);

}

}

代码也比较简单,使用原子引用来存放线程,同一线程两次调用lock()方法,如果不执行unlock()释放锁的话,第二次调用自旋的时候就会产生死锁,这个锁就不是可重入的,而实际上同一个线程不必每次都去释放锁再来获取锁,这样的调度切换是很耗资源的。

把它变成一个可重入锁:

表3

import java.util.concurrent.atomic.AtomicReference;

public class UnreentrantLock {

private AtomicReference<Thread> owner = new AtomicReference<Thread>();

private int state = 0;

public void lock() {

  Thread current =  Thread.currentThread();if (current ==  owner.get()) {state++;return;}for (;;) {if  (!owner.compareAndSet(null, current)) {return;}}

}

public void unlock() {

  Thread current =  Thread.currentThread();if (current ==  owner.get()) {if (state != 0) {state--;} else {owner.compareAndSet(current, null);}}

}

}

在执行每次操作之前,判断当前锁持有者是否是当前对象,采用state计数,不用每次去释放锁。

ReentrantLock中可重入锁实现

这里看非公平锁的锁获取方法:

表4

final boolean nonfairTryAcquire(int acquires) {

final Thread current = Thread.currentThread();

int c = getState();

if (c == 0) {

   if (compareAndSetState(0, acquires)) {setExclusiveOwnerThread(current);return true;}

}

//就是这里

else if (current == getExclusiveOwnerThread()) {

   int nextc = c + acquires;if (nextc < 0)throw new Error("Maximum lock count exceeded");setState(nextc);return true;

}

return false;

}

在AQS中维护了一个private volatile int state来计数重入次数,避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

1.3 独享锁 / 共享锁
独享锁和共享锁在你去读C.U.T包下的ReeReentrantLock和ReentrantReadWriteLock你就会发现,它俩一个是独享一个是共享锁。

独享锁:该锁每一次只能被一个线程所持有。

共享锁:该锁可被多个线程共有,典型的就是ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁确每次只能被独占。

另外读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的。

独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

对于Synchronized而言,当然是独享锁。

1.4 互斥锁 / 读写锁
1.4.1 互斥锁
在访问共享资源之前对进行加锁操作,在访问完成之后进行解锁操作。 加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进程解锁。

如果解锁时有一个以上的线程阻塞,那么所有该锁上的线程都被编程就绪状态,第一个变为就绪状态的线程又执行加锁操作,那么其他的线程又会进入等待。 在这种方式下,只有一个线程能够访问被互斥锁保护的资源

1.4.2 读写锁
读写锁既是互斥锁,又是共享锁,read模式是共享,write是互斥(排它锁)的。

读写锁有三种状态:读加锁状态、写加锁状态和不加锁状态

读写锁在Java中的具体实现就是ReadWriteLock

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。

只有一个线程可以占有写状态的锁,但可以有多个线程同时占有读状态锁,这也是它可以实现高并发的原因。当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放;如果是处于读状态锁下,允许其它线程获得它的读状态锁,但是不允许获得它的写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远多于写操作的情况。

1.5 乐观锁 / 悲观锁

1.5.1 悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。

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

1.6 分段锁

分段锁其实是一种锁的设计,并不是具体的一种锁,对于ConcurrentHashMap而言,其并发的实现就是通过分段锁的形式来实现高效的并发操作。

并发容器类的加锁机制是基于粒度更小的分段锁,分段锁也是提升多并发程序性能的重要手段之一。

在并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能。在锁上发生竞争时将通水导致这两种问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式—-每次只能有一个线程能访问它。所以对于可伸缩性来说最大的威胁就是独占锁。

我们一般有三种方式降低锁的竞争程度:

1、减少锁的持有时间

2、降低锁的请求频率

3、使用带有协调机制的独占锁,这些机制允许更高的并发性。

在某些情况下我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这成为分段锁。

其实说的简单一点就是:

容器里有多把锁,每一把锁用于锁容器其中一部分数据,那么当多线程访问容器里不同数据段的数据时,线程间就不会存在锁竞争,从而可以有效的提高并发访问效率,这就是ConcurrentHashMap所使用的锁分段技术,首先将数据分成一段一段的存储,然后给每一段数据配一把锁,当一个线程占用锁访问其中一个段数据的时候,其他段的数据也能被其他线程访问。

比如:在ConcurrentHashMap中使用了一个包含16个锁的数组,每个锁保护所有散列桶的1/16,其中第N个散列桶由第(N mod 16)个锁来保护。假设使用合理的散列算法使关键字能够均匀的分部,那么这大约能使对锁的请求减少到越来的1/16。也正是这项技术使得ConcurrentHashMap支持多达16个并发的写入线程。

1.7 偏向锁 / 轻量级锁 / 重量级锁

锁的状态:

1.无锁状态

2.偏向锁状态

3.轻量级锁状态

4.重量级锁状态

锁的状态是通过对象监视器在对象头中的字段来表明的。

四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。

这四种状态都不是Java语言中的锁,而是Jvm为了提高锁的获取与释放效率而做的优化(使用synchronized时)。

1.7.1 偏向锁
偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降低获取锁的代价。

1.7.2 轻量级
轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

1.7.3 重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

转载于:https://blog.51cto.com/14314169/2389375

Java中常见的锁简述相关推荐

  1. Java中常见的各种锁-超全面

    Java中常见的各种锁(非常全): 原文链接:https://blog.csdn.net/xingchensuiyue/article/details/108716466 乐观锁 乐观锁是一种乐观思想 ...

  2. Java中的互斥锁介绍

    前言   互斥锁是一种广泛应用于多线程编程中的并发控制机制.在Java中,互斥锁有很多不同的实现方式,在本文中我们将介绍Java中常见的几种互斥锁实现方式,并讲解它们的用法.原理和代码案例. sync ...

  3. java 中lock,java中lock获取锁的四种方法

    在java接口中会存放着许多方法,方便线程使用时的直接调用.对于lock接口大家都不陌生,我们已经初步对概念进行了理解.那么在获取锁的方法上想必还不是很清楚.下面我们就lock获取锁的四种方法分别进行 ...

  4. Java基础-JAVA中常见的数据结构介绍

    Java基础-JAVA中常见的数据结构介绍 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.什么是数据结构 答:数据结构是指数据存储的组织方式.大致上分为线性表.栈(Stack) ...

  5. JAVA中常见的Exception

    这篇文章转载自 : JAVA中常见的Exception 常见的几种如下: NullPointerException - 空指针引用异常 ClassCastException - 类型强制转换异常. I ...

  6. Java 中15种锁的介绍:公平锁,可重入锁,独享锁,互斥锁,乐观锁,分段锁,自旋锁等等...

    http://blog.51cto.com/13919357/2339446 Java 中15种锁的介绍 在读很多并发文章中,会提及各种各样锁如公平锁,乐观锁等等,这篇文章介绍各种锁的分类.介绍的内容 ...

  7. java lock unlock_详解Java中的ReentrantLock锁

    ReentrantLock锁 ReentrantLock是Java中常用的锁,属于乐观锁类型,多线程并发情况下.能保证共享数据安全性,线程间有序性 ReentrantLock通过原子操作和阻塞实现锁原 ...

  8. Java中常见的十八种异常!

    作为一只敬业的程序员,就是不能接受我的编程出现bug!可见我们对bug是如此的深恶痛觉!它已成为我们职业生涯中的拦路虎,所以今天小千精心为大家总结一下Java中常见的几种异常,望大家多多注意. 1.j ...

  9. android studio插入数据表中没有_学Java能拿高薪吗 Java中常见排序算法有哪些

    学Java能拿高薪吗?Java中常见排序算法有哪些?作为老牌编程语言,Java拥有广阔的市场占有率,几乎90%以上的大中型互联网应用系统在服务端开发都会首选Java.为了加入到Java这一高薪行业,很 ...

最新文章

  1. iphone个系列尺寸_iPhone 12尺寸对比:又是真香的典范?
  2. python 多进程 循环_python 多进程读取同一个循环处理、可以用multiprocessing
  3. 20220208--CTF MISC--两道简单的MISC题目
  4. mysql小计_使用SQL实现小计,合计以及排序_MySQL
  5. mysql是开放源代码_但这可能是很因难的,因为MySQL是开放源代码的,所以任何人...
  6. oracle服务开机启动,Linux下建立Oracle服务及其开机自启动
  7. 漏洞奖励计划的五大成功要素问答实录
  8. linux实验二目录与文件查看相关命令,Linux文件和目录管理相关命令(二)
  9. C# RestSharp的http连接
  10. mysql 批量插入 性能_MySQL批量插入数据性能比较
  11. CDliux--minidwep 无线密码渗透测试
  12. 蓝桥杯第八届等差素数列
  13. CSS 加粗(css font-weight)
  14. 苹果企业开发者账号申请攻略
  15. android 10.0 Camera2 去掉后置摄像头 仅支持前置摄像头功能
  16. 基于i.mx6q平台的NES模拟器移植
  17. 什么是关键字驱动框架?
  18. 父爱动画代码python_python表白实现代码(可视化与动画版)
  19. YouTube营销活动方案
  20. 解决127.0.0.1 已拒绝连接的方法

热门文章

  1. ecshop目录结构
  2. BZOJ 1176: [Balkan2007]Mokia( CDQ分治 + 树状数组 )
  3. Centos 修改时间地区及NTP同步北京时间
  4. 【数据结构】支持四则混合运算的计算器(转)
  5. Linux系统配置VI或VIM的技巧
  6. HTTP头入门到精通(每一个HTTP消息头解释)
  7. Android如何防止apk程序被反编译
  8. DIV布局SEO的影响
  9. JavaScript正则表达式test的用法
  10. Linux ssh/scp/docker学习