文章目录
- 一、隐式锁
- 1、同步代码块
- 2、同步方法
- 二、显示锁Lock
- 三、区别
- 1、实现方式
- 2、是否公平
- 3、灵活性
- 4、内存泄漏
- 5、使用方式
- 6、是否可中断
- 7、精确唤醒
- 四、共同点
一、隐式锁
隐式锁中又分为同步代码块和同步方法,但是都是基于Synchronized关键字来实现的,因为他只需要是使用管关键字就可以,不用显示加锁和解锁的过程,所以称之为隐式锁。
1、同步代码块
在代码块前面加上Synchroized关键字,并在后面的小括号内传入锁对象,这里的this是指定了当前的这个对象作为锁,注意任何对象都是可以作为锁来使用的。
格式:
synchronized(锁对象){
}
要点:使用同步代码块时,多个线程的锁对象必须唯一
这个是排队执行,一个线程进入代码块时,就会把这个锁对象的状态变成上锁状态,其他的线程发现是上锁状态时,就会在外面等待,直到这个线程将锁住的代码块执行完毕之后,将锁对象的状态变为解锁,其他的线程才会一起抢,谁抢到就重复上面的操作,知道满足某个条件,程序结束。
2、同步方法
同步方法就是将Synchroized关键字方放在方法的返回值前面,这里的锁就是也就是this,谁调用它,谁就是他的锁。
格式:
public synchronized void 方法名(){
}
要点:同步方法也是采用锁对象来执行的,当这个方法不是静态办法,那这个锁对象就是this,当这个方法是静态方法,锁对象就是xxx.class。
即谁调用该方法谁就是锁对象。
原理跟同步代码块一样,但是这里的锁因为是this,如果在一个线程对象里有多个方法,那么第一个线程执行时给这个this上了锁,那么其他方法都是不能执行的.只有等一个方法执行完了之后,改变锁的状态,其他的才可以执行。
二、显示锁Lock
显式锁的概念是相对于隐式锁的,因为显式锁在使用的时候程序员可以控制上锁和解锁,所以称之为显式锁。
Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的。实现类常见的有:
- ReentrantLock(重入锁)
重入锁就是支持重进入的锁,它表示该锁能够支持一个线程对资源的重复加锁。除此之外,该锁的还支持获取锁时的公平和非公平性选择
- ReentrantReadWriteLock.ReadLock(读锁)
- ReentrantReadWriteLock.WriteLock(写锁)
实现基本都是通过聚合了一个同步器(AbstractQueuedSynchronizer 缩写为 AQS)的子类来完成线程访问控制的。Lock 的操作借助于内部类 Sync,而 Sync 是继承了AbstractQueuedSynchronizer类的,这个类就是很重要的一个 AQS 类
ReentrantLock 支持公平与非公平选择,内部实现机制为:
1、内部基于 AQS 实现一个公平与非公平公共的父类 Sync ,(在代码里,Sync 是一个内部类,继承 AQS)用于管理同步状态;
2、FairSync 继承 Sync 用于处理公平问题;
3、NonfairSync 继承 Sync 用于处理非公平问题。
通常使用 ReentrantLock的lock和unlock方法。
lock()方法用来上锁,unlock()方法用来解锁。
三、区别
1、实现方式
隐式锁: synchronized 关键字是伴随着 Java 诞生就存在的元老级角色,所谓隐式或内置锁是相对于显式锁而言的。“内置”的含义是指加锁和解锁都由虚拟机帮助实现,对外只提供 synchronized 关键供开发人员使用。因此作为开发人员,无需关心锁释放等情况,只要知道其表达的含义就可以直接使用。sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的。
通过关键字 synchronized 声明来使用,实际是 jvm 层面实现的,向下则用到了 Monitor 类,再向下虚拟机的指令则是和 CPU 打交道,插入内存屏障等等操作。
显示锁:lock是通过调用对应的API方法来获取锁和释放锁的。在jdk5 后增加的== Lock 接口以及对应的各种实现类==,这属于显式的锁,就是我们能在代码层面看到锁这个对象,而这些个对象的方法实现,大都是直接依赖 CPU 指令的,无关 jvm 的实现,以 Lock 接口为核心的各种实现类,他们完全由 java 实现逻辑,那么实现类还要基于 AQS 这个队列同步器,AQS 屏蔽了同步状态管理、线程排队与唤醒等底层操作,提供模板方法,聚合到 Lock 的实现类里去实现。
2、是否公平
公平锁:公平锁是指多个线程按照申请锁的顺序来获取锁。显示锁支持公平/非公平锁。java 里面可以通过 ReentrantLock 这个锁对象,然后指定是否公平。
非公平锁:非公平锁是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁。使用 synchronized 是无法指定公平与否的,他是不公平的。
3、灵活性
隐式锁: synchronized控制的代码块无法跨方法,修饰的范围很窄。无法限制等待时间、无法对锁的信息进行监控。
显式锁:本身就是一个对象,可以充分发挥面向对象的灵活性,完全可以在一个方法里获得锁,另一个方法里释放。提供了足够多的方法来完成灵活的功能
4、内存泄漏
隐式锁:简单易用且不会导致内存泄漏。
显式锁:完全要程序员控制,容易导致锁泄露。
5、使用方式
隐式锁:使用sync关键字的时候,不用写其他的代码,然后程序就能够获取锁和释放锁了。Sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。
显式锁:需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。
6、是否可中断
隐式锁:不可中断的。除非抛出异常或者正常运行完成
显式锁:可以中断。中断方式:
1:调用设置超时方法tryLock(long timeout ,timeUnit unit)
2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断
7、精确唤醒
隐式锁:不能精确唤醒一个线程。要么随机唤醒一个线程;要么是唤醒所有等待的线程。
显式锁:能精确唤醒一个线程。实现分组唤醒需要唤醒的线程。
四、共同点
1、无论是显式锁还是隐式锁,当被锁住的代码块执行结束以后,正在等待的线程 开始抢时间偏执行代码块,并不是先来后到的方式进行执行,而是随机的的抢到时间偏就执行。
2、被锁住的代码块,谁先抢到时间偏进行代码执行以后,再次抢到的概率会增加,连续抢到的概率很大。
3、都可以解决线程安全问题