synchronized同步锁

Synchronize同步锁

synchronized 它可以把任意一个非 NULL 的对象当作锁。

(1)作用于方法时,锁住的是对象的实例(this); 

(2) 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PermGen (jdk1.8 则是 metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁, 会锁所有调用该方法的线程; 

(3)synchronized 作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。

按修饰对象分类

同步方法

非静态

public synchronized void methodName(){}

静态方法

public synchronized static void methodName(){}

同步代码块

synchronized(this | object){}

按获取的锁来分类

对象锁

修饰非静态方法

每个对象有个monitor对象,这个对象其实就是 Java 对象的锁,类的每个对象有各自的monitor对象

某一线程占有这个对象的时候,先判断monitor 的计数器是不是0,如果是0还没有线程占有,这个时候线程占有这个对象,并且对这个对象的monitor+1;如果不为0,表示这个线程已经被其他线程占有,这个线程等待。当线程释放占有权的时候,monitor-1

同一线程可以对同一对象进行多次加锁,具有重入性

synchronized(类.class){}

类锁

修饰静态方法

类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁,即这个类的所有对象用的一把锁

 

Synchronize核心组件

  • Wait Set:哪些调用wait方法被阻塞的线程被放置在这里;
  • Contention List:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;
  • Entry List:Contention List中那些有资格成为候选资源的线程被移动到Entry List中;
  • OnDeck:任意时刻,多只有一个线程正在竞争锁资源,该线程被成为OnDeck;
  •  Owner:当前已经获取到所资源的线程被称为Owner;
  •  !Owner:当前释放锁的线程

参考这篇博客

synchronized同步锁_第1张图片

  1. JVM 每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck),但是并发情况下, ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将 一部分线程移动到EntryList中作为候选竞争线程。
  2. Owner 线程会在 unlock 时,将 ContentionList 中的部分线程迁移到 EntryList 中,并指定 EntryList中的某个线程为OnDeck线程(一般是先进去的那个线程)。
  3. Owner 线程并不直接把锁传递给 OnDeck 线程,而是把锁竞争的权利交给 OnDeck, OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在 JVM中,也把这种选择行为称之为“竞争切换”。
  4.  OnDeck线程获取到锁资源后会变为Owner线程,而没有得到锁资源的仍然停留在EntryList 中。如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,直到某个时刻通过notify 或者notifyAll唤醒,会重新进去EntryList中。
  5. 处于 ContentionList、EntryList、WaitSet 中的线程都处于阻塞状态,该阻塞是由操作系统 来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。
  6. Synchronized是非公平锁。 Synchronized在线程进入ContentionList时,等待的线程会先 尝试自旋获取锁,如果获取不到就进入 ContentionList,这明显对于已经进入队列的线程是 不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck 线程的锁 资源。
  7. 每个对象都有个 monitor 对象,加锁就是在竞争 monitor 对象,代码块加锁是在前后分别加 上monitorenter和monitorexit指令来实现的,方法加锁是通过一个标记位来判断的
  8. synchronized 是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线 程加锁消耗的时间比有用操作消耗的时间更多。
  9. Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的 Java1.7 与 1.8 中,均对该关键字的实现机理做 了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。
  10. 锁可以从偏向锁升级到轻量级锁,再升级到重量级锁。这种升级过程叫做锁膨胀;

ReentrantLock 与 synchronized

  • ReentrantLock 通过方法lock()与unlock()来进行加锁与解锁操作,与synchronized会 被 JVM 自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁。为了避免程序出 现异常而无法正常解锁的情况,使用 ReentrantLock 必须在 finally 控制块中进行解锁操作。
  • ReentrantLock相比synchronized的优势是可中断、公平锁、多个锁。这种情况下需要 使用ReentrantLock

Condition 和 Object锁方法区别

  •  Condition类的awiat方法和Object类的wait方法等效
  •  Condition类的signal方法和Object类的notify方法等效
  •  Condition类的signalAll方法和Object类的notifyAll方法等效
  •  ReentrantLock类可以唤醒指定条件的线程,而object的唤醒是随机的 

工具和方法

Jconsole、 Jstack pid、Javap -V  反编译

反编译发现

代码块的加锁

monitorentermonitorExit配合使用

对方法的加锁的标志

ACC_SYNCHRONIZED

使用synchronized注意的问题

与moniter关联的对象不能为空

synchronized作用域太大

不同的monitor企图锁相同的方法

多个锁的交叉导致死锁

分类

无锁

偏向锁:

在对象第一次被某一线程占有的时候,是否偏向锁置1,锁表01,写入线程号,当其他的线 程访问的时候,竞争,失败 就转换为轻量级锁

 多次悲第一次占有它的线程获取次数多,成功

轻量级锁:线程有交替适用,互斥性不是很强,CAS失败

重量级锁:强互斥

锁消除:JIT在编译的时候吧不必要的锁去掉

自旋锁:竞争失败的时候,不是马上转化级别,而是执行几次空循环

加锁的基础是对象头

synchronized同步锁_第2张图片

hashCode的作用:HashSet

synchronized同步锁_第3张图片

 

 

你可能感兴趣的:(笔记)