摘要:如果同一个线程再次请求该锁,计数器会递增,每次占有的线程退出同步代码块时计数器会递减,直至减为时锁才会被释放。表示或在该上的所有线程的个数用来实现重入锁的计数。只有两种可能的值表示没有需要唤醒的线程表示要唤醒一个继任线程来竞争锁。
一、synchronized
1.类型
(1)对象锁
对象锁是作用在实例方法或者一个对象实例上面的
一个类可以有多个实例对象,因此一个类的对象锁可能会有多个
例子:
Account account = new Account(); synchronized(account){ .... }
(2)类锁
而类锁是作用在静态方法或者Class对象上面的
每个类只有一个Class对象,所以类锁只有一个
类锁只是一个概念上的东西,并不是真实存在的,它只是用来帮助我们理解锁定的是实例方法还是静态方法区别的
例子:
synchronized(Account.class){ .... }
2.synchronizedd的使用:
(1)锁对象的引用,以及这个锁保护的代码块。
例子:
private Object obj = new Objcet(); publci void test(){ synchronized(obj){ .... } }
(2)如果作用在实例方法上面,锁就是该方法所在的当前对象,静态synchronized方法会从Class对象上获得锁。
例子:
public synchronized void test(){ .... }
(3)究竟是synchronized方法好还是synchronized代码块好呢?
有一个原则就是锁的范围越小越好,加锁的目的就是将锁进去的代码作为原子性操作,因为非原子操作都不是线程安全的,因此synchronized代码块应该是在开发过程中优先考虑使用的加锁方式。
3.synchronizedd特性
(1)synchronizedd是一种悲观锁,也称为独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。
(2)synchronized不具有继承性。
如果父类 、子类中存在同样的方法。父类方法是同步的,子类是不同步的。那么子类的方法就不会无法继承父类的方法性质。必须自己加上synchronized关键字!
例子:
class A{ public synchronized void test(){ System.out.println("父类"); } } class B extends A{ public void test(){ System.out.println("子类无法继承父类的锁"); } }
(3)重入性
关键字synchronized具有锁重入功能,当一个线程已经持有一个对象锁后,再次请求该对象锁时是可以得到该对象的锁的,这种方式是必须的,否则在一个synchronized方法内部就没有办法调用该对象的另外一个synchronized方法了。锁重入是通过为每个所关联一个计数器和一个占有它的线程,当计数器为0时,认为锁是未被占有的。线程请求一个未被占有的锁时,JVM会记录锁的占有者,并将计数器设置为1。如果同一个线程再次请求该锁,计数器会递增,每次占有的线程退出同步代码块时计数器会递减,直至减为0时锁才会被释放。
4.实现原理
(1)Java对象头
synchronized用的锁是存在Java对象头里的,那么什么是Java对象头呢?Hotspot虚拟机的对象头主要包括两部分数据:Mark Word(标记字段)、Klass Pointer(类型指针)。
Mark Word(标记字段)
Mark Word用于存储对象自身的运行时数据,如哈希码(HashCode)、GC分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。
Klass Pointer(类型指针)
Klass Pointer是对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例,
(2)Monitor Record
Monitor Record 是线程私有的数据结构,每一个线程都有一个可用monitor record(监视记录)列表,同时还有一个全局的可用列表。每一个被锁住的对象都和一个monitor关联(对象头的MarkWord中的LockWord指向monitor的起始地址),同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识。
Monitor Record 其结构如下:
Owner: 初始时为NULL表示当前没有任何线程拥有该monitor record,当线程成功拥有该锁后保存线程唯一标识,当锁被释放时又设置为NULL;
EntryQ:关联一个系统互斥锁(semaphore),阻塞所有试图锁住monitor record失败的线程。
RcThis:表示blocked或waiting在该monitor record上的所有线程的个数
Nest:用来实现重入锁的计数。
HashCode:保存从对象头拷贝过来的HashCode值(可能还包含GC age)
Candidate:用来避免不必要的阻塞或等待线程唤醒,因为每一次只有一个线程能够成功拥有锁,如果每次前一个释放锁的线程唤醒所有正在阻塞或等待的线程,会引起不必要的上下文切换(从阻塞到就绪然后因为竞争锁失败又被阻塞)从而导致性能严重下降。Candidate只有两种可能的值0表示没有需要唤醒的线程1表示要唤醒一个继任线程来竞争锁。
同步代码块
monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁。
同步方法:
synchronized方法则会被翻译成普通的方法调用和返回指令如:invokevirtual、areturn指令,在VM字节码层面并没有任何特别的指令来实现被synchronized修饰的方法,而是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示Klass做为锁对象。
二、ReentrantLock
1.轮询锁的和定时锁
可轮询和可定时的锁请求是通过tryLock()方法实现的,和无条件获取锁不一样. ReentrantLock可以有灵活的容错机制.死锁的很多情况是由于顺序锁引起的, 不同线程在试图获得锁的时候阻塞,并且不释放自己已经持有的锁, 最后造成死锁. tryLock()方法在试图获得锁的时候,如果该锁已经被其它线程持有,则按照设置方式立刻返回,而不是一直阻塞等下去,同时在返回后释放自己持有的锁.可以根据返回的结果进行重试或者取消,进而避免死锁的发生。
2.公平性
ReentrantLock构造函数中提供公平性锁和非公平锁(默认)两种选择。所谓公平锁,线程将按照他们发出请求的顺序来获取锁,不允许插队;但在非公平锁上,则允许插队:当一个线程发生获取锁的请求的时刻,如果这个锁是可用的,那这个线程将跳过所在队列里等待线程并获得锁。我们一般希望所有锁是非公平的。因为当执行加锁操作时,公平性将讲由于线程挂起和恢复线程时开销而极大的降低性能。考虑这么一种情况:A线程持有锁,B线程请求这个锁,因此B线程被挂起;A线程释放这个锁时,B线程将被唤醒,因此再次尝试获取锁;与此同时,C线程也请求获取这个锁,那么C线程很可能在B线程被完全唤醒之前获得、使用以及释放这个锁。这是种双赢的局面,B获取锁的时刻(B被唤醒后才能获取锁)并没有推迟,C更早地获取了锁,并且吞吐量也获得了提高。在大多数情况下,非公平锁的性能要高于公平锁的性能。
3.可中断获锁获取操作
lockInterruptibly方法能够在获取锁的同时保持对中断的响应,因此无需创建其它类型的不可中断阻塞操作。
4.Condition
Lock提供的线程之间交互类:
await:阻塞线程
signal:释放阻塞的线程
signalAll:释放所有阻塞的线程
5.ReadWriteLock
(1)ReentrantLock是一种标准的互斥锁,每次最多只有一个线程能持有锁。读写锁不一样,暴露了两个Lock对象,其中一个用于读操作,而另外一个用于写操作。
(2)ReentrantReadWriteLock
ReentrantReadWriteLock实现了ReadWriteLock接口,构造器提供了公平锁和非公平锁两种创建方式。读写锁适用于读多写少的情况,可以实现更好的并发性,性能高于重入锁。
ReadLock:读锁
WriteLock:写锁
6.ReentrantLock特性
(1)ReentrantLock是可重入的锁,它不同于内置锁, 它在每次使用都需要显示的加锁和解锁, 而且提供了更高级的特性:公平锁, 定时锁, 有条件锁, 可轮询锁, 可中断锁. 可以有效避免死锁的活跃性问题.ReentrantLock实现了。
(2)ReentrantLock是一种乐观锁。
7.实现原理
(1)ReentrantLock的方法都依赖于AbstractQueuedSynchronizer的实现 (AQS)
(2)ReentrantLock的锁资源以state状态描述,利用CAS则实现对锁资源的抢占,并通过一个CLH队列阻塞所有竞争线程,在后续则逐个唤醒等待中的竞争线程。ReentrantLock继承AQS完全从代码层面实现了java的同步机制,相对于synchronized,更容易实现对各类锁的扩展。同时,AbstractQueuedSynchronizer中的Condition配合ReentrantLock使用,实现了wait/notify的功能
部分信息来自网络
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/71429.html
摘要:有可能,会造成优先级反转或者饥饿现象。悲观锁在中的使用,就是利用各种锁。对于而言,其是独享锁。偏向锁,顾名思义,它会偏向于第一个访问锁的线程,大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。 理解锁的基础知识 如果想要透彻的理解java锁的来龙去脉,需要先了解以下基础知识。 基础知识之一:锁的类型 按照其性质分类 公平锁/非公平锁 公平锁是指多个线程按照申请锁的顺序来获...
摘要:并发机制与底层实现原理是轻量级的它在多处理器开发中保证了共享变量的可见性,因为它不会引起线程上下文的切换和调度,所以比的使用和执行成本更底。如果线程间存在锁竞争,会带来额外的锁撤销的消耗。轻量级锁竞争的线程不会阻塞,提高了程序的响应速度。 java并发机制与底层实现原理 volatile volatile是轻量级的synchronize,它在多处理器开发中保证了共享变量的可见性,因为它...
摘要:的主要功能和关键字一致,均是用于多线程的同步。而仅支持通过查询当前线程是否持有锁。由于和使用的是同一把可重入锁,所以线程可以进入方法,并再次获得锁,而不会被阻塞住。公平与非公平公平与非公平指的是线程获取锁的方式。 1.简介 可重入锁ReentrantLock自 JDK 1.5 被引入,功能上与synchronized关键字类似。所谓的可重入是指,线程可对同一把锁进行重复加锁,而不会被阻...
摘要:本文探讨并发中的其它问题线程安全可见性活跃性等等。当闭锁到达结束状态时,门打开并允许所有线程通过。在从返回时被叫醒时,线程被放入锁池,与其他线程竞争重新获得锁。 本文探讨Java并发中的其它问题:线程安全、可见性、活跃性等等。 在行文之前,我想先推荐以下两份资料,质量很高:极客学院-Java并发编程读书笔记-《Java并发编程实战》 线程安全 《Java并发编程实战》中提到了太多的术语...
摘要:当一个线程持有重量级锁时,另外一个线程就会被直接踢到同步队列中等待。 java代码先编译成字节码,字节码最后编译成cpu指令,因此Java的多线程实现最终依赖于jvm和cpu的实现 synchronized和volatile 我们先来讨论一下volatile关键字的作用以及实现机制,每个线程看到的用volatile修饰的变量的值都是最新的,更深入的解释就涉及到Java的内存模型了,我们...
阅读 564·2023-04-25 16:00
阅读 1597·2019-08-26 13:54
阅读 2495·2019-08-26 13:47
阅读 3401·2019-08-26 13:39
阅读 1037·2019-08-26 13:37
阅读 2734·2019-08-26 10:21
阅读 3534·2019-08-23 18:19
阅读 1601·2019-08-23 18:02