Java Synchronized

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 隐式锁

都是可重入锁。

目的:可以一定程度上避免死锁。

Monitors:监视器。是一个程序结构,结构内的多个子程序形成的多个工作现场互斥访问共享资源。

管程实现了一个时间点,最多只有一个线程在执行管程的某个子程序。

执行线程就要成功 ,持有管程,然后才只能执行方法,最后当方法完成释放管程。方法执行期间,其他任何线程无法获取同一个管程。

 

为什么任何一个对象都可以成为一个锁

总结:每一个对象天生自带一个对象监视器(ObjectMonitor),每一个被锁住的对象都会和Monitor关联起来。

monitor和对象一起生成和销毁

Monitor与Java对象以及线程是如何关联的?

1、如果一个java对象被某个线程锁住,则该java对象的MarkWord字段的LockWord指向monitor的起始位置

2、Monitor的Owner字段会存放拥有相关联对象锁的线程id

对象头 ,右下看到左上

 

 

Synchronized升级步骤: 

1、  

 

 

1、偏向锁:MarkWord存储的是偏向的线程ID(避免统一个线程,获取同一段同步代码)  

当线程A第一次竞争到锁的时候,通过操作修改Mark Word中的偏向线程ID、偏向模式。

如果不存在其他线程竞争,那么持有偏向锁的线程将永远不需要进行同步。

只需要在锁第一次被拥有的时候,记录下偏向线程ID,这样偏向线程一直都持有着锁,(后续这个线程进入和退出加了同步代码块时,不需要再加锁和释放锁,只需要检查锁的markWord里面是不是放的自己的线程ID,避免了操作系统从用户态切换到内核态的开销)。

如果不等,表示发生了竞争,锁已经不是总是偏向同一个线程了,这个时候尝试CAS来替换MarkWord中的线程ID,为新线程ID。 

JVM作者发现,多线程情况下,锁不仅不存在多线程竞争,还存在锁由同一个线程获得。

 

 撤销:

1、当有另外的线程逐步来竞争锁的时候,竞争失败,就不能再使用偏向锁了,要升级为轻量锁。如果竞争成功是直接重新偏向。

2、竞争线程尝试cas更新对象头失败,会等待到全局安全点(此时不会执行任何代码)撤销偏向锁

2、轻量锁:MarkWord存储的是指向线程栈中的Lock Record的指针

3、重量锁:MarkWord存储的是指向堆中monitor对象的指针

 

  

锁升级后hashcode变化地方:

 

 

 

 

 

 

公平锁和非公平锁

 公平锁:是指多个线程按照申请锁的顺序来获取锁,这里类似于排队买票,先来的人先买,后来的人在队尾排队。

Lock lock = new Lock(true);//表示公平锁

非公平锁:多个线程并不是按照申请锁的顺序,有可能后申请的线程比申请的线程优先获取锁。高并发的情况下,可能造成优先级翻转。

(可以做到cpu的上下文切换的资源开销,刚使用完的线程,获取锁的几率更大,吞吐量更大)

 

锁消除:

JIT编译器会无视他,synchronized(o),每次new出来,不存在了。

锁粗化:

假如方法中首尾相接,前后相邻的都是同一个锁对象,那JIT编译器就会把这几个synchronized块合并成一个大块。加粗加大范围,一次申请使用即可。

 

 

 

 

 死锁排查策略:

jps查看继承号

jstack + 进程编号 查看死锁。

 

 Synchonized与锁升级

1、 

 

posted @ 2022-05-05 10:35  雷雷提  阅读(15823)  评论(1编辑  收藏  举报