一、主要区别(表面)
1.synchronized是java关键字;Lock是接口。
2.syn是通过JDK实现的,软件层依赖JVM锁定;Lock是硬件层通过CPU指令操作。
3.syn是隐式的获得、释放锁;Lock是显示的调用lock();unlock();在finally处必须手动释放,否则容易死锁。
4.syn未拿到锁的线程只能干等(阻塞未释放锁时效率低);Lock具有可中段锁 tryLock(long,timeUnit)。
5.syn不知道当前线程有无获得锁;Lock可以通过tryLock()返回的bool值,知道有没有成功获得锁。
6.sys不适用大量读操作(没必要的锁);Lock的实现类ReentrantReadWriteLock提供了readLock()和writeLock()用来获取读锁和写锁,读-读不互斥。
7.syn可以锁代码块、对象实例(this)、类;Lock锁的范围只局限于代码块。
8.both:可重入锁(嵌套锁直接获取); Lock only:可中段锁(等一会没拿到就中断)、公平锁(等得久先拿)、读写锁(读读不互斥)。
9.synchronized和lock的底层区别。
二、synchronized底层原理
1.锁代码块时,对应位置字节码增加两个指令:monitorenter和monitorexit.。锁对象时,就在对象的[对象头]中的MarkWord部分进行一个标记。
2.锁膨胀策略(1.6开始): 无锁状态
-> 偏向锁(假设拿锁的都是同一线程,再次获取就不用锁了:CAS操作)
-> 轻量级锁,也叫乐观锁(此阶段竞争不激烈,自旋等待而不是直接挂起,减少线程上下文切换的消耗) 。
自旋锁/自适应自旋锁:自旋次数是否固定
-> 重量级锁(上一阶段自旋超过10次就升级成重量级锁,后续所有的失败都直接挂起) ,也叫悲观锁、互斥锁。
三、ReentrantLock底层原理
ReentrantLock是互斥锁,它是基于AQS实现的,AQS是Java并发包中众多同步组件的构建基础,它通过一个int类型的状态变量state(可重入)和一个FIFO队列(公平锁)来完成共享资源的获取,线程的排队等待等。加锁时将标志位state加1,释放锁时将state减1(加1减1的操作实现了可重入),通过compareAndSetState的cas原子操作修改state,自增成功说明当前线程拿到了锁。 cas机制可以说是concurrent包的基石了。
四、死锁详解
1.死锁的概念:两个或两个以上进程/线程,执行时由于竞争资源而产生互相等待的现象,若无外力作用,这些进程/线程都将无法向前推进。(过多的同步容易造成死锁)
2.死锁的必要条件: a.互斥(一份资源每次只能被一个进程/线程使用) b.请求与保持(进程/线程因请求资源而阻塞时,不释放已拥有的资源) c.不剥夺(拥有的资源除非主动释放,不能强制剥夺) d.循环等待(若干进程/线程的等待资源头尾相连)
3.常见死锁: a.线程将自己锁住 b.多线程竞争资源时循环等待 c.进程推进顺序不当导致的死锁
4.如果避免: a.合理分配资源,合理的进程推进顺序 b.尽量减少同步 c.使用可中段锁、可轮询的锁请求等 d.降低锁的粒度
五、悲观锁和乐观锁
悲观锁:将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。
乐观锁:每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败(内存和期望不相等,或者version不一样)就重试,直到成功为止。
当有大量写操作/冲突严重时,适用悲观锁;少量写操作/冲突少时,适用乐观锁。