【二】关于java.util.concurrent包下的并发类(lock)

java.util.concurrent.lock包下的类主要实现的功能与Synchronized类似。但也存在很多不同。
在这个包中,提供了一系列关于所的抽象的类。其中主要的类为ReentrantLock和ReentrantReadWriteLock。其他类基本为辅助类。例如AbstractQueueSynchronizer,用于实现特殊规则的类,例如公平锁和不公平锁。ReentrantReadWriteLock和ReentrantLock其内部都封装了这个抽象类的实现类。

ReentrantLock:
使用Lock lock = new ReentrantLock();//默认创建一个非公平的锁
    try{
        lock.lock();
     }catch(Interrupted e){
          //.......
     }finally{
          lock.unlock();
     }
然而,ReentrantLock相对于Synchronized而言多了一些新的功能。
tryLock():调用了这个方法,若线程获得了锁资源,则返回true。如若当前线程已经获得了锁资源,则锁状态+1,返回true。如若锁资源被其他线程占据,则返回false。该线程会去进行其他动作。即可以利用这个方法尝试让该线程去获取锁资源,如若锁资源被其他线程占据,则会继续进行其他动作。
tryLock调用NotFairSync的nonfiarTryAcquire方法。该方法会先去获得锁状态c。如若c=0,则通过CAS算法比较当前c是否于内存中的state一致,如若一致,则当前线程获取所资源并返回true。如若锁状态!=0,继续判断当前是否已获得锁资源,如若是,则锁状态+1,返回true。如若均不符合,则返回false。

tryLock(long waitTime,TimeUnit timeUnit):tryLock还可以指定等待时间waitTime,意思是如若未获取到锁资源,则会继续等待指定时间,在指定时间之内如若获取到了锁资源,则返回true。如若在指定之后还没有获取到锁资源,则返回false。

lockInterruptibly():贴上源码
lockInterruptibly会调用acquireInterruptibly。这个方法会去判断当前线程是否中断,如若没有则会判断当前线程是否占据了锁资源,如若没有则会调用doAcquireInterruptibly方法。
这个方法会先调用addWaiter方法:传入当前线程创建一个新的节点Node,获取到尾节点tail,如若tail不为空,则node.prev=tail,再tail.next = node
如若tail是尾节点,则会调用一个enq方法,利用无限for循环,当tail为空时,将内存中的头结点更新为一个新的没有线程参数的node。再head = tail。再次进入循环之后,此时tail不为空,node.prev = tail,tail.next = node。再返回创建的新的节点Node。


addWaiter方法结束,再次进入一个无限for循环,先获取到Node的前一个节点p。如若p是头结点,并且获取到了锁资源,则设当前Node为头结点,随后p,next=null以便于被GC回收。随后如若线程被中断,则抛出中断异常,再在最后取消尝试获取锁的操作。

在ReentrantLock中,很多方法都用到了无限for循环。这个做法的用途有很多,其中一个便是在doAquireInterruptibly方法中利用for循环监控线程是否已中断。

ReentrantReadWriteLock
这个类的用法基本和ReentrantLock一致,只不过它还实现了读写锁。它新增了两个内部类ReadLock和WriteLock。这两个类可以用ReentrantReadWriteLock的readLock和writeLock方法返回。规则是:读取操作可由多个线程同时保持,而写入操作只能被单个线程独占。
LZ会将这个类和StampedLock一同阐述,因为StampedLock优化了ReentrantReadWriteLock这个类的读写操作。在ReentrantReadWriteLock中,只有当没有任何读写锁时,才可以获得写入锁。这是实现了一个悲观读取。所以说在读取很多,写入很少的情况下,是很效率的。但是可能会使得写入线程处于一种“饥饿状态”,使得其一直处于等待状态。而StampedLock则提供了三种模式的锁,用于控制读取/写入访问。其中一种便是乐观读取,在源码示例中,每次进行写操作和释放写锁时,stamp都会+1。乐观读取即在读时会先去得到当前的stamp。如若此时又发生了写操作,则stamp会改变,通过stampedLock的validate方式,随后加上读锁。

LockSupport
LockSupport的用法和object中的wait/notify方法类似。在源码的FIFOMutex示例中,

会将当前线程存入一个ConcurrentLinkedQueue,随后循环判断如若Queue的第一个线程不为当前线程,LockSupport.park(this),如若线程被中断,wasIterrupted设为true。如若为当前线程,则Queue中移除该线程,如若wasInterrupted为true,中断当前线程。在循环中线程处于阻塞状态,当前如若被中断,则会先忽略中断状态。等到当前线程退出while循环之后,即为队列的第一个时,再在此时中断。

LockSupport和wait、notify方法的区别
wait和notify只可在同步代码块中执行,而lockSupport则没有限制。即线程之间无需维护一个共享的同步对象,即不需依赖监视器,实现了线程间的解耦。并且unpark可以限于park执行,无需担心先调用notify方法导致线程阻塞。

ReentrantLock和Synchronized的区别
实际上两种区别不大,都用了CAS。不过一个是JVM层次的,一个是用户代码自定义的。如果你忘记unlock,则会造成死锁。
那么何时使用ReentrantLock呢?实际上在Synchronized优化之后,两者区别并不是很大了。当然你如果想使用ReentrantLock的三个独有功能的话,你可以使用它。

还有一个Condition没有介绍。在我理解就是分组唤醒线程,LZ还没去看。并且其实对lock包和atomic包LZ了解的都不是很深,均较为片面。以后有时间会加以补充。

猜你喜欢

转载自blog.csdn.net/qq_32302897/article/details/81022494