Java并发包中锁原理剖析

Posted 梦想成为DALAO

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发包中锁原理剖析相关的知识,希望对你有一定的参考价值。

6.1LockSupport

跟wait()不同!

引入了一个许可证的概念。

void park()方法

检测此时的线程是否拥有许可证,有的话。就通过,没有的话就阻塞。

LockSupport.park():在哪儿调用就是检查哪个线程

void unpark(Thread thread)方法

LockSupport.unpark(t):给t线程发一个许可证。

LockSupport.parkNanos(long nanos)

如果有许可证,直接返回

如果没有许可证,那就等一会再返回

LockSupport.park(Object blocker)方法

讲blocker参数传入到阻塞线程中去

LockSupport.parkNanos(Object blocker, long nanos)

相对时间

LockSupport.parkUnitl(Object blocker, long deadline)

准确时间

大名鼎鼎的AQS

AbstractQueuedSynchronized:简称AQS,抽象同步队列;实现同步锁的基础组件。

并发包中的锁底层就是使用AQS实现的。

 

 AQS是一个FIFO的双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型是Node。

Node节点用来保存一条线程:

  1. Shared:标记该线程是获取共享资源时被阻塞挂起后放入AQS队列的
  2. EXCLUSIVE:标记线程是获取独占资源时被挂起后放入AQS队列的
  3. CANCLELED:线程被取消
  4. SIGNAL:线程需要被唤醒
  5. CONDITION:线程条件队列里面等待
  6. PROPAGATE:释放共享资源时需要通知其它节点
  7. pre:前驱节点
  8. next:后继节点

最终要的AQS中的state是实现不同锁的基础!

AQS中有内部类ConditionObject类:每个对象对应一个条件队列(单向链表队列):其中放调用条件变量await方法后被阻塞的线程。

对于ReentrantLock来说:state指的是可重入锁的次数

对于ReetrantReadWriteLock来说:高十六位是:读的重入数;低十六位:写锁的重入数;

对于CountDownlatch来说:state用来表示计数器当前的值

根据state是否属于一个线程:操作state的方式分为独占式和共享式。

独占式

谁获得了锁,state从0---->1;假如重入获取,state+1;解锁:state-1;

没有获取到锁:放入AQS阻塞队列中

释放资源

boolean release(int args)

获取资源

void acquire(int arg):不响应中断

 获取锁,没有就讲线程保存到AQS的阻塞队列里头。

void acquireInterruptibly(int arg):响应中断

共享式

例如信号量Semaphore:多个线程去请求资源时通过CAS方式竞争资源,当一个线程获取到了资源后,另外一个线程再次去获取时如果当前资源还能满足它的需要,则当前线程只需要使用CAS方式获取即可。

释放资源

boolean releaseShared(int args)

获取资源

void acquireShared(int arg):不响应中断

void acquireInterruptiblyShared(int arg):响应中断

----------------------------------------------------------------------------------------------------------------------------------------------------------------

获取锁:都是CAS
成功:执行

失败:LockSupport.park()假如到AQS队列里头等待

释放:释放成功,LockSupport.unpar()唤醒一个,CAS获取锁咯。

上述的tryxx的方法AQS需要实现的子类自己写

 

例如:ReentrantReadWriteLock

读锁重写tryAcquireShared时,首先查看写锁是否被其它线程持有,如果有则直接返回false,否则使用CAS递增state的高16位;

释放读锁时:重写tryRealeaseShared时,在内部需要使用CAS算法把当前state的高16位减1.

 

isHeldExclusively():判断锁是被当前线程独占还是被共享。

-------------------------------------------------------------------------------------------------------------------------------------------------------------------

node节点入队操作

AQS---条件变量的支持

就是之前的哪个ConditionObject类

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionTest {

    public static void main(String[] args) {

        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("I am one");
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    System.out.println("I am one signal");
                    lock.unlock();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                System.out.println("I am two");
                condition.signal();
                System.out.println("I am two");
                lock.unlock();
            }
        }).start();
    }

    /**
     * I am one
     * I am two
     * I am two
     * I am one signal
     */
}

其实没什么难得,就是不在AQS的队列中等,在ConditionObject的队列中等。await和singal-------------》wait()和notify()差不多

当一个线程调用条件变量的signal方法时,会将条件队列的一个线程节点从条件队列中移到AQS的队列中,然后激活这个线程。

signalAll是将所有的线程放到AQS中

什么是虚假唤醒

例如:生产者和消费者

有一个生产者A和两个消费者B.C

  1. B来消费,发现没有有东西,await了,释放锁,进入wait队列
  2. A生产咯:生产过程中;C来了,因为锁被A占有了,所以直接进入了AQS队列
  3. A生产完了,唤醒B;但是此时C得到了锁,消费了一个商品。B被锁在了外头。
  4. 等C消费完了,释放锁,B才获得锁,这个时候啥都没了。
  5. 这个时候你的程序可能会报错。

所以,在判断的时候用while,每次进来都判断一下。这样才能避免虚假唤醒。

手动实现基于AQS的锁

重写tryAcquire(int acquires)、tryRelease(int releases)、newCondition()、isHeldExclusively()

独占锁ReentrantLock原理

获取锁lock()

假如是非公平锁:

公平锁

 

就是判断head指针的下一个节点是不是S,是的就冲了,不是就等等!,反正我是这么理解的

tryLock()方法

释放锁 void unlock()方法

 

 

ReentrantReadWriteLock原理

 

 

读写锁:维护了一个ReadLock和WriteLock

基于AQS实现

state高16位表示读状态

低16位标识写状态

WriteLock

如果当前没有线程获取到读锁和写锁,则当前线程可以获取到写锁然后返回。

如果当前线程有线程获取的读锁和写锁,写锁会被阻塞起来

重入+1

  1. void lock()
  2. void lockInterruptibly()
  3. boolean tryLock()
  4. boolean tryLock(long timeout, TimeUnit unit)
  5. void unlock()

ReadLock

如果当前没有线程获得写锁,那就可以获取读锁

  1. void lock()
  2. void lockInterruptibly()
  3. boolean tryLock()
  4. boolean tryLock(long timeout, TimeUnit unit)
  5. void unlock()

StampedLock

提供三种锁:

写锁WriteLock

悲观读锁readLock

可以转换位写锁

乐观读锁tryOptimisticRead

不用加锁,只需要在使用的时候,判断一下有没有写锁!

 

以上是关于Java并发包中锁原理剖析的主要内容,如果未能解决你的问题,请参考以下文章

Java 并发编程 进阶 -- Java并发包中锁原理剖析(LockSupport抽象同步队列AQS独占锁ReentrantLock读写锁ReentrantReadWriteLock)

Java并发编程之美读书笔记5-Java并发包中ThreadLocalRandom类原理剖析

Java并发编程之美读书笔记5-Java并发包中ThreadLocalRandom类原理剖析

Java 并发编程 进阶 -- ThreadLocalRandom类原理剖析原子操作类原理剖析(AtomicLong)并发List原理剖析(CopyOnWriteArrayList)

Java 并发编程 进阶 -- ThreadLocalRandom类原理剖析原子操作类原理剖析(AtomicLong)并发List原理剖析(CopyOnWriteArrayList)

Java Review - 并发编程_原子操作类原理剖析