2.3、juc锁-信号量Semaphore

1、使用场景:

1、共享资源只有两个,线程却有3个

2、我们要下载很多图片,并发异步进行,每个下载都会开辟一个新线程,可是我们又担心太多线程肯定cpu吃不消,那么我们这里也可以用信号量控制一下最大开辟线程数。

3、我们工作中遇到的各种池化资源,例如连接池、对象池、线程池等等。其中,你可能最熟悉数据库连接池,在同一时刻,一定是允许多个线程同时使用连接池的,当然,每个连接在被释放前,是不允许其他线程使用的。

2、简单介绍:

1、Semaphore是一个计数信号量,本质是一个"共享锁"(该锁可被多个线程所持有)。

2、信号量维护了一个信号量许可集。线程可以通过调用acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

3、有一个功能是 Lock 不容易实现的,那就是:Semaphore 可以允许多个线程访问一个临界区

4、"公平信号量"和"非公平信号量"的释放信号量的机制是一样的!不同的是它们获取信号量的机制:线程在尝试获取信号量许可时,对于公平信号量而言,如果当前线程不在CLH队列的头部,则排队等候;而对于非公平信号量而言,无论当前线程是不是在CLH队列的头部,它都会直接获取信号量。该差异具体的体现在,它们的tryAcquireShared()函数的实现不同。

3、实现原理:

https://www.cnblogs.com/skywang12345/p/3534050.html

和管程相比,信号量可以实现的独特功能就是同时允许多个线程进入临界区,但是信号量不能做的就是同时唤醒多个线程去争抢锁,只能唤醒一个阻塞中的线程,而且信号量模型是没有Condition的概念的,即阻塞线程被醒了直接就运行了而不会去检查此时临界条件是否已经不满足了,基于此考虑信号量模型才会设计出只能让一个线程被唤醒,否则就会出现因为缺少Condition检查而带来的线程安全问题。正因为缺失了Condition,所以用信号量来实现阻塞队列就很麻烦,因为要自己实现类似Condition的逻辑

4、例子

static int count;

// 初始化信号量

static final Semaphore s= new Semaphore(1);

// 用信号量保证互斥   

static void addOne() {

      s.acquire();

      try {

            count+=1;

      } finally {

            s.release();

      }

}

实现限流器:不允许多于 N 个线程同时进入临界区

我们用一个 List来保存对象实例,用 Semaphore 实现限流器。关键的代码是 ObjPool 里面的 exec() 方法,这个方法里面实现了限流的功能。在这个方法里面,我们首先调用 acquire() 方法(与之匹配的是在 finally 里面调用 release() 方法),假设对象池的大小是 10,信号量的计数器初始化为 10,那么前 10 个线程调用 acquire() 方法,都能继续执行,相当于通过了信号灯,而其他线程则会阻塞在 acquire() 方法上。对于通过信号灯的线程,我们为每个线程分配了一个对象 t(这个分配工作是通过 pool.remove(0) 实现的),分配完之后会执行一个回调函数 func,而函数的参数正是前面分配的对象 t ;执行完回调函数之后,它们就会释放对象(这个释放工作是通过 pool.add(t) 实现的),同时调用 release() 方法来更新信号量的计数器。如果此时信号量里计数器的值小于等于 0,那么说明有线程在等待,此时会自动唤醒等待的线程。

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Semaphore;

public class SemaphoreTest1 {

    private static final int SEM_MAX = 10;

    public static void main(String[] args) {

        Semaphore sem = new Semaphore(SEM_MAX);

        //创建线程池

        ExecutorService threadPool = Executors.newFixedThreadPool(3);

        //在线程池中执行任务

        threadPool.execute(new MyThread(sem, 5));

        threadPool.execute(new MyThread(sem, 4));

        threadPool.execute(new MyThread(sem, 7));

        //关闭池

        threadPool.shutdown();

    }

}

class MyThread extends Thread {

    private volatile Semaphore sem;    // 信号量

    private int count;        // 申请信号量的大小

    MyThread(Semaphore sem, int count) {

        this.sem = sem;

        this.count = count;

    }

    public void run() {

        try {

            // 从信号量中获取count个许可

            sem.acquire(count);

            Thread.sleep(2000);

            System.out.println(Thread.currentThread().getName() + " acquire count="+count);

        } catch (InterruptedException e) {

            e.printStackTrace();

        } finally {

            // 释放给定数目的许可,将其返回到信号量。

            sem.release(count);

            System.out.println(Thread.currentThread().getName() + " release " + count + "");

        }

    }

}

(某一次)运行结果:
pool-1-thread-1 acquire count=5

pool-1-thread-2 acquire count=4

pool-1-thread-1 release 5

pool-1-thread-2 release 4

pool-1-thread-3 acquire count=7

pool-1-thread-3 release 7

结果说明:信号量sem的许可总数是10个;共3个线程,分别需要获取的信号量许可数是5,4,7。前面两个线程获取到信号量的许可后,sem中剩余的可用的许可数是1;因此,最后一个线程必须等前两个线程释放了它们所持有的信号量许可之后,才能获取到7个信号量许可。

5、源码分析:

你可能感兴趣的:(2.3、juc锁-信号量Semaphore)