Java并发编程艺术 5 Java中的锁

来源:互联网 发布:js获取当前城市 编辑:程序博客网 时间:2024/06/05 13:25
第五章 Java中的锁

Lock接口
在Lock接口出现之前,Java使用synchronized管金子实现锁功能,在JavaSE 5 后,在并发包中新增了Lock接口(相关实现)来完成锁功能。Lock提供与synchronized类似的同步功能。

Lock在使用过程中需要显式的获取和释放锁。,缺少了synchronized提供隐式获取释放的便捷性。但是提供了锁的操作性,可以中断获取锁、超时获取锁等同步特性
synchronized隐式的获取和释放锁,将锁的获取和释放固化。简单快捷。

Lock lock = new ReentrantLock();
lock.lock();   不要讲获取锁放在try中,如果获取锁异常(自定义锁实现),异常抛出同时,会导致锁无故释放。
try{
}finally{
     lock.unlock();  //在finally中释放锁,确保最终被释放。
}

队列同步器 AbstractQueuedSynchronizer
同步器是实现锁的关键,在锁的实现中聚合同步器,利用同步器实现锁的语意。
锁面向使用者,同步器面向锁功能的实现

同步器依赖内部的同步队列(双向的FIFO队列)来完成同步状态的管理。当前线程获取同步状态失败的时候,同步器将当前线程以及同步状态信息构建成一个Node保存到同步队列中,同时当前线程阻塞。当同步状态释放,队列中的首节点唤醒,再次去获取同步状态。
线程获得同步状态的顺序是先入先出(FIFO)。


重入锁
重入锁ReentrantLock,表示该锁能够支持一个线程对资源的重复加锁。在同一个线程中,获取同步锁不会阻塞。该锁还支持获取锁的公平性和非公平性选择。在调用lock()方法是,已经获取到锁的线程能够再次调用lock()方法获取锁而不被阻塞。
synchronized关键字一样支持隐式的重进入。

实现重入性:
1.线程再次获取锁。锁需要先是被获取锁的线程是否为当前占据锁的线程,如果是则再次成功获取,并且获取计数自增。在所对象中保存当前持有锁的线程。
2.锁的最终释放。线程重复n次获取了锁,随后在第n次进行释放该锁后,其他线程才能够获取到该锁。   锁的最终释放要求锁对于获取进行计数自增,计数表示当前锁被重复获取的次数,而锁被释放时,计数自减,当计数等于0时表示锁已经成功释放。

public class MyReentrantLock {
     private boolean isLocked = false;
     private Thread LockedBy = null;
     private int lockedCount = 0; // 重入锁次数,防止线程错乱 this.LockedBy !=
                                                // Thread.currentThread()
     /*
      * 记录同一个线程重复对一个锁对象加锁的次数。否则,一次unblock()调用就会解除整个锁,即使当前锁已经被加锁过多次。
      * 在unlock()调用没有达到对应lock()调用的次数之前,我们不希望锁被解除。
      */
     public synchronized void lock() throws InterruptedException {

           while (isLocked && Thread.currentThread() != LockedBy) { // 自旋锁:防止虚假唤醒ifeve.com-6
                wait();
           }
           this.isLocked = true;
           this.lockedCount++;
           this.LockedBy = Thread.currentThread();
     }
     public synchronized void unlock() {
           if (this.LockedBy != Thread.currentThread()) { // 防止线程错乱,没有持锁的线程进行解锁操作
                throw new IllegalMonitorStateException("Calling thread has not locked this lock");
           }
           lockedCount--;
           if (lockedCount == 0) {
                this.isLocked = false;
                this.LockedBy = null;
                notify();
           }
     }
}


读写锁
重入锁是怕他锁,同一时刻只允许一个线程进行访问。但是读写锁同一时刻可以允许多个读线程访问,但是写进程访问时,所有读线程和其他写线程都被阻塞。
同时维护一对锁,一个读锁和一个写锁,分离读写可以使并发性有很大提升。一般情况下读场景是多于写的,所以读写锁的性能会更好,更好的并发性和吞吐量。

int getReadLockCount();    反复当前线程读锁被获取的次数。(包含子线程)。次数不等于获取读锁的线程数,如果一个线程重入了n次读锁。那这个方法返回的是n。
int getReadHoldCount();   返回当前线程获取读锁的次数。(不包含子线程)。也算重入计数。
boolean isWriteLocked();  判断写锁是否被读取。
int getWriteHoldCount();  返回当前写锁被获取的次数

HashMap是线程不安全的,通过读写锁来保证线程安全。在get读操作中,需要先获取读锁,在并发访问时get时,不会阻塞。在put写操作中需要先获取写锁。当获取写锁后,其他线程的读和写都被阻塞。
public class ReadWriteLockTest {
     static ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
     static Lock rlock = rwlock.readLock();
     static Lock wLock = rwlock.writeLock();
     static Map<String, Object> cache = new HashMap<>();

     public static Object get(String key){
           rlock.lock();
           try {
                return cache.get(key);
           } finally {
                rlock.unlock();
           }
     }
     public static void put(String key, Object value){
           wLock.lock();
           try {
                cache.put(key, value);
                //map.clear()
           } finally {
                wLock.unlock();
           }
     }

}
写锁的获取与释放
写锁是一个支持重入的排他锁。如果其他线程持有读锁或者写锁,当前线程进入等待状态。当前获取到写锁后,增加写状态。
在获取写锁的过程中,加入了读锁是否存在的判断,因为读写锁要求写锁操作对读锁可见。如果读锁在被获取的情况下,正在运行的读锁无法感知到当前写锁的操作,所以需要等待所有读锁释放以后才能获取写锁。当写锁被获取以后,所有读锁或者写锁都被阻塞。
(如果不断有新的读锁加入,写锁是否会持续等待???)并不会持续等待。当前出去读锁状态,获取写锁等待时,在读锁之后获取的读锁也会等待。反过来持续写锁加入,当获取读锁等待时,后续的写也会等待。
写锁释放与ReentrantLock类似

读锁的获取与释放
读锁也是支持重入的排他锁,能被多个读线程获取。如果不存在其他写线程读锁总能被成功获取,并不会阻塞。如果其他存在写线程,则进入等待状态。
读锁释放与ReentrantLock类似

锁降级


LockSupport工具


Condition接口
每个Java对象都有都有一组监视器方法,定义在Object中。wait()、wait(long)、nitofy()、notifyAll()。与关键字synchronized配合使用可以实现等待/通知模式。
如果使用的Lock对象,就需要Condition接口来配合使用。Condition接口中也提供了类似于Object中的监控方法。

void await()  // 造成当前线程在接到信号或被中断之前一直处于等待状态。
void awaitUninterruptibly()// 造成当前线程在接到信号之前一直处于等待状态。名字中可以看出对中断不敏感
long awaitNanos(long nanosTimeout)// 造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。
boolean awaitUntil(Date deadline)      // 造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态。

void signal()// 唤醒一个等待线程。
void signalAll()// 唤醒所有等待线程。