Lock
的标准用法
lock.lock();
try {
count++;
} finally{
lock.unlock();
}
Lock
的常用
API
ReentrantLock
锁的可重入
“同一个线程对于已经获得到的锁,可以多次继续申请到该
锁的使用权”。
而
synchronized
关键字隐式的支持重进入,比如一个
synchronized
修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍能连续多次地获得
该锁。
ReentrantLock
在调用
lock()
方法时,已经获取到锁的线程,能够再次调用
lock()
方法获取锁而不被阻塞。
读写锁
ReentrantReadWriteLock
ReentrantReadWriteLock
其实实现的是
ReadWriteLock
接口。
之前提到锁(如
Mutex
和
ReentrantLock
)基本都是排他锁,这些锁在同一
时刻只允许一个线程进行访问,而读写锁在同一时刻可以允许多个读线程访问,
但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对
锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁
有了很大提升。
一般情况下,读写锁的性能都会比排它锁好,因为大多数场景读是多于写的。
在读多于写的情况下,读写锁能够提供比排它锁更好的并发性和吞吐量
Condition
接口
任意一个
Java
对象,都拥有一组监视器方法(定义在
java.lang.Object
上),
主要包括
wait()
、
wait(long timeout)
、
notify()
以及
notifyAll()
方法,这些方法与
synchronized
同步关键字配合,可以实现等待
/
通知模式。
Condition
接口也提供
了类似
Object
的监视器方法,与
Lock
配合可以实现等待
/
通知模式
condition使用范式
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
public void conditionWait() throws InterruptedException{
lock.lock();
try {
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() throws InterruptedException{
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
了解
LockSupport
LockSupport
定义了一组的公共静态方法,这些方法提供了最基本的线程阻
塞和唤醒功能,而
LockSupport
也成为构建同步组件的基础工具。
LockSupport
定义了一组以
park
开头的方法用来阻塞当前线程,以及
unpark(Thread thread)
方法来唤醒一个被阻塞的线程。
LockSupport
增加了
park(Object blocker)
、
parkNanos(Object blocker,long nanos)
parkUntil(Object
blocker,long deadline)
方法,用于实现阻塞当前线程的功能,其中参数
blocker
是用来标识当前线程在等待的对象(以下称为阻塞对象),该对象主要用于问题
排查和系统监控。
CLH 队列锁
CLH
队列锁即
Craig, Landin, and Hagersten (CLH) locks ,这三个人的名字首字母组成
。
CLH
队列锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程
仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束
自旋。
当一个线程需要获取锁时:
1.
创建一个的
QNode
,将其中的
locked
设置为
true
表示需要获取锁,
myPred
表示对其前驱结点的引用
2.
线程
A
对
tail
域调用
getAndSet
方法,使自己成为队列的尾部,同时获取
一个指向其前驱结点的引用
myPred
线程
B
需要获得锁,同样的流程再来一遍
3.
线程就在前驱结点的
locked
字段上旋转,直到前驱结点释放锁
(
前驱节点
的锁值
locked == false)
4.
当一个线程需要释放锁时,将当前结点的
locked
域设置为
false
,同时回收
前驱结点
如上图所示,前驱结点释放锁,线程
A
的
myPred
所指向的前驱结点的
locked
字段变为
false
,线程
A
就可以获取到锁。
CLH
队列锁的优点是空间复杂度低(如果有
n
个线程,
L
个锁,每个线程每
次只获取一个锁,那么需要的存储空间是
O
(
L+n
),
n
个线程有
n
个
myNode
,
L
个锁有
L
个
tail
)。
CLH
队列锁常用在
SMP
体系结构下。
Java
中的
AQS
是
CLH
队列锁的一种变体实现。
AbstractQueuedSynchronizer
AQS
使用方式和其中的设计模式
AQS
的主要使用方式是继承,子类通过继承
AQS
并实现它的抽象方法来管
理同步状态,在
AQS
里由一个
int
型的
state
来代表这个状态,在抽象方法的实
现过程中免不了要对同步状态进行更改,这时就需要使用同步器提供的
3
个方法
(
getState()
、
setState(int newState)
和
compareAndSetState(int expect,int update)
)
来进行操作,因为它们能够保证状态的改变是安全的。
模板方法模式
同步器的设计基于模板方法模式。模板方法模式的意图是,定义一个操作中
的算法的骨架,而将一些步骤的实现延迟到子类中。模板方法使得子类可以不改
变一个算法的结构即可重定义该算法的某些特定步骤。我们最常见的就是 S
pring
框架里的各种
Template
。
AQS
中的方法
模板方法
实现自定义同步组件时,将会调用同步器提供的模板方法,
可重写的方法
访问或修改同步状态的方法
重写同步器指定的方法时,需要使用同步器提供的如下
3
个方法来访问或修
改同步状态。
•
getState()
:获取当前同步状态。
•
setState(int newState)
:设置当前同步状态。
•
compareAndSetState(int expect,int update)
:使用
CAS
设置当前状态,该方
法能够保证状态设置的原子性
来源:oschina
链接:https://my.oschina.net/y986/blog/4257779