Java面试03|并发及锁

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java面试03|并发及锁相关的知识,希望对你有一定的参考价值。

 

 

 

1、synchronized与Lock的区别

 

使用synchronized这个关键字实现的同步块有一些缺点: 

(1)锁只有一种类型 

(2)线程得到锁或者阻塞 

(3)Lock是在Java语言层面基于CAS自旋方式来实现锁的,在并发条件下,其性能要相对比synchronized好一些。 

为了解决如上的各种问题,后来又提出了一种更为复杂的锁 - 线程锁。线程锁可以在几个方面进行提升: 

(1)添加不同类型的锁,如读取锁和写入锁(主要实现类为ReentrantReadWriteLock类) 

(2)对锁的阻塞没有限制,即可以在一个方法中上锁,在另外一个方法中解锁。 

由于Semaphore不会将许可与特定的线程关联起来,因此在一个线程中获得的许可可以在另外一个线程中释放。

(3)如果线程得不到锁,比如锁由另外一个线程持有,就允许该线程后退或继续执行,或者做其他事情。如使用tryLock()与tryLock(long,TimeUnit)。当发现尝试加锁无法加上,可以先释放对其他对象已经成功添加的锁,过一会儿再尝试,这样可以一定程度上避免死锁。

 

(4)允许线程尝试取锁,并可以在超过等待时间后放弃。

 

ReentrantLock实现了Lock接口,并提供了与synchronized相同的互斥性和内存可见性。在获取/退出ReentrantLock时,有着与进入/退出同步代码块相同的内存语义。同时也提供可重入的加锁语义。

ReentrantLock相比synchronized增加了一些高级功能,主要有3项:

(1)等待可中断

(2)可实现公平锁(synchronized实现的是非公平锁)

(3)锁可以绑定多个条件(一个ReentrantLock可以同时绑定多个Condition对象

 

ReentrantLock获取锁定与三种方式:
a) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁 

b) tryLock(),如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false 

c) tryLock(long timeout,TimeUnit unit) 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false

d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

 

 

 

2、Lock机制的实现

Lock的实现其内部有个AQS机制。AQS(AbstractQueuedSynchronizer)是java中管理“锁”的抽象类,锁的许多公共方法都是在这个类中实现。AQS是独占锁(例如ReentrantLock)和共享锁(例如Semaphore)的公共父类。 

队列是AQS中“等待锁”的线程队列。在多线程中,为了保护竞争资源不被多个线程同时操作而起来错误,我们常常需要通过锁来保护这些资源。在独占锁中,竞争资源在一个时间点只能被一个线程锁访问;而其它线程则需要等待。队列就是管理这些“等待锁”的线程的队列。

队列是一个非阻塞的 FIFO 队列。也就是说往里面插入或移除一个节点的时候,在并发条件下不会阻塞,而是通过自旋锁和 CAS 保证节点插入和移除的原子性

 

举个例子,如独占锁ReentrantLock。ReentrantLock分为“公平锁”和“非公平锁”。它们的区别体现在获取锁的机制上是否公平。ReentrantLock是通过一个FIFO的等待队列来管理获取该锁所有线程的。

(1)在“公平锁”的机制下,线程依次排队获取锁;

(2)“非公平锁”在锁是可获取状态时,不管自己是不是在队列的开头都会获取锁。

 技术分享

 

以非公平锁的实现为例,如下:

技术分享

 

释放锁的实现过程如下:

技术分享

 

参考文章:

(1)http://tech.meituan.com/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html

 

 

3、源码剖析AQS在几个同步工具类中的使用

详细查看这篇文章:

(1)http://ifeve.com/abstractqueuedsynchronizer-use/#more-18899

(2)CountdowLatch的实现原理:http://blog.csdn.net/yanyan19880509/article/details/52349056

技术分享

 

 

 

工具类 工具类作用 工具类加锁方法 工具类释放锁方法 Sync覆盖/非覆盖的方法 state的作用及锁的类型 锁维护
Semaphore 控制同时访问某个特定资源的操作数量 acquire:每次请求一个许可都会导致计数器减少1,,一旦达到了0,新的许可请求线程将被挂起 release:每调用 添加一个许可,释放一个正在阻塞的获取者

覆盖:

tryAcquireShared tryReleaseShared

 

 

表示初始化的许可数

共享锁

每一次请求acquire()一个许可都会导致计数器减少1,同样每次释放一个许可release()都会导致计数器增加1,一旦达到了0,新的许可请求线程将被挂起。
CountDownLatch 把一组线程全部关在外面,在某个状态时候放开。一种同步机制来保证一个或多个线程等待其他线程完成。 await:在计数器不为0时候阻塞调用线程,为0时候立即返回 countDown :计数递减 tryAcquireShared tryReleaseShared

维护一个计数器

共享锁

初始化一个计数,每次调用countDown方法计数递减,在计数递减到0之前,调用await的线程都会阻塞
ReentrantLock 标准的互斥操作,也就是一次只能有一个线程持有锁 lock:如果没有线程使用则立即返回,并设置state为1;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用,等待 tryLock:如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false unlock:尝试释放锁,如果当前线程占有锁则count减一,如果count为0则释放锁。如果占有线程不是当前线程,则抛异常

覆盖:

tryAcquire

tryRelease

 

非覆盖:

nonfairTryAcquire

state表示获得锁的线程

对锁的重入次数

排他锁

获取锁时,如果没有线程使用则立即返回,并设置state为1;如果当前线程已经占有锁,则state加1;如果其他线程占有锁,则当前线程不可用。释放锁时,在该方法中主要作用是state状态位减少release个,表示释放锁,如果更新后的state为0,表示当前线程释放锁,如果不为0,表示持有锁的当前线程重入数减少
ReentrantReadWriteLock 读写锁。允许多个读线程同时持有锁,但是只有一个写线程可以持有锁。写线程获取写入锁后可以再次获取读取锁,但是读线程获取读取锁后却不能获取写入锁 ReadLock#lock :获取读锁 ReadLock#tryLock:尝试当前没有其他线程当前持有写锁时获取读锁 WriteLock#lock:获取写锁 WriteLock#tryLock:尝试当前没有其他线程持有写锁时,呼气写锁。 ReadLock#unlock:释放读锁 WriteLock#unlock:释放写锁

覆盖:

acquireShared releaseShared

tryAcquire

tryRelease

非覆盖:

tryReadLock tryWriteLock

高16位表示共享锁的数量,

低16位表示独占锁的重入次数

读锁:共享

写锁:排他

对于共享锁,state是计数器的概念。一个共享锁就相对于一次计数器操作,一次获取共享锁相当于计数器加1,释放一个共享锁就相当于计数器减1;排他锁维护类似于可重入锁。
FutureTask 封装一个执行任务交给其他线程去执行,开始执行后可以被取消,可以查看执行结果,如果执行结果未完成则阻塞。 V get()

run()

set(V)

cancel(boolean)

覆盖:

tryAcquireShared tryReleaseShared

非覆盖:

innerGet innerRun() innerSet innerIsCancelled

 

state状态位来存储执行状态RUNNING、RUN、CANCELLED

共享锁

获取执行结果的线程(可以有多个)一直阻塞,直到执行任务的线程执行完毕,或者执行任务被取消。

以上是关于Java面试03|并发及锁的主要内容,如果未能解决你的问题,请参考以下文章

(多线程与并发)面试题03--java中读写锁ReadWriteLock

(多线程与并发)面试题03--java中读写锁ReadWriteLock

Java并发机制及锁的实现原理

JAVA并发之锁获取步骤及锁优化

Java并发机制及锁的实现原理

阿里面试失败后,一气之下我图解了Java中18把锁