lock和synchronized对比
1.一个关键字,一个是类
2.lock更具灵活性,可以控制锁的获取和释放;
synchronized的锁的释放是被动的,当出现异常或者同步代码块执行完毕以后,才会释放锁;
3.lock可以判断锁的状态,而synchronized无法做到;
4.lock可以实现公平锁、非公平锁,而synchronized只有非公平锁;
实现lock的常见类:
ReentrantLock:表示重入锁,它是唯一一个实现了 Lock 接口的类
ReentrantReadWriteLock:重入读写锁,在这个类中维护了两个锁,一个是 ReadLock,一个是 WriteLock,他们都分别实现了 Lock接口。读写锁是一种适合读多写少的场景,基本原则是: 读和读不互斥、读和写互斥、写和写互斥。
重入锁
什么是重入锁
重入锁,表示支持重新进入的锁,也就是说,某线程获得对象锁后,再次尝试获得该对象锁时,不会阻塞
重入锁设计目的
避免线程的死锁
例:
比如调用 demo 方法获得了当前的对象锁,然后在这个方法中再去调用
demo2,demo2 中的存在同一个实例锁,这个时候当前线程会因为无法获得
demo2 的对象锁而阻塞,就会产生死锁。重入锁的设计目的是避免线程的死
锁。
public class ReentrantDemo{
public synchronized void demo(){
System.out.println("begin:demo");
demo2();
}
public void demo2(){
System.out.println("begin:demo1");
synchronized (this){
}
}
public static void main(String[] args) {
ReentrantDemo rd=new ReentrantDemo();
new Thread(rd::demo).start();
}
}
读写锁
ReentrantReadWriteLock读写锁在同一时刻可以允许多个线程访问,但是在写线程访问时,所有的读线程和其他写线程都会被阻塞,读写锁的性能都会比排它锁好,适合大多数场景读是多于写的。
ReentrantLock
使用案例
public class AtomicDemo {
private static int count=0;
static Lock lock=new ReentrantLock();
public static void inc(){
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
count++;
lock.unlock();
}
public static void main(String[] args) throws
InterruptedException {
for(int i=0;i<1000;i++){
new Thread(()->{AtomicDemo.inc();}).start();;
}
Thread.sleep(3000);
System.out.println("result:"+count);
}
}
实现原理
在多线程竞争重入锁时,竞争失败的线程是如何实现阻塞以及被唤醒的呢?
在 Lock 中,用到了一个同步队列 AQS,全称 AbstractQueuedSynchronizer,AQS 队列内部维护的是一个 FIFO 的双向链表,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
公平锁和非公平锁
区别:
公平锁:锁的获取顺序应该符合请求的绝对时间顺序,也就是FIFO
非公平锁:新线程获取锁的时候,会先通过cas抢占
ReentrantLock默认是非公平锁,构造方法中传入true,则是公平锁;