JUC源码学习

目录

1.Lock

ReentrantLock源码解析

公平锁和非公平锁的区别

什么是AQS

Synchronized和Lock的区别

2.Condition( 线程通信)

3.BlockQueue阻塞队列

4.CountDownLatch

5.Semaphore(信号灯)

6.Atomic原子类


1.Lock

Lock是JUC下控制锁的接口,它要比前面用到的Synchronized更加灵活。

他有一些方法

  • lock()加锁
  • unlock()释放锁,它与lock()配套使用。(和Synchronized不同,Lock只能手动的释放锁)
  • trylock()在不阻塞线程的情况下尝试获取锁,返回一个boolean类型

Lock接口的实现类

  • ReentrantLock(重入锁)互斥锁
  • ReentrantReadWriteLock(重入读写锁)共享锁
  • StampedLock(读写锁)

读写锁的特点

  • 读和读不互斥
  • 读和写互斥
  • 写和写互斥 

可重入锁的特点

比如ThreadA获得了锁,但是他没有释放,它又再次想要获得锁。这就是可重入锁,如果不可重入的话在这种情况就会出现死锁的问题。

ReentrantLock源码解析

不传参的话默认是非公平锁。

用ReentrantLock锁解决原子性的线程安全问题

public class Demo1{
    public static int count = 0;
    public static final Lock lock = new ReentrantLock();
    public static void incr() {
        //获得锁(互斥锁)
        lock.lock();
        try {
            count++;
        }finally {
            //释放锁
            lock.unlock();
        }

    }
    public static void main(String[] args) throws InterruptedException {
        //创建1000个线程执行 incr方法
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                Demo1.incr();
            }).start();
        }
        //睡3秒 确保1000个线程执行结束
        Thread.sleep(3000);
        System.out.println("结果" + count);
    }
}

lock()方法源码分析:

private volatile int state; //互斥资源 0  在AQS抽象类中定义的

lock() -> AQS.lock()

    final void lock() {
             //cas这个方法是线程安全的| 只有一个线程能够进入.
            if (compareAndSetState(0, 1)) //如果从0改成1 说明能抢占到
                //能够进入到这个方法 ,把获得锁的线程放进去
                //保存当前的线程
                setExclusiveOwnerThread(Thread.currentThread()); 
            else
                //没拿到锁的线程 进入这个逻辑
                acquire(1);
        }
    public final void acquire(int arg) {
        //尝试去加锁,看锁是不是释放了,该方法也有2个实现,FairSync与 NonfairSync,
        //我们暂时只看默认的NonfairSync //如果tryAcquire为false,则执行acquireQueued,
        //并且 acquireQueued为true,执行selfInterrupt
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • ! tryAcquire(arg) 尝试获取锁
  • addWaiter 将未获得锁的线程加入到AQS同步队列
  • acquireQueued(); 去抢占锁或者阻塞.

tryAcquire->nonfairTryAcquire 非公平的实现方式

final boolean nonfairTryAcquire(int acquires) {
            //获得当前的线程
            final Thread current = Thread.currentThread();
    //获取AbstractQueuedSynchronizer的值,因为线程1把他设为了 1,所以c为1
            int c = getState();
            //c为0,是线程1释放了锁的场景,但是现在没有,不满足
            if (c == 0) { //无锁
                if (compareAndSetState(0, acquires)) { //cas
                    //设置当前执行代码块的线程为线程2
                    setExclusiveOwnerThread(current);
                    //返回ture,代表线程2拿到了锁,去正常执行业务代码
                    return true;
                }
            }
            //判断?重入
            else if (current == getExclusiveOwnerThread()) {
//将state 改成state +1 这个时候,state不止是一个状态,而 是代表加锁了多少次(重入次数)
                int nextc = c + acquires; //增加state的值
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc); //并不需要通过cas 因为可重入
                return true;
            }
            ///如果抢占不到锁,并且当前线程不是占有锁线程,返回false
            return false;
        }

addWaiter 会把未获得锁的线程顺序的加入双向链表中(AQS的同步队列)

//参数mode 为null(在lock没有用处 condition中有用)
private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        //new node对象,对象为AQS中的一个数据结构,里面存有线程信息,
        //以及前 后节点数据、首尾节点等信息
        // Try the fast path of enq; backup to full enq on failure
        //tail node里面定义尾结点,默认为null
        Node pred = tail;
        //所以,thread2进来的时候,pred为null,不满足下面条件
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //进入enq方法
        enq(node);
        //返回node节点
        return node;
    }

enq方法

//传入的是new的那个node
private Node enq(final Node node) {
        //自旋
        for (;;) {
            //tail默认为null
            Node t = tail;
            //第一次满足条件
            if (t == null) { // Must initialize
                //cas去设置head节点
                if (compareAndSetHead(new Node()))
                    //设置成功,将tail设置为head节点,继续自旋
                    tail = head;
            } else {
                //第二次自旋,当t!=null时进入 
                //node的前节点指向t
                node.prev = t;
                //cas将tail节点设置为node
                if (compareAndSetTail(t, node)) {
                    //设置t的next为node
                    t.next = node;
                    //跳出循环,返回t
                    return t;
                }
            }
        }
    }
acquireQueued 方法
该方法主要是根据 node节点去操作相关获取锁以及LockSupport.park() 操作

//node为当前线程的node节点
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取node节点的前一个节点
                final Node p = node.predecessor();
                //如果p==head 如果是thread2,那么满足,但是thread2 tryAcquire失败,
                //因为thread1占有锁;所以不满足
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                //进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire方法

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        //得到节点的状态,默认为0 第二 次进入为-1
        int ws = pred.waitStatus;
        //waitStatus!=-1不满足 第二次 满足条件,返回true
        if (ws == Node.SIGNAL)
            
            return true;
        if (ws > 0) { //不满足
            //该逻辑是当我前一个节点的线程状态取消时,将前后链表的关系取 消
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else { //进入else
           //修改当前节点的前一个节点的状态为-1
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        //返回false,进入外面的自旋,
        return false;
    }
重新进入自旋,进入 shouldParkAfterFailedAcquire 方法 , 判断 ws=-1 ,满足条件

parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
        //如果没有拿到锁,线程park
        LockSupport.park(this);
        //获取中断状态并且复位
        return Thread.interrupted();
    }

unlock()方法源码分析

拿到锁的线程执行unlock方法释放锁

public void unlock() {
        sync.release(1);
    }

release()方法

public final boolean release(int arg) {
        //调用tryRelease方法 尝试释放锁
        if (tryRelease(arg)) {
            //如果锁释放成功
            Node h = head;
            //得到head节点,如果head节点!=null 并且状态 !=0 满足条件
            if (h != null && h.waitStatus != 0)
                //执行unparkSuccessor
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease()方法 更改state状态

    protected final boolean tryRelease(int releases) {
            //加入thread1只重入了一次。c-1=0
            int c = getState() - releases;
            //判断加锁的线程是不是当前需要释放的线程
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            //如果thread1只加锁一次
            if (c == 0) {
                free = true;
                //将当前拿锁线程设置为空
                setExclusiveOwnerThread(null);
            }
            //将state设置为0
            setState(c);
            return free;
        }
tryRelease 执行完毕后, state=0 exclusiveOwnerThread=null 。这个时候,其他线程可以抢占锁了。

unparkSuccessor方法

//node 为head节点
private void unparkSuccessor(Node node) {
        //得到head的节点状态
        int ws = node.waitStatus;
        if (ws < 0) //现在的状态为-1 满足条件
             //将头节点改成 0
            compareAndSetWaitStatus(node, ws, 0);

        //得到head节点的下一个节点,我们的场景为thread2的node
        Node s = node.next;
        //thread2node的状态为-1,正常状态
        if (s == null || s.waitStatus > 0) {
            s = null;
            //如果需要释放锁的那个线程是取消状态,从后往前找到最前面的那 一个node
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        //满足条件
        if (s != null)
            //唤醒阻塞的线程(头节点的下一个节点)
            LockSupport.unpark(s.thread);
    }
如果获取锁的线程 node 被取消或者异常,那么从后往前找到第一个 node ,为什么要从后往前?
因为有可能出现前指针没有的场景,在 enq 存在并发的时候。

head的下一个节点被唤醒

会从线程被阻塞的地方唤醒 也就是parkAndCheckInterrupt方法

private final boolean parkAndCheckInterrupt() {
        //如果没有拿到锁,线程park
        LockSupport.park(this);

        //被释放锁唤醒,如果外面中断过范围false,否则返回true,并复位
        return Thread.interrupted();
    }

进入acquireQueued方法进行自旋

//假设被唤醒的是thread2
final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取thread2 node的前节点
                final Node p = node.predecessor();
                
                //thread2的前节点是head,并且这个时候thread1已经释放了锁,能拿到锁
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    //这个时候之前的node节点还存在强引用,将next关联去除,
                    //方便回收之前的head节点
                    p.next = null; // help GC
                    failed = false;
                    //返回
                    return interrupted;
                }
                //进入shouldParkAfterFailedAcquire与 parkAndCheckInterrupt
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

图解

公平锁和非公平锁的区别

非公平锁就是允许插队,比如线程1刚执行unlock方法释放了锁,这时AQS同步队列中有线程2正再阻塞,这时他刚要被唤醒,此时不在队列的线程3直接来抢占锁,并且拿到了锁,这就是非公平的体现。

公平锁是不允许插队的。

非公平锁的实现思路就是,先抢占锁,抢不到我再进行排队。

公平锁的实现思路就是,先判断AQS同步队列中是否有线程排队,如果有我就去排在后边,如果没有我在尝试抢占锁。

什么是AQS

AQSjuc包下的一个工具类,全称是AbstractQueuedSynchronizer 抽象队列同步器。我们并发编程中的很多类的功能底层都是基于AQS来实现。

比如ReentrantLock CountDownLatch condition等等。

首先数据结构。既然是实现并发的核心类,那么AQS中维护了一个state字段,代表锁的抢占情况。并提供对statecas操作。以及提供加锁的模板方法,比如tryAcquire,自己可以去重现实现相关逻辑。

同时,抢不到的线程需要排队并且等待,所以AQS中有个线程等待队列。它里面最主要的是有一个双向列表。 节点的数据结构是node, node存有线程的信息,以及node的状态。同时提供对双向列表的相关操作方法。

如果线程抢占不到锁,就会进入AQS中的等待队列,并且park。同时提供了释放锁都相关方法,释放锁会唤醒相关线程。进行锁抢占操作

Synchronized和Lock的区别

1.synchronized底层jvm层面实现 ReentrantLock javajuc

2.synchronized被动释放锁 lock必须手动释放

3.synchronized不能响应中断,lock可以响应中断

4.synchronized可重入,只能非公平 ; lock可重入,可公平与非公平

5.Lock提高读写能力,在1.6之前性能比synchronized高,但是1.6之后,synchronized加入了锁升级也使用了CAS思想,是的他们间的性能差不多了

2.Condition( 线程通信)

  • Condition await()阻塞线程并释放锁(把线程放发哦condition等待队列)
  • Condition signal() signalAll() 唤醒阻塞状态的线程(把condition等待队列的线程唤醒到AQS队列的尾部)

实现生产者消费者案例

Producer

public class Producer implements Runnable{

    private Queue<String> bags;
    private int size;
    private final Lock lock;
    private Condition condition;

    public Producer(Queue bags, int size, Lock lock, Condition condition) {
        this.bags = bags;
        this.size =size;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;

            lock.lock();
            try {
                while (bags.size() == size) {
                    System.out.println("bags以满");
                    try {
                        //阻塞 释放锁
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    //一秒生产一个
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
                bags.add("bags-"+i);
                System.out.println("生产者-生产:bags-" + i);
                //唤醒阻塞状态的消费者
                condition.signal();

            } finally {
                lock.unlock();
            }


        }
    }
}

Consumer

public class Consumer implements Runnable{
    private Queue<String> bags;
    private int size;
    private final Lock lock;
    private Condition condition;

    public Consumer(Queue bags, int size, Lock lock, Condition condition) {
        this.bags = bags;
        this.size =size;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;

            lock.lock();
            try {
                while (bags.isEmpty()) {
                    System.out.println("bags以空");
                    //阻塞 释放锁
                    try {
                        condition.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                try {
                    //一秒消费一个
                    Thread.sleep(1000);
                } catch (InterruptedException interruptedException) {
                    interruptedException.printStackTrace();
                }
                String bag = bags.remove();
                System.out.println("消费者-消费:bags-" + i);
                //唤醒阻塞的生产者
                condition.signal();

            } finally {
                lock.unlock();
            }


        }
    }
}

Test

public class AwaitSignalDemo {
    public static void main(String[] args) {
        Queue<String> bags = new LinkedList<>();
        int size = 5;
        Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Thread p = new Thread(new Producer(bags,size,lock,condition));
        Thread c = new Thread(new Consumer(bags,size,lock,condition));

        p.start();
        c.start();
    }
}

简单图解

3.BlockQueue阻塞队列

插入操作

add(e) :添加元素到队列中,如果队列满了,继续插入元素会报错,IllegalStateException。

offer(e) : 添加元素到队列,同时会返回元素是否插入成功的状态,如果成功则返回true

put(e) :当阻塞队列满了以后,生产者继续通过put添加元素,队列会一直阻塞生产者线程,知道队列可用

offer(e,time,unit) :当阻塞队列满了以后继续添加元素,生产者线程会被阻塞指定时间,如果超时,则线程直接退出

移除操作

remove():当队列为空时,调用remove会返回false,如果元素移除成功,则返回true

poll(): 当队列中存在元素,则从队列中取出一个元素,如果队列为空,则直接返回null

take():基于阻塞的方式获取队列中的元素,如果队列为空,则take方法会一直阻塞,直到队列中有新的数据可以消费

poll(time,unit):带超时机制的获取数据,如果队列为空,则会等待指定的时间再去获取元素返回
 

4.CountDownLatch

计数器

public class Demo3 implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"执行业务");
    }

    public static void main(String[] args) {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        for (int i = 0; i < 5; i++) {
            new Thread(()-> {
                try {
                    //当countDownLatch 为0时会被唤醒
                     //也可以被interrupt唤醒
                    countDownLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"执行业务");
            }).start();
        }
        System.out.println("主线程执行");
        countDownLatch.countDown(); //countDownLatch 会减一

    }
}

 CountDownLatch countDownLatch = new CountDownLatch(1);  //相当于把AQS里的state赋值   在lock中state作为锁的标志 在countDownLactch中state作为计数器

countDownLatch.await();  //如果countDownLacth != 0 就会把线程放到AQS同步队列,并park;  当countDownLacth ==0 会被 唤醒  还可以被interrupt唤醒 

countDownLatch.countDown();  //会让state-1

countDownLatuch在并发查询的时候,我们为了增加查询效率,一般是会加入缓存。并给缓存添加过期时间。假如:并发情况下,出现,缓存穿透,也就是缓存没了,需要查库的时候。怎么解决

可以使用countDownLatuch cdl=new CountDownLatuch(1);只允许一个线程去获取缓存,如果缓存不存在,重新从库里拿出来加入缓存。然后放行所有线程通过执行下层逻辑。

可以用一个监听器,监听器那边,监听器那边down,主业务await啥的。

5.Semaphore(信号灯)

实现个小Demo

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(5);//指定令牌数
        for (int i = 0; i < 10; i++) {
            //启动10个Car线程
            new Car(semaphore, i).start();
        }
    }

    static class Car extends Thread{
        Semaphore semaphore;
        int num;

        public Car(Semaphore semaphore, int num) {
            this.semaphore = semaphore;
            this.num = num;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();//获取令牌(没拿到令牌,会阻塞,拿到了就可以往下执行)
                System.out.println("第"+num+"线程,占用令牌");
                Thread.sleep(2000);
                System.out.println("第"+num+"线程,释放令牌");
                semaphore.release(); //释放令牌
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

6.Atomic原子类

解决原子性问题

public class Demo2 {
    //使用原子类
    public static AtomicInteger count = new AtomicInteger(0);
    public static void incr() {
        //count++
        count.getAndIncrement();
    }
    public static void main(String[] args) throws InterruptedException {
        //创建1000个线程执行 incr方法
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                Demo2.incr();
            }).start();
        }
        //睡3秒 确保1000个线程执行结束
        Thread.sleep(1000);
        System.out.println("结果" + count.get());
    }
}

多次执行,线程安全;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

w7486

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值