锁
1.synchronized
synchronized关键字用于Java对象、方法、代码块提供线程安全的操作。
sychronized属于独占式的悲观锁,同时属于可重入锁。在使用sychronized修饰对象时,同一时刻只能有一个线程对该对象进行访问;在修饰代码块、方法时,同一时刻只能有一个线程执行该方法体或代码块。
Java所有对象中有一个monitor对象,加锁就是在竞争monitor对象。对代码块加锁是通过在前后分别加上monitorenter和monitorexit指令实现的,对方法是否加锁是通过一个标记位来判断的。
1.1.synchronized作用范围
- synchronized作用于成员变量和非静态方法时,所著的对象是对象的实例,即this对象
- synchronized作用于静态方法时,锁住的是Class实例,因为静态方法属于Class而不属于对象
- synchronized作用于一个代码块时,锁住的是所有代码块中配置的对象
1.2.sychronized的实现原理
在synchronized内部包括ContentionList、EntryList、WaitSet、OnDeck、Owner、!Owner这六个区域,每个区域的数据都代表锁的不同状态。
- ContentionList:锁竞争队列,所以请求锁的线程都被放在竞争队列中
- EntryList:竞争候选列表,在ContentionList中有资格成为候选者的来竞争锁资源的线程被移动到EntryList中
- WaitSet:等待集合,调用wait方法后被阻塞的线程将被放在WaitSet中
- Ondeck:竞争候选这同一时刻最多只能有一个线程竞争索资源,该线程的状态被称为Ondeck
- Owner:竞争到锁资源的线程被称为Owenr状态线程
- !Owner:在Onwer线程释放锁后,会从Owner状态变为!Owner
synchronized在收到新的锁请求时首先自旋,如果通过自旋没有获得锁资源,则将被放入锁竞争队列ContentionList中。
为了防止锁竞争时ContentionList尾部的元素被大量的并发线程进行CAS访问而影响性能,Owner线程会在释放锁资源时将Contention中的部分线程移动到EntryList中,并指定EntryList中的某个线程为Ondeck线程。Owner线程并没有直接把锁传递给Ondeck线程,而是把锁竞争的权利交给Ondeck线程,让Ondeck线程重新竞争锁。该行为牺牲了公平性,但提高了性能。
获得锁资源的Ondeck线程会变为Owner线程,而未获取到锁资源的线程仍然停留在EntryList中。
Owner线程在被wait方法阻塞后,将进入WaitSet队列中,直到被notify或notifyAll方法唤醒,会在次进入到EntryList中。
ContentionList和EntryList中的线程均为阻塞状态。
示意图:
在sychronized中,线程在进入ContentionList之前,等待的线程回先尝试自旋来获得锁,如果获取不到锁将会进入ContentionList,该做法对于已经进入队列等待的线程是不公平的,因此sychronized是非公平锁。另外,自旋获得锁的线程也可以直接抢占Ondeck线程的锁资源。
sychronized是一个重量级操作,JDK1.6对sychronized做了许多优化,引入了适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等以提高锁的效率。锁可以从偏向锁升级到轻量级锁,在升级到重量级锁。这种升级过程叫做锁膨胀。
2.ReentrantLock
ReentrantLock在我之前的博客有写到
链接:点击进入
3.sychronized和ReetrantLock的比较
共同点
- 都用于控制多线程对共享对象的访问
- 都是可重入锁
- 都保证了可见性和互斥性
不同点
- ReetrantLock显式获取和释放锁;sychronized隐式获取锁和释放锁。为了避免程序出现异常而无法释放锁,在使用ReentrantLock时需要在finally中释放锁
- ReentrantLock可相应中断、可轮回,为处理锁提供了更多的灵活性
- ReentrantLock是API级别的,synchronized是JVM级别的
- ReentrantLock可以定义公平锁
- ReentrantLock通过Condition可以绑定多个条件
- 底层实现不同:sychronized是同步阻塞,采用的时悲观并发策略;ReentrantLock是同步非阻塞,采用的是乐观并发策略
- Lock是一个接口,sychronized是Java关键字,synchronized是由内置的语言实现的
- 我们可以通过Lock知道有没有成功获取锁,通过synchronized却无法做到
- Lock可以通过分别定义读写锁提高多个线程读操作的效率