lock锁的原理(AQS算法) - 草稿 - 草稿

解决多线程的并发安全问题,Java无非就是加锁,具体就是2个方法。

1.Synchronized(java自带的关键字)

2.lock可重入锁(可重入锁这个包java.util.concurrent.locks底下有2个接口,分别对应两个实现类)

      a.lock接口,实现类为:ReentrantLock类,可重入锁。

      b.readwirtelock接口,实现类为ReentrantReadWriteLock读写锁。

有就是说有三种:

1、synchronized是互斥锁。

2、ReentrantLock顾名思义是:可重入锁。

3、ReentrantReadWirteLock是:读写锁。

读写锁的特点:
a)多个线程可以同时进行读。
b)写线程必须互斥(只允许一个线程写,也不允许读和写同时进行)
c)写线程优先于读线程。(一旦有写线程,则后续读线程必须等待,唤醒时优先考虑写线程。)

总结来说:Lock和Synchronized有以下几点不同:

1)Lock是一个接口,而Synchronized是Java中的关键字,Synchronized是内置的语言实现。

2)当Synchronized块执行结束时,会自动释放锁,而lock一般需要我们手动在finally块中释放。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而lock在发生异常时,如果没有主动通过unlock()去释放锁,则很可能造成死锁现象,因此使用lock时需要在finally块中释放锁。

3)lock等待锁过程中可以用interrupt来终断等待,而synchronized只能等待锁的释放,不能响应锁的中断。

4)lock可以通过trylock来知道线程有没有获取到锁,而synchronized不能知道。

5)当synchronized执行时,只能使用非公平锁,无法实现公平锁。而lock可以通过new ReentrantLock(true) 来设置为公平锁,从而在某些场景下提高效率。

6)lock可以提高多个线程进行读写操作的效率。(通过readwirtelock实现读写分离)

7)synchronized锁类型:可重入、不可中断、非公平。而lock锁类型:可重入、可中断、可公平。

在性能上来说,如果资源竞争不激烈,两者的性能是查不多的,而当竞争资源非常激烈时,此时lock的性能要远远优于synchronized。

首先来看一下synchronized的原理:

1、synchronized

把代码块声明为synchronized,有2个重要的后果,通常是指该代码具有原子性和可见性。实现原子性的算法为CAS

(1)原子性

原子性意味着某个时刻 ,只有一个线程能够执行一段代码,这段代码通过一个monitor object保护。从而防止多个线程在更新共享状态时相互冲突。

(2)可见性

可见性更为微妙,它要对付内存缓存和编译器优化的各种反常行为。啥是可见性呢?

答:它必须确保释放锁之前对共享数据做出的更改对随后获得该锁的另一个线程是可见的。
作用:如果没有同步机制提供的这种可见性保证,线程看到的共享变量可能是修改前的值或不一致的值,这将引发许多严重问题。一般来说,线程以某种不必让其他线程立即可以看到的方式,不受缓存变量值的约束,但是开发人员如果使用了同步,那么运行库将确保某一线程对变量所做的更新先于对现有synchronized块所进行的更新。当进入由同一监控器(lock)保护的另一个synchronized块时,将立刻可以看到这些对变量所做的更新。类似的规则也存在于volatile变量上。
volatile只保证可见性,不保证原子性!

(3)synchronized的限制:

1.当线程尝试获取锁的时候,如果获取不到锁会一直阻塞,他无法中断一个正在等侯获得锁的线程;

2.如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待,也无法通过投票得到锁,如果不想等下去,也就没法得到锁。

2、ReentrantLock(可重入锁)

可重入的意思是某一个线程是否可多次获得一个锁,在继承的情况下,如果不是可重入的,那就形成死锁了,比如递归调用自己的时候;如果不能可重入,每次都获取锁不合适,比如synchronized就是可重入的,ReentrantLock也是可重入的。

2.1、为什么要可重入

如果线程A继续再次获取这个锁呢?比如一个方法是synchronized的,递归调用自己,那么第一次就已经获得了锁,在第二次调用的时候还能进入吗?直观上当然需要能进入,这就是可重入,可重入锁也叫递归锁,不然就死锁了。

它实现的方式是:

为每个锁关联一个获取计数器和一个所有者线程,当计数器为0时,这个锁就没有被任何线程持有,当线程请求一个未被持有的锁时,JVM会记下锁的持有者,并且将获取计数值置为1,如果同一个线程再次获取这个锁,计数值将递增,退出一次同步代码块,计数值递减,当计数值为0时,这个锁就被释放。ReentrantLock里面有实现。
其实也有不可重入锁:linux下的pthread_mutex_t锁是默认非递归的。jdk里面没有默认的实现类。
java.util.concurrent.lock中的Lock框架是锁 定的一个抽象,lock弥补了synchronized的局限,提供了更加细粒度的加锁功能。
ReentrantLock是唯一实现了Lock接口的实现类,它拥有与syncjronized相同的并发性和内存语义。但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,他还提供了在激烈竞争下更佳的性能。
使用synchronized修饰的方法或者语句块在代码执行完成后会自动释放锁。而lock需要我们手动释放锁,所以为了保证锁最终被释放,需要把互斥区放在try内,释放锁放在finally内。

Lock接口API如下:

public interface Lock{           
void lock(); 
void lockInterruptibly();
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();

lock()、lockInterruptibly()、trylock()、和trylock(long time,TimeUnit unit)是原来获取锁的。

unlock()是用来释放锁的。

在lock中声明了4个方法来获取锁,那么这4个方法有什么区别吗?

lock()方法是平时使用的最多的一个方法,就是用来获取锁,如果锁已被其他线程获取,则进行等待。因为lock锁是不会自动释放的,所以一般使用lock()方法时会在try块中进行,并且将锁的释放放在finally块中,来保证锁一定会被释放,防止死锁的发生。

tryLock()方法是有返回值的,他表示用来尝试获取锁,如果获取成功,则返回true,若获取失败则返回false(表示锁已被其他线程获取)。也就是说这个方法无论获取成功与失败都会返回结果,并不会在获取不到锁的时候一直在等待。

tryLock(long time,TimeUtil util)方法也是和tryLock方法类似的,只不过这个方法的区别是它在拿不到锁的时候是会等待一定的时间的,不会直接返回false,在等待时间结束后,如还未拿到锁,则返回false。如果在一开始或者等待期内拿到锁,则返回true。

lockInterruptibly()方法比较特殊,当通过这个方法来获取锁时,若线程处在等待状态,则这个线程可以响应中断,也就是说中断线程的等待状态。假如说有2个线程A和B申请同一个锁,当A线程得到锁之后,则B线程就会进入等待状态,此时我们可以通过threadB.interrupt()方法来终断线程B的等待过程。

注意:当一个线程获得锁之后,是不会被interrupt()方法中断的。单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
因此当通过lockinterruptibly()方法来获取某个锁的时候,如果不能获取到,只有进行等待的情况下,是可以响应中断的。
而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

2 AQS

abstractqueuedsynchronized简称aqs。是一个用于构建锁和同步容器的框架。事实上concurrent包内许多类都是基于aqs构建。例如ReentrantLock,Semaphore,CountDownlatch,ReentrantReadWirteLock,FutureTask等。aqs解决了在实现同步容器时设计的大量细节问题。

你可能感兴趣的:(lock锁的原理(AQS算法) - 草稿 - 草稿)