synchronized 和Lock区别以及可重入锁(ReentrantLock)
对于synchronized 还没理解的可以先看看我之前写的这篇博客讲解synchronized 先对synchronized有一个初步了解:https://blog.csdn.net/dekulugu/article/details/108929818
一、synchronized 和Lock区别
知道了synchronized 和Lock,他们都是实现了多线程的加锁,那么我们就要明白什么时候适合什么锁。
区别如下:
- Lock是java的一个interface接口,而synchronized是Java中的关键字,synchronized是由JDK实现的,不需要程序员编写代码去控制加锁和释放;Lock的接口如下:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
- synchronized修饰的代码在执行异常时,jdk会自动释放线程占有的锁,不需要程序员去控制释放锁,因此不会导致死锁现象发生;但是,当Lock发生异常时,如果程序没有通过unLock()去释放锁,则很可能造成死锁现象,因此Lock一般都是在finally块中释放锁;格式如下:
Lock lock = new LockImpl; // new 一个Lock的实现类
lock.lock(); // 加锁
try{
//todo
}catch(Exception ex){
// todo
}finally{
lock.unlock(); //释放锁
}
-
Lock可以让等待锁的线程响应中断处理,如tryLock(long time, TimeUnit unit),而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够中断,程序员无法控制;
-
是否知道获取锁
Lock可以通过trylock来知道有没有获取锁,而synchronized不能; -
Lock的实现类ReentrantReadWriteLock提供了readLock()和writeLock()用来获取读锁和写锁的两个方法,这样多个线程可以进行同时读操作。
-
synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,
//Condition定义了等待/通知两种类型的方法
Lock lock=new ReentrantLock();
Condition condition=lock.newCondition();
...
condition.await();
...
condition.signal();
condition.signalAll();
- Lock可以绑定条件,实现分组唤醒需要的线程;synchronized要么随机唤醒一个,要么唤醒全部线程。
注意:两者的性能区别
在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。
但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。
2种机制的具体区别:
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。
而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。
现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。.
二、可重入锁(ReentrantLock)
既然要了解可重入锁那么就要先明白可重入是什么意思:可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
-
ReentrantLock最核心的应该就是公平锁了
对于公平锁的实现,就要结合着我们的可重入性质了。公平锁的含义我们上面已经说了,就是谁等的时间最长,谁就先获取锁。
首先new一个ReentrantLock的时候参数为true,表明实现公平锁机制。在这里我们多定义几个线程ABCDE,然后再test方法中循环执行了两次加锁和解锁的过程。 -
非公平锁实现
就在于先new一个ReentrantLock的时候参数为false,当然我们也可以不写,默认就是false。
非公平锁那就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁。 -
响应中断
响应中断就是一个线程获取不到锁,不会傻傻的一直等下去,ReentrantLock会给予一个中断回应。在这里我们举一个死锁的案例。
在这里我们定义了两个锁lock1和lock2。然后使用两个线程thread和thread1构造死锁场景。正常情况下,这两个线程相互等待获取资源而处于死循环状态。但是我们此时thread中断,另外一个线程就可以获取资源,正常地执行了。
测试结果: -
限时等待
也就是通过我们的tryLock方法来实现,可以选择传入时间参数,表示等待指定的时间,无参则表示立即返回锁申请的结果:true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题。
示例:
在这个案例中,一个线程获取lock1时候第一次失败,那就等10毫秒之后第二次获取,就这样一直不停的调试,一直等到获取到相应的资源为止。当然,我们可以设置tryLock的超时等待时间tryLock(long timeout,TimeUnit unit),也就是说一个线程在指定的时间内没有获取锁,那就会返回false,就可以再去做其他事了。
上一篇: 操作系统作业:Linux进程的创建与并发
下一篇: 并发编程从操作系统底层工作的整体认识开始
推荐阅读
-
多线程通信的两种方式? (可重入锁ReentrantLock和Object)
-
关于synchronized和ReentrantLock实现卖票功能以及synchronized的对象锁和全局锁验证
-
售票情景解读synchronized和Lock两种锁的区别
-
AQS-用配钥匙和保险箱理解可重入锁(ReentrantLock)
-
多线程通信的两种方式? (可重入锁ReentrantLock和Object)
-
Lock(四) — 可重入锁(ReentrantLock)源码解析
-
Synchronized 和 Volatile、Lock 以及 ReentrantLock的区别
-
synchronized 和Lock区别以及可重入锁(ReentrantLock)
-
synchronized关键字的理解以及和Lock,ReentrantLock的区别
-
可重入锁ReentrantLock,lock/unlock源码解读