java锁小结

说起多线程,不得不提及的一个问题就是线程同步,这时就会涉及到锁的问题,接下来我们来讨论一下java中的锁

首先锁可分为以下几类:

1、可重入锁和不可重入锁

2、公平锁和非公平锁

3、独享锁和共享锁

4、互斥锁和读写锁

5、乐观锁和悲观锁

6、偏向锁/轻量级锁/重量级锁

7、分段锁

8、自旋锁

1、可重入锁和不可重入锁:能否实现递归调用锁,并不产生死锁

(1)可重入锁:可重入锁是指当前线程获取锁之后,内部需要再次获取相同锁(即递归调用锁),这时不需要再次申请锁了,内部可以直接使用(否则会产生死锁),这样的锁称为可重用锁。

       比如:线程A先来申请到锁了,这时需要将state设置为1,之后线程B又来申请锁,这时线程B需要排队等待,但是此时线程A内部再次申请锁,这时线程A内部就直接获取锁,并将state+1,否则会造成死锁。当线程A内部释放了一次锁,就将state-1,。直到线程A的state为1,其他线程才能够获取锁

Synchronized的可重入锁实现方式:为锁关联一个线程持有者和计数器state

ReentrantLock的可重入性实现方式:基于Thread.currentThread();判断持有锁的当前线程以及持有锁执行的次数以及

(2)不可重入锁:与可重入锁相反,不可递归调用(可用自旋锁模拟)

2、一个队列,基于锁内部维护的一个双向链表,结点Node的值就是每一个请求当前锁的线程

(1)公平锁:每次都是按照申请锁的顺序获取锁,依次从队首取值

(2)非公平锁:在等待锁的过程中,如果有任意新的线程想要获取锁,会有很大的几率获取锁,即允许在线程发出请求后立即尝试获取锁

       *Synchronized是公平锁, ReentrantLock提供了两种锁获取方式,FairSynNofairSync(它的公平锁和非公平锁是通过构造方法实现的)

ReentrantLock的公平锁和非公平锁都基于AbstractQueuedSynchronizer#acquire去获取,tryAcquire是公平锁和非公平锁的实现原理所在

3、独享锁和共享锁:

       *AQS的内部类Node定义了两个常量SHAREDEXCLUSIVE, 他们分别标识 AQS队列中等待线程的锁获取模式。

关于AQS的源码解读可以看看这篇:

http://www.cnblogs.com/waterystone/p/4920797.html

Share:Semaphore/CountDownLatch

Exclusive:ReentrantLock

(1)独享锁:每次只能有一个线程持有锁(ReentrantLock就是独占方式的互斥锁,synchronized是独享锁),独占锁是一种悲观保守的加锁机制,它避免读读冲突

(2)共享锁:允许多个线程同时获取锁,并发访问共享资源(ReadWriteLock),共享锁是一种乐观锁,Semaphore是一种共享锁

4、互斥锁和读写锁:是对独享锁和共享锁的具体事项

(1)互斥锁:ReentrantLock

(2)读写锁:ReadWriteLock(支持非公平和公平锁获取方式;支持可重入;锁降级)

5、乐观锁和悲观锁

(1)乐观锁:顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的。

(2)悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。比如Java里面的同步原语synchronized关键字的实现就是悲观锁

       *CAS:一门CPU的原子锁技术,可通过CPU对共享变量加锁的形式,实现数据变更的原子操作,state是volatile类型(volatile和CAS是并发抢占的关键);CAS也是非阻塞算法的一种实现

       *CAS(Compare And Swap),即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。CAS有效地说明了“我认为位置V应该包含值A;如果包含该值,则将B放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。

6、偏向锁/轻量级锁/重量级锁

       *指的是锁的状态,(针对Synchronized,这三种锁是通过对象监视器在对象头中的字段表明)

(1)偏向锁:是指一段同步代码一直被一个线程访问,那么该线程会自动获取锁,降低获取的代价

(2)轻量级锁:是指当有偏向锁的时候,被另一个线程所访问,偏向锁会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能

(3)重量级锁:是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但是自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞状态,该锁膨胀为重量级锁,重量级锁会让其他申请的线程进入阻塞状态,性能降低

7、分段锁:是指锁的设计,ConcurrentHashMap并发的实现是通过分段锁的形式来实现的,ConcurrentHashMap的分段锁称为Segment

分段锁设计目的是:细化锁的粒度,操作不需要更新到整个数组中,仅仅对数组中的一项进行加锁操作

8、自旋锁:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样做的好处:减少了线程的上下文切换消耗,缺点是:循环会消耗CPU适用于加锁时间较短的场景,默认值为10

===========================================================

一、互斥同步

       从处理问题上说,属于悲观的并发策略,解决阻塞和唤醒所带来的性能问题

互斥是因,即手段;同步是果,即目的(互斥的操作有:临界区、互斥量、信号量)

二、非阻塞同步

       基于冲突检测的乐观并发策略(这种并发操作实现不需要将线程挂起,故称为非阻塞同步),要求:操作和冲突检测步骤具有原子性,只能靠硬件实现

CAS(Compare And Swap),CAS漏洞:ABA问题

三、无同步方案

1、可重入代码:条件返回结果可预测

2、线程本地存储(Thread Local Storage):

===========================================================

Synchronized和ReentrantLock的比较:

(1)Synchronized关键字,是基于JVM层面实现了对临界资源的同步互斥访问,ReentrantLock实现了Lock接口的一个类

(2)Synchronized执行完同步代码会自动释放锁,ReentrantLock必须在finally中手动释放锁

(3)Synchronized释放锁条件:①同步代码块执行正常结束;②执行发生异常,jvm让线程释放;ReentrantLock随时释放

(4)Synchronized执行不可中断,即一旦一个线程获取该锁后,即使由于等待IO或者调用sleep方法被阻塞了,也不会释放锁,其他线程必须等待其释放锁,这样会影响程序执行效率。ReentrantLock可以中断操作

(5)Synchronized即使当前两个线程都执行的读操作,但是依然只能有一个线程执行操作,而Lock的实现类ReadWriteLock可以实现多个线程的同步读操作

(6)Synchronized无法得知当前线程是否成功获取锁,ReentrantLock可以得知

(7)Synchronized可重入,非公平,不可中断;ReentrantLock可重入,可判断,可公平或非公平

(8)Synchronized少量同步,ReentrantLock大量同步,可实现同时绑定多个condition对象

===========================================================

Lock获取锁的四种方式:

1、lock():获取锁,如果锁被占用,会一直等待

2、tryLock():boolean类型的方法,该方法会立马返回一个结果,线程不会等待

3、tryLock(long time,TimeUnit unit):给定一定等待时间

4、lockInterruptly():表示可被中断,尝试获取锁,如果获取不到就会等待,在等待过程中可以中断此线程(即中断线程等待过程)

猜你喜欢

转载自blog.csdn.net/sinat_36722750/article/details/81981515