Java并发工具J.U.C1

Lock (Synchronized)

在Lock接口出现之前,Java中的应用程序对于多线程的并发安全处理只能基于synchronized关键字来解决。但是synchronized在有些场景中会存在一些短板,也就是它并不适合于所有的并发场景。但是在 Java5以后,Lock的出现可以解决synchronized在某些场景中的短板,它比synchronized更加灵活。

public class AtomicDemo {
    private static int count=0;
    static Lock lock=new ReentrantLock();
    public static void inc(){
        lock.lock(); //获得锁(互斥锁)
        try {
            Thread.sleep(1);
            count++;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁 
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->AtomicDemo.inc()).start();
        }
        Thread.sleep(4000);
        System.out.println("result:"+count);
    }
}

输出结果:

result:1000

ReentrantLock(重入锁)

ReentrantLock是一种互斥锁。
重入锁:已经获得锁的线程,在不释放锁的前提下可以再次获得锁。
例如:

public class AtomicDemo {

    private static int count=0;

    //重入锁(如何实现的?)
    static Lock lock=new ReentrantLock(true);

    public static void inc(){
        lock.lock(); //获得锁(互斥锁) ThreadA 获得了锁
        try {
            Thread.sleep(1);
            count++;
            decr();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();//释放锁 ThreadA释放锁  state=1-1=0
        }
    }
    public static void decr(){
        lock.lock(); //state=2   //ThreadA再次来抢占锁 : 不需要再次抢占锁,而是只增加重入的次数
        try{
            count--;
        }finally {
            lock.unlock(); //state=1
        }
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->AtomicDemo.inc()).start();
        }
        Thread.sleep(4000);
        System.out.println("result:"+count);
    }
}

假如锁不能重入,ThreadA在调用inc()方法时,将会出现死锁。因为A第一次已经获得了锁,在调用decr()时候如果获取不到锁就会发生阻塞,从而死锁。
在这里插入图片描述
ReentrantLock 默认是非公平锁

在这里插入图片描述
lock()方法目的是获得锁,做的第一件事就是抢占资源

private volatile int state; //互斥资源 0
 final void lock() { //抢占互斥资源()
	if(cas()){ //线程并行。 //有多个线程进入到这段代码?多个线程抢占到同一把锁. 				 ...
   }
//不管当前队列是否有人排队的? 临界点的情况.
if (compareAndSetState(0, 1)) //乐观锁( true / false) |只有一个线程能够进入. 
//能够进入到这个方法 , 表示无锁状态 
	setExclusiveOwnerThread(Thread.currentThread());//保存当前的线程
else
	acquire(1);
}

抢占资源(抢占锁)失败的线程进入acquire(1);方法中

 public final void acquire(int arg) {
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}

! tryAcquire(arg) 继续尝试去抢占看是否成功
addWaiter 将未获得锁的线程加入到队列
acquireQueued(); 去抢占锁或者阻塞.

 final boolean nonfairTryAcquire(int acquires) { 
 //获得当前的线程
final Thread current = Thread.currentThread(); 
int c = getState();
if (c == 0) { //无锁
        if (compareAndSetState(0, acquires)) { //cas
            setExclusiveOwnerThread(current);
            return true;
} }
//判断?重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires; //增加state的值 
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded"); 
setState(nextc); //并不需要通过cas
return true;
}
    return false;
}

可以看出ReentrantLock是通过CAS乐观锁的方式来实现的互斥。
通过unsafe类中LockSupport.park() 来阻塞未获得锁的线程,LockSupport.unpark(thread) 来唤醒线程。
在这里插入图片描述

ReentrantReadWriteLock(重入读写锁)

适用于读多写少的情况下.
1、读和读不互斥
2、读和写互斥
3、写和写互斥
ReentrantReadWriteLock里面维护了2把锁,一把读锁,一把写锁。

public class Demo {
    static Map<String,Object> cacheMap=new HashMap<>();
    static ReentrantReadWriteLock rwl=new ReentrantReadWriteLock();
    static Lock read=rwl.readLock();
    static Lock write=rwl.writeLock();
    public static Object get(String key){
        read.lock(); //读锁 ThreadA 阻塞
        try{
            return cacheMap.get(key);
        }finally {
            read.unlock(); //释放读锁
        }
    }
    public static Object write(String key,Object value){
        write.lock(); //Other Thread 获得了写锁
        try{
            return cacheMap.put(key,value);
        }finally {
            write.unlock();
        }
    }
}

StampedLock

读多写少的情况下. 读和读不互斥 读和写互斥 写和写互斥

思考锁的实现(设计思维)

{互斥}
1、锁的互斥特性 -> 共享资源()-> 标记 (0 无锁, 1代表有锁)
2、没有抢占到锁的线程?-> 释放CPU资源 , [等待 -> 唤醒]
3、等待的线程怎么存储? -> 数据结构去存储一些列等待中的线程,FIFO (等待队列) 4、公平和非公平(能否插队),synchronized是一个非公平锁
5、重入的特性(识别是否是同一个人?ThreadID)
{技术方案}
1、volatile state =0 (无锁) , 1代表是持有锁 , >1代表重入
2、wait/notify | condition 无法唤醒指定线程。
而需要唤醒指定线程。[LockSupport.park(); ->unpark(thread)] unsafe类中提供的一个方法。
3、双向链表
4、逻辑层面去实现
5、在某一个地方存储当前获得锁的线程的ID,判断下次抢占锁的线程是否为同一个。

Condition

学习 synchronized 的时候,有讲到 wait/notify 的 基本使用,结合 synchronized 可以实现对线程的通信。那 么这个时候我就在思考了,既然 J.U.C 里面提供了锁的实现 机制,那 J.U.C 里面有没有提供类似的线程通信的工具呢? 于是找阿,发现了一个 Condition 工具类。
Condition 是一个多线程协调通信的工具类,可以让某些线 程一起等待某个条件(condition),只有满足条件时,线程 才会被唤醒

public class Producer implements Runnable{

    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock();
                while(msg.size()==maxSize){
                    System.out.println("生产者队列满了,先等待");
                    try {
                        condition.await(); //阻塞线程并释放锁
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产消息:"+i);
                msg.add("生产者的消息内容"+i);
                condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

public class Consumer implements Runnable{
    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i=0;
        while(true){
            i++;
            lock.lock(); //synchronized
            while(msg.isEmpty()){
                System.out.println("消费者队列空了,先等待");
                try {
                    condition.await(); //阻塞线程并释放锁   wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费消息:"+msg.remove());
            condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

public class App 
{
    public static void main( String[] args )
    {
        Queue<String> queue=new LinkedList<>();
        Lock lock=new ReentrantLock(); //重入锁
        Condition condition=lock.newCondition();
        int maxSize=5;

        Producer producer=new Producer(queue,maxSize,lock,condition);
        Consumer consumer=new Consumer(queue,maxSize,lock,condition);

        Thread t1=new Thread(producer);
        Thread t2=new Thread(consumer);
        t1.start();
        t2.start();

    }
}

通过这个案例简单实现了 wait 和 notify 的功能,当调用 await 方法后,当前线程会释放锁并等待,而其他线程调用 condition 对象的 signal 或者 signalall 方法通知并被阻塞 的线程,然后自己执行 unlock 释放锁,被唤醒的线程获得 之前的锁继续执行,最后释放锁。
所以,condition 中两个最重要的方法,一个是 await,一 个是 signal 方法
await:把当前线程阻塞挂起
signal:唤醒阻塞的线程

Condition 源码分析

调用 Condition,需要获得 Lock 锁,所以意味着会存在一 个 AQS 同步队列,在上面那个案例中,假如两个线程同时 运行的话,那么 AQS 的队列可能是下面这种情况

在这里插入图片描述

那么这个时候 ThreadA 调用了 condition.await 方法,它做 了什么事情呢?

condition.await

调用 Condition 的 await()方法(或者以 await 开头的方法), 会使当前线程进入等待队列并释放锁,同时线程状态变为 等待状态。当从 await()方法返回时,当前线程一定获取了 Condition 相关联的锁

public final void await() throws InterruptedException {
if (Thread.interrupted()) //表示await允许被 中断
throw new InterruptedException();
Node node = addConditionWaiter(); //创建一个 新的节点,节点状态为 condition,采用的数据结构仍然是链表
int savedState = fullyRelease(node); //释放当前的锁,得到锁的状态,并唤醒 AQS 队列中的一个线程 int interruptMode = 0;
//如果当前节点没有在同步队列上,即还没有被 signal, 则将当前线程阻塞
while (!isOnSyncQueue(node)) {//判断这个节点 是否在 AQS 队列上,第一次判断的是 false,因为前面已经释 放锁了
LockSupport.park(this); //通过 park 挂起当 前线程
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; }
// 当这个线程醒来,会尝试拿锁, 当 acquireQueued 返回 false 就是拿到锁了.
// interruptMode != THROW_IE -> 表示这个线程 没有成功将 node 入队,但 signal 执行了 enq 方法让其入 队了.
// 将这个变量设置成 REINTERRUPT.
if (acquireQueued(node, savedState) && interruptMode != THROW_IE) interruptMode = REINTERRUPT;
// 如果 node 的下一个等待者不是 null, 则进行清理, 清理 Condition 队列上的节点.
// 如果是 null ,就没有什么好清理的了.
if (node.nextWaiter != null) // clean up if cancelled
       unlinkCancelledWaiters();
// 如果线程被中断了,需要抛出异常.或者什么都不做 if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

addConditionWaiter

这个方法的主要作用是把当前线程封装成 Node,添加到 等待队列。这里的队列不再是双向链表,而是单向链表

 private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果 lastWaiter 不等于空并且 waitStatus 不等于 CONDITION 时,把冲好这 个节点从链表中移除
if (t != null && t.waitStatus != Node.CONDITION) {
       unlinkCancelledWaiters();
t = lastWaiter; }
//构建一个 Node,waitStatus=CONDITION。这里的链表是一个单向的,所以相比 AQS 来说会简单很多
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null) firstWaiter = node;
else
t.nextWaiter = node; lastWaiter = node; return node;
}

图解分析

执行完 addConditionWaiter 这个方法之后,就会产生一个 这样的 condition 队列

在这里插入图片描述

fullyRelease

fullRelease,就是彻底的释放锁,什么叫彻底呢,就是如果 当前锁存在多次重入,那么在这个方法中只需要释放一次 就会把所有的重入次数归零。

final int fullyRelease(Node node) {
boolean failed = true; try {
int savedState = getState(); //获得重入的次数
if (release(savedState)) {//释放锁并且唤醒下一个同步队列中的线程
failed = false;
return savedState; } else {
throw new
IllegalMonitorStateException();
       }
 } finally {
if (failed)
node.waitStatus = Node.CANCELLED;
} }      

图解分析

此时,同步队列会触发锁的释放和重新竞争。ThreadB 获 得了锁。

在这里插入图片描述

isOnSyncQueue

判断当前节点是否在同步队列中,返回 false 表示不在,返 回 true 表示在
1、如果不在 AQS 同步队列,说明当前节点没有唤醒去争抢同 步锁,所以需要把当前线程阻塞起来,直到其他的线程调 用 signal 唤醒
2、如果在 AQS 同步队列,意味着它需要去竞争同步锁去获得 执行程序执行权限。

为什么要做这个判断呢?原因是在 condition 队列中的节 点会重新加入到 AQS 队列去竞争锁。也就是当调用 signal 的时候,会把当前节点从 condition 队列转移到 AQS 队列。
➢大家思考一下,基于现在的逻辑结构。如何去判断
ThreadA 这个节点是否存在于 AQS 队列中呢?

  1. 如果 ThreadA 的 waitStatus 的状态为 CONDITION,说 明它存在于 condition 队列中,不在 AQS 队列。因为AQS 队列的状态一定不可能有 CONDITION
  2. 如果 node.prev 为空,说明也不存在于 AQS 队列,原因 是 prev=null 在 AQS 队列中只有一种可能性,就是它是
    head 节点,head 节点意味着它是获得锁的节点。
  3. 如果 node.next 不等于空,说明一定存在于 AQS 队列中,因为只有 AQS 队列才会存在 next 和 prev 的关系
  4. findNodeFromTail,表示从 tail 节点往前扫描 AQS 队列,一旦发现 AQS 队列的节点和当前节点相等,说明节点一定存在于 AQS 队列中
final boolean isOnSyncQueue(Node node) {
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
return true;
return findNodeFromTail(node);
}

Condition.signal

await 方法会阻塞 ThreadA,然后 ThreadB 抢占到了锁获 得了执行权限,这个时候在 ThreadB 中调用了 Condition 的 signal()方法,将会唤醒在等待队列中节点

public final void signal() {
if (!isHeldExclusively()) //先判断当前线程是否获得了锁,这个判断比较简单,直接用获得锁的线程和当前线程相比即可
throw new IllegalMonitorStateException();
Node first = firstWaiter; // 拿到 Condition队列上第一个节点
if (first != null) doSignal(first);
}

Condition.doSignal

对 condition 队列中从首部开始的第一个 condition 状态的 节点,执行 transferForSignal 操作,将 node 从 condition 队列中转换到 AQS 队列中,同时修改 AQS 队列中原先尾 节点的状态。

private void doSignal(Node first) { do {
//从 Condition 队列中删除 first 节点
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null; // 将 next 节点设置成 null
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}

AQS.transferForSignal

该方法先是 CAS 修改了节点状态,如果成功,就将这个节 点放到 AQS 队列中,然后唤醒这个节点上的线程。此时, 那个节点就会在 await 方法中苏醒

final boolean transferForSignal(Node node) {
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))//更新节点的状态为0,如果更新失 败,只有一种可能就是节点被 CANCELLED 了
return false;
Node p = enq(node);//调用enq,把当前节点添加到AQS 队列。并且返回返回按当前节点的上一个节点,也就是原 tail 节点
int ws = p.waitStatus;
// 如果上一个节点的状态被取消了, 或者尝试设置上一 个节点的状态为 SIGNAL 失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞),
if (ws > 0|| !compareAndSetWaitStatus(p, ws,
Node.SIGNAL)) LockSupport.unpark(node.thread); // 唤醒节点上的线程.
return true; //如果 node 的 prev 节点已经是signal 状态,那么被阻塞的 ThreadA 的唤醒工作由 AQS 队 列来完成
}

图解分析

执行完 doSignal 以后,会把 condition 队列中的节点转移 到 aqs 队列上,逻辑结构图如下
这个时候会判断 ThreadA 的 prev 节点也就是 head 节点 的 waitStatus,如果大于 0 或者设置 SIGNAL 失败,表示 节点被设置成了 CANCELLED 状态。这个时候会唤醒 ThreadA 这个线程。否则就基于 AQS 队列的机制来唤 醒,也就是等到 ThreadB 释放锁之后来唤醒 ThreadA。

被阻塞的线程唤醒后的逻辑

前面在分析 await 方法时,线程会被阻塞。而通过 signal 被唤醒之后又继续回到上次执行的逻辑中。
checkInterruptWhileWaiting 这个方法是干嘛呢?其实从 名字就可以看出来,就是 ThreadA 在 condition 队列被阻 塞的过程中,有没有被其他线程触发过中断请求

public final void await() throws InterruptedException {
if (Thread.interrupted()) throw new
InterruptedException();
   Node node = addConditionWaiter(); int savedState = fullyRelease(node);
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) ! = 0)
break; }
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT; 
if (node.nextWaiter != null) //clean up if cancelled
unlinkCancelledWaiters(); 
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}

checkInterruptWhileWaiting

如果当前线程被中断,则调用 transferAfterCancelledWait 方法判断后续的处理应该是 抛出 InterruptedException 还是重新中断。

acquireQueued

这个方法在讲 aqs 的时候说过,是的当前被唤醒的节点 ThreadA 去抢占同步锁。并且要恢复到原本的重入次数状 态。调用完这个方法之后,AQS 队列的状态如下
将 head 节点的 waitStatus 设置为-1,Signal 状态。

await 和 signal 的总结

我把前面的整个分解的图再通过一张整体的结构图来表 述,线程 awaitThread(ThreadProducer) 先通过 lock.lock()方法获取锁成功 后调用了 condition.await 方法进入等待队列,而另一个 线程 signalThread (ThreadConsumer)通过 lock.lock()方法获取锁成功后调用 了 condition.signal 或者 signalAll 方法,使得线程 awaitThread 能够有机会移入到同步队列中,当其他线程 释放 lock 后使得线程 awaitThread 能够有机会获取 lock,从而使得线程awaitThread 能够从 await 方法中退 出执行后续操作。
如果 awaitThread 获取 lock 失败会直 接进入到同步队列。

在这里插入图片描述

1、阻塞:await()方法中,在线程释放锁资源之后,如果节点 不在 AQS 等待队列,则阻塞当前线程,如果在等待队 列,则自旋等待尝试获取锁
2、释放:signal()后,节点会从 condition 队列移动到 AQS 等待队列,则进入正常锁的获取流程

阻塞队列 BlockingQueue

何谓阻塞,添加元素的时候,队列满了,阻塞添加元素的线程
获取元素, 队列为空, 获取数据的线程会阻塞
因此,阻塞队列是一个生产者/消费者的场景。

在这里插入图片描述

常用的阻塞队列ArrayBlockingQueue和LinkedBlockingQueue。
LinkedBlockingQueue是无界队列,ArrayBlockingQueue是有界队列。
在这里插入图片描述

public class App {
    public static void main( String[] args ) throws InterruptedException {
        BlockingQueue<String> blockingQueue=new ArrayBlockingQueue<>(10);
        blockingQueue.put("Mic");
        blockingQueue.take(); //wait/notify   | condition .await/signal | Lock
//        blockingQueue.iterator();
        System.out.println( "Hello World!" );
    }
}

ArrayBlockingQueue

在这里插入图片描述
可以看出ArrayBlockingQueue是由Condition+数组来实现的。Condition用来做条件控制。有notEmpty和notFull 两个condition条件。
在这里插入图片描述

在这里插入图片描述

CountDownLatch

从命 名可以解读到 countdown 是倒数的意思,类似于我们倒计 时的概念。
countdownlatch 提供了两个方法,一个是 countDown, 一个是 await, countdownlatch 初始化的时候需要传入一 个整数,在这个整数倒数到 0 之前,调用了 await 方法的 程序都必须要等待,然后通过 countDown 来倒数。

public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch=new CountDownLatch(3);
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"->begin");
            countDownLatch.countDown(); //初始值-1 =3-1=2;
            System.out.println(Thread.currentThread().getName()+"->end");
        },"t1").start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"->begin");
            countDownLatch.countDown(); //2-1=1;
            System.out.println(Thread.currentThread().getName()+"->end");
        },"t2").start();
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"->begin");
            countDownLatch.countDown(); //1-1=1;
            System.out.println(Thread.currentThread().getName()+"->end");
        },"t3").start();

        countDownLatch.await(); //阻塞Main线程
    }
}

t1和t2、t3线程是可以正常执行的,阻塞的是main线程。
调用 CountDownLatch 的 countDown 方法时,这个计数器就会减一。
通过 await 方法去阻塞主流程。
在这里插入图片描述
模拟高并发场景

public class CountDownLatchDemo1 implements Runnable{

    static CountDownLatch countDownLatch=new CountDownLatch(1);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(new CountDownLatchDemo1()).start();
        }
        //TODO ..
        countDownLatch.countDown(); //1-0
    }
    @Override
    public void run() {
        try {
            countDownLatch.await(); //阻塞线程| 1000个线程阻塞
            System.out.println(Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

countDownLatch.await()会阻塞1000个线程。

CountDownLatch 源码分析

对于 CountDownLatch,我们仅仅需要关心两个方法,一 个是 countDown() 方法,另一个是 await() 方法。 countDown() 方法每次调用都会将 state 减 1,直到 state 的值为 0;而 await 是一个阻塞方法,当 state 减 为 0 的时候,await 方法才会返回。await 可以被多个线 程调用,大家在这个时候脑子里要有个图:所有调用了 await 方法的线程阻塞在 AQS 的阻塞队列中,等待条件 满足(state == 0),将线程从队列中一个个唤醒过来。

CyclicBarrier

Semaphore

semaphore 也就是我们常说的信号灯,semaphore 可以控 制同时访问的线程个数,通过 acquire 获取一个许可,如 果没有就等待,通过 release 释放一个许可。有点类似限流 的作用。叫信号灯的原因也和他的用处有关,比如某商场 就 5 个停车位,每个停车位只能停一辆车,如果这个时候 来了 10 辆车,必须要等前面有空的车位才能进入。

public class SemaphoreDemo {

    public static void main(String[] args) {
        Semaphore semaphore=new Semaphore(5); //令牌数 state=5
        for(int i=0;i<10;i++){
            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(); //5-1 获得令牌.(没拿到令牌,会阻塞,拿到了就可以往下执行)
                System.out.println("第"+num+"线程占用一个令牌");
                Thread.sleep(3000);
                System.out.println("第"+num+"线程释放一个令牌");
                semaphore.release(); //释放令牌
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

运行结果:

1线程占用一个令牌
第4线程占用一个令牌
第3线程占用一个令牌
第2线程占用一个令牌
第0线程占用一个令牌

第4线程释放一个令牌
第1线程释放一个令牌
第0线程释放一个令牌
第2线程释放一个令牌
第3线程释放一个令牌
第8线程占用一个令牌
第7线程占用一个令牌
第6线程占用一个令牌
第5线程占用一个令牌
第9线程占用一个令牌
第8线程释放一个令牌
第7线程释放一个令牌
第6线程释放一个令牌
第5线程释放一个令牌
第9线程释放一个令牌

Semaphore使用场景

Semaphore 比较常见的就是用来做限流操作了。

Semaphore 源码分析

从 Semaphore 的功能来看,我们基本能猜测到它的底层 实现一定是基于 AQS 的共享所,因为需要实现多个线程共 享一个令牌池

创建 Semaphore 实例的时候,需要一个参数 permits, 这个基本上可以确定是设置给 AQS 的 state 的,然后每 个线程调用 acquire 的时候,执行 state = state - 1,

release 的时候执行 state = state + 1,当然,acquire 的 时候,如果 state=0,说明没有资源了,需要等待其他线 程 release。
Semaphore 分公平策略和非公平策略

FairSync

static final class FairSync extends Sync {
private static final long
serialVersionUID = 2014338818796000944L;
FairSync(int permits) { super(permits);
}
protected int tryAcquireShared(int acquires) {
for (;;) {
	// 区别就在于是不是会先判断是否有线程在排 队,然后才进行 CAS 减操作
	if(hasQueuedPredecessors()) return -1;
	int available = getState();
	int remaining = available - acquires;
	if (remaining < 0 || compareAndSetState(available,remaining))
	return remaining;
} }
}

NofairSync

通过对比发现公平和非公平的区别就在于是否多了一个 hasQueuedPredecessors 的判断

static final class NonfairSync extends Sync {
   private static final long
serialVersionUID = - 2694183684443567898L;
NonfairSync(int permits) { super(permits);
}
protected int tryAcquireShared(int acquires) {
return nonfairTryAcquireShared(acquires);
   }
}
final int nonfairTryAcquireShared(int acquires) {
	for (;;) {
		int available = getState();
		int remaining = available - acquires;
		if (remaining < 0 ||
		compareAndSetState(available,remaining))
		return remaining;
} }

后面的代码和 CountDownLatch 的是完全一样,都是 基于共享锁的实现

原子类

基于unsafe类中的cas操作来实现的

public class AtomicDemo {

//    public static int i=0;
    public static AtomicInteger i= new AtomicInteger(0);
    public static void incr(){
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
//        i++; //
        i.getAndIncrement();
    }
    public static void main(String[] args) {
        for (int j = 0; j < 1000; j++) {
            new Thread(()->AtomicDemo.incr()).start();
        }
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("result:"+i.get());
    }
}

输出结果:

result:1000
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值