一、作用
首先它是一个关键字hh
官方的解释:
同步方法支持一种简单的策略来防止线程干扰和内存一致性错误:如果一个对象对多个线程可见,则对该对象变量的所有读取或写入都是通过同步方法完成的。
一句话总结出Synchronized的作用:
能够保证在同一时刻最多只有一个线程执行该段代码,以达到保证并发安全的效果
主要有三种用法
- 修饰方法,🔒的是this既当前对象
- 修饰一段代码,也就是同步代码块,🔒的是这一段代码
- 修饰static的方法,🔒的是当前Class。
当一个线程想访问sync之中的东西时,他必须要得到锁,拿到锁运行完之后必须释放锁,就像上厕所必须拿到门上的,上完之后要开门。。
二、其一些底层的实现
1.对于修饰的时代码块,我们可以看到在其字节码上代码块两端有一个monitorenter 和两个monitoreixt。(两个相当于finally 释放🔒)
当一个monitor被持有后。既为锁定状态。当线程执行到monitorenter时就会去尝试获取monitor所有权,既尝试获得对象的锁。一旦某个对象持有了锁,就会把锁的计数器值加一,而执行了monitoreixt值就会减一。如果线程没拿到锁,就会被阻塞等待,直到该线程拿到锁为止。
2.对于修饰的是方***在字节码方法体上多一个 ACC_SYNCHRONIZED
3.有两个注意点
-
sync是可重入的,意味着同一线程反复进入同步代码块也不会把自己锁死。
-
被sync修饰的同步代码块在释放锁之前,会无条件的阻塞其他线程的进入。就不妥协!
三、sync的优化
从这样看来sync好像很重,在jdk1.6之前是这样的,但是jdk1.6对其进行了一波优化,反正现在使用的挺多的0 0
1.锁的升级
sync锁的信息是存在对象头Mark Word 中的,这里就暂时不说那个了。
锁的状态一共有四种,从低到高级别为:无锁、偏向锁、轻量级锁、重量级锁
- 偏向锁:经巨佬研究大多情况,大多情况下锁不存在竞争,而且总是由某一个线程得到。当一个线程获得锁时,对象头会记录该线程的ID;当下一次该线程进来或退出时,就不需要cas了,校验一下对象头的信息就好了。"偏"的含义就是锁会偏向第一个获得他的线程。。
jdk默认开启偏向锁
开启偏向锁:-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0
关闭偏向锁:-XX:-UseBiasedLocking
-
轻量级锁:《深入理解java虚拟机》上说一旦另外一个线程尝试去获得这个锁,偏向锁模式就会马上结束来到轻量级锁。这里不会马上冲到重量级锁,毕竟和os打交道消耗会有点大。
线程在执行同步块之前,JVM 会先在当前线程的栈桢中创建用于存储锁记录的空间,并将对象头中的 Mark Word 复制到锁记录中,官方称为 Displaced Mark Word。然后线程尝试使用 CAS 将对象头中的 Mark Word 替换为指向锁记录的指针。如果成功,当前线程获得锁,如果失败,表示其他线程竞争锁,当前线程便尝试使用自旋来获取锁,自旋有一定次数,自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因)。
-
重量级锁 : 利用操作系统实现线程之间的切换。需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高。
注意:锁的升级是不可逆的
几种锁的优缺点: