相同点
- 都是加锁方式同步;
- 都是重入锁;
- 都是阻塞式的同步;
不同点
比较方面 | SynChronized | ReentrantLock(实现了 Lock接口) |
原始构成 | 它是java语言的关键字,是原生语法层面的互斥,需要jvm实现 | 它是JDK 1.5之后提供的API层面的互斥锁类 |
实现 | 通过JVM加锁解锁 | api层面的加锁解锁,需要手动释放锁。 |
代码编写 | 采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全, | 而ReentrantLock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。需要lock()和unlock()方法配合try/finally语句块来完成, |
灵活性 | 锁的范围是整个方法或synchronized块部分 | Lock因为是方法调用,可以跨方法,灵活性更大 |
等待可中断 | 不可中断,除非抛出异常(释放锁方式: 1.代码执行完,正常释放锁; 2.抛出异常,由JVM退出等待) | 持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,(方法: 1.设置超时方法 tryLock(long timeout, TimeUnit unit),时间过了就放弃等待; 2.lockInterruptibly()放代码块中,调用interrupt()方法可中断,而synchronized不行) |
是否公平锁 | 非公平锁 | 两者都可以,默认公平锁,构造器可以传入boolean值,true为公平锁,false为非公平锁, |
底层原理
- Synchronized:
Synchronized进过编译,会在同步块的前后分别形成monitorenter和monitorexit这个两个字节码指令。在执行monitorenter指令时,首先要尝试获取对象锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象锁,把锁的计算器加1,相应的,在执行monitorexit指令时会将锁计算器就减1,当计算器为0时,锁就被释放了。如果获取对象锁失败,那当前线程就要阻塞,直到对象锁被另一个线程释放为止。
monitorenter :
每个对象有一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权
monitorexit:
执行monitorexit的线程必须是objectref所对应的monitor的所有者。
指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个
monitor 的所有权
- ReentrantLock:
CAS+CLH队列来实现。它支持公平锁和非公平锁,两者的实现类似。
CAS:Compare and
Swap,比较并交换。CAS有3个操作数:内存值V、预期值A、要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。该操作是一个原子操作,被广泛的应用在Java的底层实现中。在Java中,CAS主要是由sun.misc.Unsafe这个类通过JNI调用CPU底层指令实现。
CLH队列:带头结点的双向非循环链表,为什么用双向而不用单向是因为:独占模式下需要查看上一个节点是否是首节点;加上前一个节点释放锁后要唤醒下一个节点 就用到了node,pre 和node.next
锁升级
详情:详情点击这里