Java中锁你知道多少?一文带你走进锁的世界!

java中的锁你知道多少?

       Java中的锁主要用于保障多线程在多并发情况下保持数据的一致性。在多线程编程中为了保障数据的一致性,我们通常需要在使用对象或者调用方法之前加锁,这时候如果有其他的线程也需要使用该对象或者调用该方法,则需要先获得锁,如果某个线程发现锁正在别其他线程使用,就会进入阻塞队列等待锁的释放,直到其他线程执行完成后释放锁 ,改线程才会有机会再次获取锁并执行操作。这样就保障了在同一时刻只有一个线程持有改对象的锁并修改该对象,从而保障数据的安全。

​锁是如何分类的呢?在这里插入图片描述

       锁从乐观和悲观的角度来看可以分为乐观锁和悲观锁,从获取资源的公平性角度可分为公平锁和非公平锁,从是否共享资源的角度可分为共享锁和独占锁,从锁的状态的绝度可分为偏向锁,轻量级锁和重量级锁。同时还有自旋锁让你更快使用CPU资源。

  1. 乐观锁
           乐观锁通常都默认为别人不会修改该数据,所以不会加锁,但是在更新的时候会判断再次期间有没有别人动过该数据,如果再次期间有人动过该数据,则不会去更新,直接返回失败状态,是采用CAS(Compare And Swap,比较和交互)原理实现的;具体实现思路为:比较当前版本号与上一次版本号,如果版本号一致,则更新;如果版本号不一致则重复进行读、比较、写等操作。 在这里插入图片描述
           使用乐观锁有可能会产生ABA问题,需要多注意一下,CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么这个时间差会导致数据的变化。
           比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存位置V中取出A,并且线程2进行了一些操作将值变成了B,然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中V的位置仍是A,然后线程1操作成功。尽管线程1的CAS操作成功,但是不代表这个过程就是没问题的。
  2. 悲观锁
           悲观锁则是采用悲观思想,每次读取数据时都会认为别人会修改数据,所以每次在读写数据时都会加锁,这样别人要想修改这个数据时,就必须等待他释放锁之后才能修改,是采用AQS(Abstract Queued Synchronized,抽象的队列同步器)架构实现。AQS定义了一套多线程访问共享资源的同步框架,许多同步类都采用它的思想实现的,该框架下的锁会先尝试以CAS乐观锁去获取锁,如果获取不到,则会转为悲观锁。在这里插入图片描述
    如果有需要的小伙伴可以看看这篇文章哦! 点击查看

  1. 悲观锁
           自旋锁的实现思想是如果线程能在很短的时间内释放资源,那么那些等待竞争的线程就不需要做内核态和用户态之间的切换进入阻塞、挂起状态,只需要等一等,在等待持有锁的线程释放锁之后即可立即获取锁,这样就避免了用户线程在用户态和内核态之间的频繁切换而导致的时间消耗。
           自旋锁可以减少CPU上下文的切换,对于占用锁的时间非常短或者锁竞争不激烈的代码块中来说性能大幅度提升,因为自旋的CPU消耗明显小于线程阻塞、挂起、再唤醒时两次CPU上下文切换所用的时间。
           但是在持有锁的线程占用锁时间过长或锁的竞争过于激烈时,线程在自旋的过程中会长时间获取不到资源,将引起CPU的浪费。所以在JDK1.5的时候设置了一个固定的时间,默认自旋锁循环10次若还未得到锁就升级为重量级锁,JDK1.6之后则引入了适应性自旋锁(不是固定时间,而是由上一次在同一个锁上的自旋时间及锁的拥有者状态来决定的)。
    在这里插入图片描述
  2. synchronized关键字
           synchronized属于独占式锁,同时属于可重入锁,被synchronized修饰对象、方法、代码块时,同一时刻只能有一个线程去进行访问或执行。
    synchronized的作用范围:
           1.作用于成员变量和非静态方法,锁住的是对象的实例,即this对象。
           2.作用于静态方法,锁住的是Class实例。
           3.作用于代码块,锁住的是所有代码块中配置的对象。
  3. 可重入锁
           可重入锁指该锁能够支持一个线程对同一个资源执行多次加锁操作。也叫做递归锁;在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class)。
    可重入锁的意义便在于防止死锁!!!
  4. 公平锁
           公平锁指在分配锁前检查是否有线程在排队等待获取该锁,优先将锁分配给排队时间最长的线程。
  5. 非公平锁
           非公平锁指在分配锁时不考虑线程排队等待的情况,直接尝试获取锁,在获取不到锁时再排到队尾等待。
  6. ReentrantLock
           ReentrantLock继承了Lock接口并实现了接口中定义的方法,是一个可重入的独占锁。是基于AQS来实现锁的获取与释放。
           ReentrantLock支持公平锁和非公平锁。
           ReentrantLock之所以被称为可重入锁,是因为ReentrantLock锁可以反复的进入。即允许连续两次获得同一把锁,两次释放同一把锁。
  7. 读写锁
           读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥。在读的地方使用读锁,在写的地方使用写锁,在没有写锁的情况下,读是无阻塞的。
           一般的做法是定义一个读锁和一个写锁。在读取共享数据是用读锁,在使用完之后释放读锁,在写共享数据时使用写锁,使用完后释放。
public class TestLock{

	private final Map<String, Object> tLock= new HashMap<String, Object>();
	private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
	
	//读锁
	private final Lock readLock = rwLock.readLock();
	//写锁
	private final Lock writeLock = rwLock.writeLock();

	//读取数据时候加锁
	public Object get(String key){
		readLock.lock();
		try{
			return tLock.get(key);
		}finally{
			readLock.unLock();
		}
	}

	//写数据时加锁
	public Object put(String key, Object obj){
		writeLock.lock();
		try{
			return tLock.put(key, obj);
		}finally{
			writeLock.unLock();
		}
	}
}
  1. 共享锁
           允许多个线程同时获取该锁,并发访问共享资源。读写锁中的读锁就是机遇共享锁的实现。

  2. 独占锁(排他锁)
           独占锁又叫做排他锁,独占锁是指该锁在同一时刻只能被一个线程获取,而获取锁的其他线程只能在同步队列中等待,也叫互斥锁。JDK 中 synchronized和 JUC 中 Lock 的实现类就是互斥锁。

  3. 重量级锁
           重量级锁是基于操作系统的互斥量而实现的锁,会导致进程在用户态和内核态之间切换,相对开销较大。
    synchronized属于重量级锁,运行效率不高。
    在这里插入图片描述

  4. 轻量级锁
           轻量级锁是相对于重量级锁而言的。轻量级锁的核心设计旨在没有多线程竞争的前提下,减少重量级锁的使用以提高系统性能。轻量级锁适用于线程交替执行同步代码块的情况,如果同一时刻有多个线程访问同一个锁,则将会导致轻量级锁膨胀为重量级锁。
    在这里插入图片描述

  5. 偏向锁
           偏向锁用于在某个线程获取某个锁之后,消除这个线程锁重入的开销,看起来似乎是这个线程得到了该锁的偏向。
           偏向锁使用了一种竞争才会释放锁的机制,也就是只有其他线程竞争偏向锁时,持有偏向锁的线程才有可能释放锁。
           偏向锁在JDK1.6和1.7中都是默认开启的,但是需要在程序启动几秒之后才会自动开启,若是不想延迟开启,可以修改参数-XX:BiasedLockingStartupDelay=0
    现在项目使用多线程的情况比较常见,竞争锁也是比较激烈,每次都是从偏向锁升级到轻量级锁,而偏向锁用得比较少,所以你也可以关闭偏向锁,直接默认进入轻量锁(自旋锁)状态,使用参数-XX:-UseBiasedLocking=false即可。

  6. 分段锁
           分段锁并非是一种实际的锁,而是一种设计思想,用于将数据分段并在每个分段上都单独加锁,把锁进一步细粒度化,以提高并发效率。众所周知,在使用集合时,HashMap线程不安全,HashTable效率低下,考虑到线程安全都会选择ConcurrentHashMap,而ConcurrentHashMap在JDK 1.7就是基于Segment(分段锁)和HashEntrty实现的,在JDK1.8之后采用了Node数组+链表+红黑树实现。

  7. 同步锁
           同步锁的职责可以说就一个,限制资源的使用(线程安全从属)。
           同步锁的应用场景:多个线程同时操作一个事务必须保证正确性;一个资源只能同时由一线程访问操作;一个资源最多只能接入k的并发访问;保证访问的顺序性;
           同步锁的实现方式操作系统调度实现;应用自行实现;CAS自旋
    它一般至少会包含两个功能:

    1. 给资源加锁;
    2. 给资源解锁;另外,它一般还有 等待/通知即 wait/notify 的功能;
  8. 死锁
            线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。
    产生死锁的必要条件:
           互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
           请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
           不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
           环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

================= 【END】 =================
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值