锁用于解决多线程对共享资源的同时访问,而引起的非线程安全问题:某一时该只有一条线程可以访问共享资源,达到了线程安全的目的,但同时也限制了并发处理的速度。
锁的分类:
同步锁: synchronized 是java 内置的语法
可重入锁:ReentrantLock 是一个java类
读写锁:ReentrantReadWriteLock 是一个java类
锁定的范围:
由于锁使线程互斥访问,未获得锁的线程会阻塞,限制了并发处理的速度,因此线程应该锁定尽量小的范围及尽快的释放锁。
比如一家企业,有10个厕所,每个厕所有5个坑,每个员工都要上厕所,厕所的同一个坑不可能由多人共同使用,每个员工相当于一线线程,茅坑相当于共享资源,因此需要多个员工之间互斥使用同一个茅坑。
错误的锁定范围:
10个厕所:意味着 某个员工使用某个茅坑时,所有厕所都不能使用,严重浪费资源,影响并发使用率。
每个厕所:意味着 某个员工使用某个茅坑时,该厕所不能被别人使用,其他9个厕所允许9个人分别使用,浪费资源,影响并发使用率。
正确的锁定范围:
每个茅坑:意味着 某个员工使用某个茅坑时,该茅坑不能被别人使用,其他49个茅坑允许49个人分别使用。
java中的锁定对象:
synchronized的锁定对象:
语法:synchronized(被锁定对象) 或 在方法签名上声明
全局锁定:Class对象,static 对象及方法,singleton 实例,equals相等的String对象
部分锁定:某个多例类的实例,不同实例由不同的线程同时访问。
public class Test {
public synchronized void kk(){//做用于方法,锁定当前实例,不同实例间不影响
}
public static synchronized void ww(){//做用于静态方法锁定的是Class对象
}
public void t(Object obj){
synchronized(obj){//锁定指定对象
//
}
}
public void f( ){
synchronized(this){//锁定当前实例
//
}
}
}
ReentrantLock 锁定对象:
同一个ReentrantLock实例,不同实例可由不同线程同时访问。
用法:
class X {
private final ReentrantLock lock = new ReentrantLock();
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
ReentrantReadWriteLock
readLock 共享锁,占用锁时,同一个ReentrantReadWriteLock实例的writeLock,被阻塞,同一个ReentrantReadWriteLock实例的readLock可以同时访问。
writeLock 独占锁,占用锁时,同一个ReentrantReadWriteLock实例的writeLock和readLock阻塞,直至锁被释放,不同实例可由不同线程同时访问
锁的释放:
1 代码执行完成,退出锁定
2.由于条件不足,而无法继续执行,主动释锁定,等待条件满足,当条件满足时,需要另一条线程唤醒等待线程,唤醒的线程争得锁之后主动释放锁的代码之后执行
A.synchronized
1.synchronized方法或代码块执行完成,而释放锁
2.synchronized:被锁定对象.wait()方法被调用时释放锁而等待, 被唤醒方式:被锁定对象.notifyAll()(这两个方法需要执行锁时才能执行)
B. ReentrantLock
1.代码执行完成后,ReentrantLock.unlock()代码被调用,而释放锁
2.由ReentrantLock.newCondition()生成Condition对象的await()方法被调用时,释放锁而等待,被唤醒方式同一个Condition对象的signalAll方法被调用。(这两个方法需要执行锁时才能执行)
C.ReentrantReadWriteLock
1.代码执行完成后,ReentrantReadWriteLock的readLock 或writeLock 对象的unlock方法被调用,而释放锁
2.readLock 不支持由条件不足释放锁定而等待条件满足。writeLock.newCondition()生成Condition对象的await()方法被调用时,释放锁而等待,被唤醒方式同一个Condition对象的signalAll方法被调用。(这两个方法需要执行锁时才能执行)
class RWDictionary {
private final Map<String, Data> m = new TreeMap<String, Data>();
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock r = rwl.readLock();
private final Lock w = rwl.writeLock();
public Data get(String key) {
r.lock();
try { return m.get(key); }
finally { r.unlock(); }
}
public String[] allKeys() {
r.lock();
try { return m.keySet().toArray(); }
finally { r.unlock(); }
}
public Data put(String key, Data value) {
w.lock();
try { return m.put(key, value); }
finally { w.unlock(); }
}
public void clear() {
w.lock();
try { m.clear(); }
finally { w.unlock(); }
}
信号量Semaphore :
一般用于限制对某些资源固定数量的访问。
Semaphore维护了N个的许可,某线程通过acquire()获取一个许可或acquire(int permits) 获取多许可,如果许可数量N大于线程要求的许可数,线程得到许可并可以继续执行。
否则线程阻塞,等待某线程释放许可。
某线程释放许可,许可数量增加并唤醒被阻塞的线程,阻塞线程唤醒后再试着获取许可,获取成功则继续执行否则再次阻塞。
不使用锁解决非线程安全问题(有一定局限性):
实现方式:一般使用CAS (compare and swap) 实现
使用场景:AtomicLongFieldUpdater 对volatile long 字段进行原子更新
AtomicReferenceFieldUpdater 对 volatile 字段进行原子更新
AtomicReference 原子方式更新的对象引用
示例:ConcurrentLinkedQueue
boolean casNext(Node<E> cmp, Node<E> val) {
return nextUpdater.compareAndSet(this, cmp, val);
}
public boolean offer(E e) {
if (e == null) throw new NullPointerException();
Node<E> n = new Node<E>(e, null);
for (;;) {
Node<E> t = tail;
Node<E> s = t.getNext();
if (t == tail) {
if (s == null) {
if (t.casNext(s, n)) {
casTail(t, n);
return true;
}
} else {
casTail(t, s);
}
}
}
}
private static final
AtomicReferenceFieldUpdater<Node, Node>
nextUpdater =
AtomicReferenceFieldUpdater.newUpdater
(Node.class, Node.class, "next");