JUC中的锁、信号量、并发集合

一、JUC中锁的机制

(1)AQS:AQS全名AbstractQueuedSynchronizer,是并发容器JUC(java.util.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。

工作原理:

AQS使用一个int state成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。

AQS使用CAS对该同步状态进行原子操作实现对其值的修改,当state大于0的时候表示锁被占用,如果state等于0时表示没有占用锁。

CAS:https://blog.csdn.net/weixin_53455615/article/details/126535949?spm=1001.2014.3001.5501

理解:如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用, 获取不到锁的线程加入到队列中。然后排队获取资源。

二、JUC中的锁

JUC中锁的底层使用的就是AQS

  1. ReentrantLock: Lock接口的实现类, 可重入锁。相当于synchronized同步锁

  2. ReentrantReadWriteLock:ReadWriteLock接口的实现类。类中包含两个静态内部类,ReadLock读锁、WriteLock写锁。

  3. Condition:是一个接口,都是通过lock.newCondition()实例化。属于wait和notify的替代品。提供了await()、signal()、singnalAll()与之对应

  4. LockSupport:和Thread中suspend()和resume()相似

 三、ReentrantLock重入锁

可重入锁:https://blog.csdn.net/weixin_53455615/article/details/126483112?spm=1001.2014.3001.5501

提供了2种类型的构造方法。

1. ReentrantLock(): 创建非公平锁的重入锁。

2. ReentrantLock(boolean): 创建创建锁。取值为true表示公平锁,取值为false表示非公平锁。

公平锁:多线程操作共一个资源时, 严格按照顺序执行。

非公平锁:新添加的线程先尝试获取获取资源,若获取到直接执行此线程,若没有获取到就添加到队列中去排队。

注意:

1. ReentrantLock出现异常时, 不会自动解锁

2. 多线程的情况下, 一个线程出现异常, 并没有释放锁, 其他线程也获取不到锁, 容易出现死锁

3. 建议把解锁方法finally{}代码块中

4. synchronized加锁与释放锁不需要手动的设置, 遇到异常时, 会自动的解锁

5.避免死锁,需要将解锁放到finally{}中

package com.java.test;

import java.util.concurrent.locks.ReentrantLock;

public class Test02 {
    static int a = 0;

    public static void main(String[] args) throws InterruptedException {
        ReentrantLock rt = new ReentrantLock();

        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i1 = 0; i1 < 1000; i1++) {
                        rt.lock();//加锁
                        a++;
                        rt.unlock();//解锁
                    }
                }
            }).start();
        }

        Thread.sleep(3000);

        System.out.println(a);//5000
    }
}

四、Condition等待 | 唤醒(线程通信)

condition.await(); //线程等待

condition.signal(); //唤醒一个线程
condition.signalAll(); //唤醒所有线程

注意:等待 | 唤醒,会释放锁

package com.java.test;

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

/*
* 两个线程配合输出《静夜思》
* 两个线程一个线程一句话
* */
public class Test03 {
    public static void main(String[] args) {
        //创建Condition实现类
        ReentrantLock rt = new ReentrantLock();
        Condition condition = rt.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //加锁
                    rt.lock();
                    System.out.println("床前明月光");
                    //线程等待
                    condition.await();
                    System.out.println("举头望明月");
                    //唤醒线程
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //解锁
                    rt.unlock();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    rt.lock();
                    System.out.println("疑是地上霜");
                    condition.signal();
                    condition.await();
                    System.out.println("低头思故乡");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    rt.unlock();
                }

            }
        }).start();
    }
}

五、ReentrantReadWriteLock读写锁

ReadLock 读锁,又称为共享锁。允许多个线程同时获取该读锁

WriteLock 写锁,又称为独占锁。只有一个线程能获取,其他写的线程等待, 避免死锁

package com.java.test;

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class Test04 {
    public static void main(String[] args) {
        //创建读写锁
        ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
        //获取读锁 多个线程可以同时持有 , 共享锁
        ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
        //获取写锁 只能一个线程持有 , 独占性
        ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();

        new Thread(new Runnable() {
            @Override
            public void run() {
                //readLock.lock();//加读锁
                writeLock.lock();//加写锁
                System.out.println(Thread.currentThread().getName());
            }
        }).start();

    }
}

六、LockSupport 暂停 | 恢复

LockSupport是Lock中实现线程暂停和线程恢复。suspend()和resume()是synchronized中的暂停和恢复。

注意: 暂停恢复不会释放锁, 避免死锁问题。

package com.java.test;

import java.util.concurrent.locks.LockSupport;

public class Test05 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("5秒后继续执行");
                //暂停线程
                LockSupport.park();
                System.out.println("执行结束");
            }
        });

        thread.start();

        Thread.sleep(5000);
        LockSupport.unpark(thread);
    }
}

七、CountDownLatch计数器

在开发中经常遇到在主线程中开启多个线程去并行执行任务,并且主线程需要等待所有子线程执行完毕后再进行汇总的场景。之前是使用join() | 主线程休眠实现的,但是不够灵活,某些场合和还无法实现,所以开发了CountDownLatch这个类。底层基于AQS。

CountDown是计数递减的意思,Latch是门闩的意思。内部维持一个递减的计数器。可以理解为初始有n个Latch,等Latch数量递减到0的时候,结束阻塞, 执行后续操作。

package com.java.test;

import java.util.concurrent.CountDownLatch;

public class Test06 {

    static int a = 0;

    public static void main(String[] args) throws InterruptedException {
        //创建计数器
        CountDownLatch count = new CountDownLatch(5);
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test();
                    count.countDown();//计数器 -1
                }
            }).start();
        }
        count.await();//当计数器为0时继续执行
        System.out.println(a);
    }

    private synchronized static void test() {
        for (int i = 0; i < 1000; i++) {
            a++;
        }
    }
}

八、CyclicBarrier回环屏障

CountDownLatch优化了join()在解决多个线程同步时的能力,但CountDownLatch的计数器是一次性的。计数递减为0之后,再调用countDown()、await()将不起作用。为了满足计数器可以重置的目的,JDK推出了CyclicBarrier类。

使用原理:await()方法表示当前线程执行时计数器值不为0则等待。如果计数器为0则继续执行。每次await()之后计算器会减少一次。当减少到0下次await从初始值重新递减。九、

九、Semaphore 信号量

CountDownLatch和CyclicBarrier的计数器递减的,而Semaphore的计数器是可加可减的,并可指定计数器的初始值,并且不需要事先确定同步线程的个数,等到需要同步的地方指定个数即可。且Semaphore也具有回环重置的功能,这一点和CyclicBarrier很像。底层也是基于AQS。

package com.java.test;


import java.util.concurrent.Semaphore;

public class Test07 {
    public static void main(String[] args) throws InterruptedException {
        //创建信号量
        Semaphore semaphore = new Semaphore(3);
        //信号量+1
        semaphore.release();
        //信号量+n
        semaphore.release(5);
        //信号量-1
        semaphore.acquire();

        //信号量-n 信号量的值小于0 , 线程阻塞执行
        semaphore.acquire(10);
        //获取信号量中的值
        int i = semaphore.availablePermits();
        System.out.println(i);
    }
}

十、并发集合

并发集合类:主要是提供线程安全的集合

比如:

1. ArrayList对应的并发类是CopyOnWriteArrayList

2. HashSet对应的并发类是 CopyOnWriteArraySet

3. HashMap对应的并发类是ConcurrentHashMap

十一、CopyOnWriteArrayList

使用方式和ArrayList相同, 当然CopyOnWriteArrayList线程为安全的。

Vector:也是ArrayList的子类,它也是线程安全的。

原理:写时复制

通俗的理解是当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器,然后往新的容器里添加元素。

添加完元素之后,再将原容器的引用指向新的容器。这样做的好处是我们可以对CopyOnWrite容器进行并发的读,而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器。

十二、CopyOnWriteArraySet

CopyOnWriteArraySet在CopyOnWriteArrayList 的基础上使用了Java的装饰模式,所以底层是相同的。而CopyOnWriteArrayList本质是个动态数组队列,所以CopyOnWriteArraySet相当于通过动态数组实现的Set, CopyOnWriteArrayList中允许有重复的元素;但CopyOnWriteArraySet是一个Set集合,所以它不能有重复数据。因此, CopyOnWriteArrayList额外提供了addIfAbsent()和addAllAbsent()这两个添加元素的API,通过这些API来添加元素时,只有当元素不存在时才执行添加操作!

十三、ConcurrentHashMap

(1)Segment段锁:Segment继承了ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在ConcurrentHashMap,一个Segment就是一个子哈希表,Segment里维护了一个HashEntry数组,并发环境下,使用多个锁来控制对hash表的不同部分(段segment)进行的修改,如果多个修改操作发生在不同的段上,他们就可以并发进行,从而提高了效率。

(2)ConcurrentHashMap在JDK8中进行了巨大改动。它摒弃了Segment(锁段)的概念,而是启用了一种全新的方式实现, 利用synchronized + CAS, 如果没有出现hash冲突, 使用CAS直接添加数据, 只有出现hash冲突的时候才会使用同步锁添加数据, 又提升了效率, 它底层由"数组"+链表+红黑树的方式思想(JDK8中HashMap的实现), 为了做到并发,又增加了很多辅助的类,例如TreeBin,Traverser等对象内部类。

(3)辅助类

Node节点: 默认数组上的结点就是Node结点。Node只有一个next指针,是一个单链表,提供find方法实现链表查询, 当出现hash冲突时,Node结点会首先以链表的形式链接到table上,当结点数量大于等于8并且数组长度大于64,链表会转化为红黑树。

TreeNode节点: TreeNode就是红黑树的结点,TreeNode不会直接链接到table[i]——桶上面,而是由TreeBin链接,TreeBin会指向红黑树的根结点。

TreeBin节点: TreeBin会直接链接到table[i]——桶上面,该结点提供了一系列红黑树相关的操作,以及加锁、解锁操作。

ForwardingNode: ForwardingNode 在table扩容时使用,内部记录了扩容后的table,即nexttable。

ReservationNode: 在并发场景下、在从 Key不存在 到 插入 的 时间间隔内,为了防止哈希槽被其他线程抢占,当前线程会使用一个reservationNode节点放到槽中并加锁,从而保证线程安全。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值