首先synchronized是jdk的一个关键字,ReentrantLock是java.util.concurrent.locks并发包下的一个类。

1.从公平与非公平角度来看

什么是公平锁与非公平锁:

公平锁与非公平锁都会维护一个队列,在公平锁中,新来的线程一定会进入队列的尾部,直到轮到自己拿到锁,他能保证每个线程都能执行。而非公平锁中,多个线程抢锁时,获取锁的线程不一定是同步队列中等待时间最长的线程,有可能是同步队列之外的线程先抢到锁,因为当线程来到它会首先尝试能否获取倒锁,减少了进入队列被唤醒一次开销,因为进入同步队列的线程都会被阻塞,而阻塞到被唤醒,需要操作系统的介入是比较消耗系统资源的。所以一般情况下我们使用非公平锁即可。

公平锁:

 

java clh队列 公平锁 非公平锁 synchronized实现公平锁_自旋锁

非公平锁:

java clh队列 公平锁 非公平锁 synchronized实现公平锁_自旋锁_02

 

synchronized只能实现非公平

而ReentrantLock既可以实现公平和非公平,它默认是非公平的。它有一个构造方法当参数为true即可变为公平锁。  

java clh队列 公平锁 非公平锁 synchronized实现公平锁_自旋锁_03

2.从锁的对象来看

synchronized当修饰静态方法或者当同步代码块中为xx.class的时候都是锁的当前XX类的所有实例

synchronized修饰普通方法的时候锁的是当前实例对象

synchronized修饰同步代码块的时候锁的是当前代码块里面的对象。

ReentrantLock.lock()没有参数, 不像synchronized(xx)可以指定被锁定的对象,那么它锁的就是本身自己。

证明锁的是本身自己的一个例子:

public class Test  {
	public static void main(String[] args) {
		Demo1 demo1 = new Demo1();
		for (int i = 0; i < 10; i++) {
			new Thread(new Runnable() {
				
				@Override
				public void run() {
					// TODO Auto-generated method stub
					demo1.count();
				}
			}).start();
		}
		}
		
	
	

}
class Demo1 {
	private int a = 0;
	ReentrantLock reentrantLock = new ReentrantLock();
	
	public void count() {
		reentrantLock.lock();
		System.out.println("执行a加操作");
		a++;
		System.out.println("a加操作执行完毕");
		reentrantLock.unlock();
	}
	
	
}

当吧Demo1实例放在for循环外面打印结果: 都是成对出现

java clh队列 公平锁 非公平锁 synchronized实现公平锁_公平锁_04

当吧Demo1实例放在for循环里面打印结果:

java clh队列 公平锁 非公平锁 synchronized实现公平锁_java clh队列 公平锁 非公平锁_05

可见锁的都是当前对象实例

3.从性能上看

从性能来看,两者需要再不同场景下来区别,网上有许多博主也用例子证明了两者性能,我这里就给出一个结论:在高并发下使用synchronized会打满cpu,从而反作用于获得锁的线程,在低并发,特别同时是轻量化的操作,synchronized可能可以获得更好的性能。现实情况中虽然我们可能有几百个线程,但是大多数情况下,对于共享资源的修改,同时只有几个或者十几个,那么使用synchronized也不失为一种好选择。

4.从是否需要手动释放锁

synchronized是独占锁即锁的期间只允许一条线程进入,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。


5.从内部实现核心来看

synchronized在1.6之前是重量级锁,直接与操作系统打交道,性能很差,而在1.6之后呢对其做了大量的优化,进行一个锁升级的一个过程。 从偏向锁->轻量级锁(也叫自旋锁,CAS)->重量级锁 。

jvm认为大多数情况下你的这把锁竞争性不太强,我就举一个占坑的一个例子,一个厕所只有一个坑位,当第一个人来,肠胃很好,很顺畅的拉出来走人,此时第二个人也恰好来到这,直接顺利就进去了,没有一个等待,抢厕所的操作,后续的人皆是如此。这时候上的就是偏向锁,效率很高。但是咱们的女厕所就不一样了(你懂的),人越来越多,大家都在抢这个厕所,此时锁升级为自旋锁,它采用CAS这种无锁算法。何时自旋锁升级为重量级锁,大家知道自旋的线程多了,是非常占用cpu资源的,当自旋的线程到达某个值时,这个值jvm帮你自动设定的,它会升级为重量级锁。 然后操作系统直接不让他们自旋了,扔到这把锁的等待队列中,这时候的线程是不消耗cpu资源的,好比让他们排好队一个个上厕所。

而ReentrankLock内部采用的是AQS(AbstractQuenedSynchronizer)实现的,AQS里边采用的是双向链表,即如果当前线程未获取到锁将会加入到链表中。 公平锁采用判断当前Node是不是头结点,如果是的话就获取锁并做业务处理,不是头结点的不能获取锁。非公平锁没有判断当前结点,采用CAS,谁第一个拿到了state=0,则视为获取锁,并把state设置为1。

总的来说ReentrantLock功能更强大:

  1. ReentrantLock 提供了一种能够中断等待锁线程的机制,通过 lock.lockInterruptibly() 来实现这个机制,也就是说正在等待的线程可以选择放弃等待,改为处理其他事情,而synchronize是不能响应中断的;
  2. ReentrantLock 可以指定是公平锁还是非公平,而 synchronized 只能是非公平锁
  3. ReentrantLock 还提供了等待锁,即满足一定时间后还没获取到锁,便放弃获取锁。

它其中比较常用的方法:

1. void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁 . 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.

2. boolean tryLock(): 如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false . 该方法和lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用,当前线程仍然继续往下执行代码. 而 lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行.

3. void unlock():执行此方法时, 当前线程将释放持有的锁 . 锁只能由持有者释放, 如果线程并不持有锁, 却执行该方法, 可能导致异常的发生.

4. Condition newCondition(): 条件对象,获取等待通知组件 。该组件和当前的锁绑定,当前线程只有获取了锁,才能调用该组件的 await()方法,而调用后,当前线程将缩放锁。

5. getHoldCount() :查询当前线程保持此锁的次数,也就是执行此线程执行 lock 方法的次数。

6. getQueueLength():返回正等待获取此锁的线程估计数,比如启动 10 个线程,1 个 线程获得锁,此时返回的是 9

7. getWaitQueueLength:(Condition condition)返回等待与此锁相关的给定条件的线程估计数。比如 10 个线程,用同一个 condition 对象,并且此时这 10 个线程都执行了condition 对象的 await 方法,那么此时执行此方法返回 10

8. hasWaiters(Condition condition):查询是否有线程等待与此锁有关的给定条件 (condition),对于指定 contidion 对象,有多少线程执行了 condition.await 方法

9. hasQueuedThread(Thread thread):查询给定线程是否等待获取此锁

10. hasQueuedThreads():是否有线程等待此锁

11. isFair():该锁是否公平锁

12. isHeldByCurrentThread(): 当前线程是否保持锁锁定,线程的执行 lock 方法的前后分 别是 false 和 true

13. isLock():此锁是否有任意线程占用

14. lockInterruptibly():如果当前线程未被中断,获取锁

15. tryLock():尝试获得锁,仅在调用时锁未被线程占用,获得锁

16. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持, 则获取该锁