1 对synchronized的认识:
1.1:synchronized关键字解决了多个线程之间的资源同步性,synchronized关键字保证了它修饰的方法或者代码块任意时刻只有一个线程在访问。
1.2:synchronized是重量级锁,因为监视锁是依赖于底层操作系统mutex lock实现的,java线程是映射到操作系统之上的。如果要挂起或者唤醒一个线程需要操作系统来帮忙完成,而操作系统从用户态切换到内核态之间的状态非常耗时,成本高。
从jdk6开始以后对锁开始了大量的优化:如自旋锁,适应性自旋锁,锁消除,锁粗化,偏向锁,轻量级锁等技术减少锁操作的的开销。
2 如何使用synchronize关键字
2.1:修饰实例方法:作用于当前对象实例加锁,进入同步代码前要获得 当前对象的实例锁
2.2:修饰静态方法:相当于给当前对象加锁,会作用域类的所有对象实例,进入同步代码前要获得当前class的锁。因为静态成员不属于任何一个类对象,(无论多少个对象都只有一份)。
访问静态方法synchronized相当于锁的当前的类,而非静态方法是相当于锁的是当前实例对象。
2.3:修饰代码块
指定加锁对象,对给定对象/类加锁,synchronized(this|object)表示进入同步代码块一定要获得该锁。
总结:
1、synchronized关键字加到static静态方法和synchronized(class)代码块上实质都是给Class类上锁。
2、synchronized关键字加到实例方法上是给对象实例上锁
3、尽量不要使用synchronized(String a)因为jvm中,字符串常量池具有缓存功能。
手写单例模式。为什么要双重校验锁?
3、构造方法可以修饰构造函数吗?
结论:synchronized不能使用synchronized关键字修饰。
构造方法本身属于线程安全,不存在同步的构造方法一说。为什么?(首先线程安全涉及共享资源才会出现,即使多个线程同时构造一个对象,但是各自构造的对象之间也是互不干扰。不存在共享资源,所以加synchronized没有意义,但是如果需要对相关资源(静态变量)进行同步具体情况再处理)。
4、synchronized关键字的底层原理
使用的是monitorenter和monitorexit指令,当执行monitorenter指令,线程视图获取对象监视器monitor的持有权
Monitor是由C++实现的,java中每个对象内置了一个ObjectMonitor对象。另外wait/notify等方法也依赖于monitor对象,这就是因为什么只有在同步的块或者方法中才能调用wait/notify方法,否则抛出java.lang.IllegalMonitorStateException异常。
在执行monitorenter时,会尝试获取对象锁,如果锁计数器为0则表示所可以被获取,然后将计数器置为1.
对象锁的拥有者线程才能执行monitorexit指令来释放锁,在执行monitorexit指令后,将锁设为0,表示锁被释放,其他线程可以尝试获取锁
如果对象获取锁失败,那么当前线程就要阻塞等待,知道锁被另一个线程释放为止。
5、synchronized修饰方法并没有monitorenter和monitorexit指令,取而代之的是ACC_SYNCHRONIZED
表示,标识该方法为一个同步方法。
如果是实例方法,jvm会尝试获取实例对象的锁,如果是静态方法,jvm获取当前class的锁。
总结:synchronized同步代码块实现用的是monitorenter和moniterexit指令,其中minitorenter指向同步代码块开始的位置,monitorexit是指明同步代码块结束位置。
synchronized修饰的方法并没有monitorenter和moniterexit指令,取而代之是acc_synchronized标识。不过两者本质都是对对象监视器的获取。
6、jdk6以后锁的优化
6.1 jdk6以后引入了大量的优化,如偏向锁、轻量级锁、自旋锁、适应性锁、锁消除、锁粗化等技术来减少锁操作的开销
6.2 锁主要存在4种状态:无锁状态、偏向锁状态、轻量级锁、重量级锁。他们随着竞争的升级而升级,锁可以升级不可以降级,这种策略为了提高锁和释放锁的效率。
锁优化详情:https://www.cnblogs.com/wuqinglong/p/9945618.html
7、synchronized和reentrantlock的区别
可重入锁:自己可以再次获取自己内部的锁,比如一个线程获取了某个对象的锁,此时这个对象锁还没有释放,当该线程想要再次获取这个对象的锁还是可以获取,如果是不可重入锁,那么就会造成死锁。同一个线程每次获取锁,锁的计数器会加1,
直到锁计数器数量减为0才能释放锁。
7.1 两者都是可重入锁
7.2synchronized依赖于JVM而ReentrantLock依赖于API,Reentrantlock是jdk层面的实现,需要lock和unlock方法配合try/finally配合完成。
7.3reentrantlock比synchronized增加了一些高级功能
1:等待可中断:reentrantlock提供了一种能够中断等待锁的线程机制,通过lock.lockInterruptbly来实现这个机制,也就是说正在等待的线程可以选择放弃等待,做其他事情
2:可实现公平锁:reentrantlock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁,所谓公平锁指的是先等待的线程先获得锁,非公平锁通过构造方法来指定是否公平
3:可实现选择性通知:可实现选择性的通知,synchronized关键字与wait()和notify/notifyall实现等待/通知机制。reentrantlock类当然也可实现使用的是Condition接口和newcondition方法()
4:condition接口中的显示调用方法await、signal、signalall也对notify等的升级,和wait一样,await在进入等待队列会释放锁和cpu,当其他线程唤醒或者超时重新获取锁,获取锁后才能从await方法退出,await与wait一样存在等待返回,
反悔不代表条件成立,也需要主动判断。 不要与wait、notify等混合使用,会出现不可预见的行为。
可重入锁:(递归锁)
指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提:锁对象是同一个对象)
不会因为之前获取过还没释放而阻塞。
ReentrantLock 显式锁Lock和Unlock
和
Synchronized 隐式锁
都是可重入锁。
目的:可以一定程度上避免死锁。