本文共 3829 字,大约阅读时间需要 12 分钟。
lock
ReentrantLock
这个问题没看~~~
说说ReentrantLock是基于哪个类的?说说队列同步器synchronized是java中的一个关键字
,也就是说是Java语言内置的特性
。那么为什么会出现Lock呢?
理由一: synchronized等待获取锁的线程一直等待,直到获取锁
代码块被synchronized修饰时,若获得锁的线程要等待IO或者其他原因(sleep)被阻塞了,那么其他线程只能等待该线程释放锁(方式1:正常运行完代码块,线程释放锁;方式2:发生异常,JVM让线程自动释放锁),这样会很影响程序执行效率。
而Lock可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断)。
理由二: synchronized不能实现共享读,而lock能实现共享读
当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象
。
但是采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,那么线程在进行读操作时
,其他线程只能等待
无法进行读操作。
而Lock可以使得多个线程都只是进行读操作时,线程之间不会发生冲突。
理由三: Lock可以知道线程有没有成功获取到锁
通过synchronized不知道线程有没有成功获取到锁
,而Lock无法办到的。
1)Lock是一个接口
,而synchronized是Java中的关键(JVM)字
,synchronized是内置的语言实现
;
2)释放锁的方式不同:
如果采用synchronized不需要用户去手动释放锁
,当synchronized方法或者synchronized代码块执行完之后或者程序出现异常时,系统会自动让线程释放对锁的占用;
如果采用Lock
,必须主动去释放锁
,并且在发生异常时,不会自动释放锁
。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁
的操作放在finally块
中进行,以保证锁一定被被释放,防止死锁的发生
。
3)获取不到锁的处理方式的不同:
tryLock()
方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true
,如果获取失败(即锁已被其他线程获取),则返回false
,也就说这个方法无论如何都会立即返回,在拿不到锁时不会一直等待
。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁
,就返回false
。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
lockInterruptibly()方法比较特殊
,当通过这个方法去获取锁时,如果线程正在等待获取锁
,则这个线程能够响应中断
,即中断线程的等待状态
。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程
。
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()
必须放在try块
中,或者在调用lockInterruptibly()的方法外声明抛出InterruptedException
。注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的
。因为本身在前面的文章中讲过单独调用interrupt()
方法不能中断正在运行过程中的线程
,只能中断阻塞过程中的线程
。
而用synchronized
修饰的话,当一个线程处于等待某个锁
的状态,是无法被中断的
,只有一直等待下去
。synchronized只能是非公平锁
。所谓的公平锁就是先等待的线程先获得锁。
4)唤醒等待线程的方式不同
ReenTrantLock提供了一个Condition(条件)类
,用来实现分组唤醒
,需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程,要么唤醒全部线程。
5)读共享,写不共享的实现
采用synchronized关键字来实现同步的话,就会导致一个问题:如果多个线程都只是进行读操作,所以当一个线程在进行读操作时
,其他线程只能等待
无法进行读操作。通过Lock
就可以使得多个线程进行读操作时
,线程之间不会发生冲突
。
6)性能的不同
在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized
引入了偏向锁,轻量级锁(自旋锁)
后,两者的性能就差不多了
,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化借鉴了ReenTrantLock中的CAS技术,都是试图在 用户态 就把加锁问题解决,避免进入内核态 的线程阻塞。
7)可重入性
ReenTrantLock和synchronized都是是可重入的
。两者都是同一个线程每进入一次
,锁的计数器都自增1
,所以要等到锁的计数器下降为0时才能释放锁
。
8)公平锁和非公平锁
ReenTrantLock可以指定是公平锁还是非公平锁
。而synchronized只能是非公平锁
。所谓的公平锁就是先等待的线程先获得锁
。
ReentrantLock(可重入锁)实现了Lock接口的类
,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。
ReentrantLock是一个可重入且独占式的锁
,它具有与使用synchronized监视器锁相同的基本行为和语义,但与synchronized关键字相比,它更灵活、更强大,增加了轮询、超时、中断等高级功能
。ReentrantLock,顾名思义,它是支持可重入锁的锁,是一种递归无阻塞的同步机制
。除此之外,该锁还支持获取锁时的公平和非公平
选择。
所谓的公平与非公平指的是在请求先后顺序上
,先对锁进行请求的就一定先获取到锁,那么这就是公平锁
,反之,如果对于锁的获取并没有时间上的先后顺序,如后请求的线程可能先获取到锁,这就是非公平锁
。一般而言非,非公平锁机制
的效率往往会胜过
公平锁的机制
,但在某些场景下,可能更注重时间先后顺序
,那么公平锁自然是很好的选择
。
实现重进入
重进入是指任意线程在获取到锁之后
能够再次获取该锁而不会被锁阻塞
,该特性的首先需要解决以下两个问题:
线程再次获取锁:锁需要去识别获取锁的线程
是否为当前占据锁的线程
,如果是,则再次获取成功;
锁的最终释放:线程重复n次获取了锁,随后在第n次释放该锁后,其它线程能够获取到该锁。锁的最终释放要求锁对于获取进行计数自增
,计数表示当前线程被重复获取的次数,而被释放时,计数自减
,当计数为0时表示锁已经成功释放
。
ReentrantLock是通过自定义同步器
来实现锁的获取与释放
,我们以非公平锁(默认)
实现为例,对锁的获取和释放进行详解。
ReadWriteLock也是一个接口,在它里面只定义了两个方法
public interface ReadWriteLock { /** * Returns the lock used for reading. * * @return the lock used for reading. */ Lock readLock(); /** * Returns the lock used for writing. * * @return the lock used for writing. */ Lock writeLock();}
一个用来获取读锁
,一个用来获取写锁
。也就是说将文件的读写操作分开
,分成2个锁来分配给线程
,从而使得多个线程可以同时进行读操作
。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。
ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁
。
如果有一个线程已经占用了读锁
,则此时其他线程如果要申请写锁
,则申请写锁的线程
会一直等待释放读锁
。
如果有一个线程已经占用了写锁
,则此时其他线程如果申请写锁或者读锁
,则申请的线程会一直等待释放写锁
。
基于队列同步器
AbstractQueueSychronier :队列同步器维护一个同步队列(双向链表),同时也维护一个同步状态state;它的方法分类:独占式锁和分享式锁
独占式锁
的实现:加入同步队列时
,结点需要自旋
查看它的前一个结点是否获取锁
,如果获取的话
,它应该尝试获取锁
(tryAcquire)。
详情请见:
转载地址:http://irbrb.baihongyu.com/