Semaphore原理浅析

236 阅读9分钟

Semaphore介绍

代码地址gitee.com/doforme/juc…

一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行。

信号量通常用于限制线程数,而不是访问某些(物理或逻辑)资源。 例如,这是一个使用信号量来控制对一个项目池的访问的类:

在获得项目之前,每个线程必须从信号量获取许可证,以确保某个项目可用。 当线程完成该项目后,它将返回到池中,并将许可证返回到信号量,允许另一个线程获取该项目。 请注意,当调用acquire()时,不会保持同步锁定,因为这将阻止某个项目返回到池中。 信号量封装了限制对池的访问所需的同步,与保持池本身一致性所需的任何同步分开。

信号量被初始化为一个,并且被使用,使得它只有至多一个允许可用,可以用作互斥锁。 这通常被称为二进制信号量 ,因为它只有两个状态:一个许可证可用,或零个许可证可用。 当以这种方式使用时,二进制信号量具有属性(与许多Lock实现不同),“锁”可以由除所有者之外的线程释放(因为信号量没有所有权概念)。 这在某些专门的上下文中是有用的,例如死锁恢复。

此类的构造函数可选择接受公平参数。 当设置为false时,此类不会保证线程获取许可的顺序。 特别是, 闯入是允许的,也就是说,一个线程调用acquire()可以提前已经等待线程分配的许可证-在等待线程队列的头部逻辑新的线程将自己。 当公平设置为真时,信号量保证调用acquire方法的线程被选择以按照它们调用这些方法的顺序获得许可(先进先出; FIFO)。 请注意,FIFO排序必须适用于这些方法中的特定内部执行点。 因此,一个线程可以在另一个线程之前调用acquire ,但是在另一个线程之后到达排序点,并且类似地从方法返回。 另请注意, 未定义的tryAcquire方法不符合公平性设置,但将采取任何可用的许可证。

通常,用于控制资源访问的信号量应该被公平地初始化,以确保线程没有被访问资源。 当使用信号量进行其他类型的同步控制时,非正常排序的吞吐量优势往往超过公平性。

本课程还提供了方便的方法, 一次acquirerelease多个许可证。 当没有公平地使用这些方法时,请注意增加无限期延期的风险。

内存一致性效应:在另一个线程中成功执行“获取”方法(如acquire()之前,调用“释放”方法之前的线程中的操作,例如release() happen-before

内存一致性协议

内存一致性属性

Chapter 17 of the Java Language Specification定义了内存操作(例如共享变量的读取和写入) 之前发生关系。 只有在写操作发生之前 ,一个线程的写入结果才能保证对另一个线程的读取可见。 synchronizedvolatile构造以及Thread.start()Thread.join()方法可以形成发生之前的关系。 尤其是:

  • 在线程中的每个动作发生在该线程中的每个动作之前 ,程序的顺序将稍后。
  • 在同一监视器每个后续锁定( synchronized块或方法条目) 之前,发生监视器的解锁( synchronized块或方法退出)。 并且因为事件发生之前的关系是可传递的,所以在解锁之前的线程的所有动作都发生在所有线程锁定之后的所有动作之前
  • 写入一个volatile字段发生在该同一字段的每个后续读取之前 。 写入和读取volatile字段具有与进入和退出显示器相似的内存一致性效果,但要求互斥锁定。
  • 在线程中调用start 发生在启动的线程中的任何动作之前
  • 线索中的全部动作发生,之前的任何其他线程成功地从返回join该线程。

java.util.concurrent及其子包中的所有类的方法将这些保证扩展到更高级别的同步。 尤其是:

  • 在将对象放入任何并发集合之前,线程中的操作发生在另一个线程中从该集合访问或删除该元素之后的操作之前
  • RunnableExecutor之前,线程中的Executor 发生在其执行开始之前 。 同样为Callables提交到一个ExecutorService
  • 由一个代表异步计算采取的行动Future 发生,之前通过对结果的检索后续行动Future.get()在另一个线程。
  • 操作之前为“释放”同步器的方法,如Lock.unlockSemaphore.releaseCountDownLatch.countDown 发生-前行动一个成功的“获取”方法如后续Lock.lockSemaphore.acquireCondition.awaitCountDownLatch.await在另一个线程相同的同步对象。
  • 对于每对经由成功交换对象的线程的Exchanger ,现有的行动exchange()在每个线程中发生,这些向对应的后续exchange()在另一个线程。
  • 调用CyclicBarrier.awaitPhaser.awaitAdvance 之前的操作(以及其变体) 发生在屏障动作执行的动作之前,以及由屏障动作执行的动作发生在从其他线程中的相应await成功返回之后的动作之前

Semaphore的构造方法

public Semaphore(int permits)

创建一个 Semaphore与给定数量的许可证和非公平公平设置。

  • 参数

    permits - permits的初始许可证。 该值可能为负数,在这种情况下,必须在任何获取被授予之前发布释放。

public Semaphore(int permits,
                 boolean fair)

创建一个 Semaphore与给定数量的许可证和给定的公平设置。

  • 参数

    permits - permits的初始许可证。 该值可能为负数,在这种情况下,必须在任何获取被授予之前发布释放。

    fair - true如果这个信号量将保证首先在竞争中首先授予许可证,否则 false

package com.juc.semaphore.semaphore1;

import java.util.concurrent.Semaphore;

public class Service {
    /**
     * semaphore 的同步性 保证线程排队的运行
     */
    private Semaphore semaphore = new Semaphore(1);

    public void testMethod(){
        try {
            System.out.println("***1 "+semaphore.toString());
            semaphore.acquire();
            System.out.println("semaphore = " + semaphore.hasQueuedThreads());
            System.out.println(semaphore.getQueueLength());
            System.out.println("***2 "+semaphore.toString());
            System.out.println(Thread.currentThread().getName() + " begin timer " + System.currentTimeMillis());
            Thread.sleep(5000);
            System.out.println(Thread.currentThread().getName() + " end timer " + System.currentTimeMillis());
            semaphore.release();
            System.out.println("***3 "+semaphore.toString());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

package com.juc.semaphore.semaphore1;

public class ThreadA extends Thread {

    private Service service;

    public ThreadA(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run(){
        service.testMethod();
    }
}

package com.juc.semaphore.semaphore1;

public class ThreadB extends Thread {
    private Service service;

    public ThreadB (Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

package com.juc.semaphore.semaphore1;

public class ThreadC extends Thread {
    private Service service;
    public ThreadC(Service service) {
        super();
        this.service = service;
    }

    @Override
    public void run() {
        service.testMethod();
    }
}

package com.juc.semaphore.semaphore1;

public class Run {
    public static void main(String[] args) {
        Service service = new Service();
        ThreadA a = new ThreadA(service);
        a.setName("A");
        ThreadB b = new ThreadB(service);
        b.setName("B");
        ThreadC c = new ThreadC(service);
        c.setName("C");
        a.start();
        b.start();
        c.start();
        
    }
}

package com.juc.semaphore.semaphore10;

import java.util.concurrent.Semaphore;

public class MyServiceFair {

    /**
     * 有时候,获取许可的顺序与线程的启动顺序有关 这信号量就分公平和非公平
     * 所谓公平信号量是获得锁的顺序和线程的启动顺序有关 但不能保证100%的获取信号量
     * 仅仅概率上的保证。
     * 而非公平信号量就无关
     */
    private boolean isFair = true;

    private Semaphore semaphore =new Semaphore(1, isFair);

    public void testMethod(){
        try {
            semaphore.acquire();
            System.out.println("threadName " +Thread.currentThread().getName() );
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }

}

Semaphore的其他方法

1.tryAcquire 试着获取许可

tryAcquire(int permits) 的作用是尝试的获得x个许可 如果获取不到则返回false

tryAcquire(long timeout, TimeUnit unit) 这个方法 在指定时间内尝试的获得1个许可 如果获取不到就返回false

tryAcquire(int permits, long timeout, TimeUnit unit) 在指定时间内尝试的获得X个许可 如果获取不到就返回false

 /**
     * tryAcquire(permits) 的作用是尝试的获得x个许可 如果获取不到则返回false
     */
    private Semaphore semaphore = new Semaphore(3);

    public void testMethod() {
        if (semaphore.tryAcquire(3)) { //semaphore.tryAcquire(3, 3, TimeUnit.SECONDS) / semaphore.tryAcquire(3, TimeUnit.SECONDS)
            System.out.println("threadName=  " + Thread.currentThread().getName() + " 首选进入!");
            for (int i = 0; i < Integer.MAX_VALUE; i++) {
                String newStr = new String();
                Math.random();
            }
            //方法release 对应的permits 也要修改
            semaphore.release(3);
        }else {
            System.out.println("threadName = " + Thread.currentThread().getName() + "未成功进入 !");
        }
    }

2.availablePermits 用于调试的参数

availablePermits 返回此 Semaphore 对象当前可用的许可数 方法通常用于调试 因为许可的数量可能经常的变动 不是固定的数

package com.juc.semaphore.semaphore7;

import java.util.concurrent.Semaphore;

public class MyService {
    /***
     * availablePermits 返回此 Semaphore 对象当前可用的许可数
     * 次方法通常用于调试 因为许可的数量可能经常的变动 不是固定的数
     */
    private Semaphore semaphore = new Semaphore(10);
    public void testMethod() {
        try {
            semaphore.acquire();
            System.out.println(semaphore.availablePermits());
            System.out.println(semaphore.availablePermits());
            System.out.println(semaphore.availablePermits());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
            System.out.println(semaphore.availablePermits());
        }

    }
}

3.drainPermits() 调用时返回许可个数同时 清空许可

package com.juc.semaphore.semaphore8;

import java.util.concurrent.Semaphore;

public class MyService {
    /**
     * drainPermits() 返回许可可用的数量 并将许可可用数量清0
     */
    private Semaphore semaphore = new Semaphore(10, true);

    public void testMethod() {
        try {
            semaphore.acquire();
            System.out.println(semaphore.availablePermits());
            System.out.println(semaphore.drainPermits() + "  " + semaphore.availablePermits());
            System.out.println(semaphore.drainPermits() + "  " + semaphore.availablePermits());
            System.out.println(semaphore.drainPermits() + "  " + semaphore.availablePermits());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            semaphore.release();
        }
    }

}

4.getQueueLength() hasQueuedThreads()

package com.juc.semaphore.semaphore9;

import java.util.concurrent.Semaphore;

public class MyService {
    /**
     * getQueueLength()的作用取得等待许可的线程个数
     * hasQueuedThreads() 判断有没有线程在等待这个
     */
    private Semaphore semaphore =new Semaphore(1);

    public void teseMethod(){
        try {
            semaphore.acquire();
            Thread.sleep(1000);
            System.out.println(" 还有大约 " + semaphore.getQueueLength() + " 个线程在等待");
            System.out.println(" 是否有线程正在等信号呢?" + semaphore.hasQueuedThreads());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            semaphore.release();
        }
    }
}

源码分析

Semaphore该类实现了 Serializable

其主要是通过 抽象的静态内部类Sync实现主要功能,Syn继承AbstractQueuedSynchronizer,

下面的方法是Semaphore的构造方法之一传入的是他的许可个数,而且在没有指定是否公平的获取的参数时,默认是非公平的

 public Semaphore(int permits) {
        sync = new NonfairSync(permits);
 }
  • setState
    protected final void setState(long newState)
    

    设置同步状态的值。 此操作具有volatile写入的记忆语义。

    • 参数

      newState - 新的状态值

   abstract static class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 1192457210091910933L;

        Sync(int permits) {
            setState(permits);
        }

        final int getPermits() {
            return getState(); //AbstractQueuedSynchronizer的状态值
        }

        final int nonfairTryAcquireShared(int acquires) {//批量的请求许可
            for (;;) {
                int available = getState();//首先状态值
                int remaining = available - acquires; //用现在的状态值-许可数 当减成负的值记录下当前的状态为了后面
                //tryReleaseShared 将这个State值加回0;这样呢就实现了这个锁的争夺和释放,
                if (remaining < 0 ||
                    compareAndSetState(available, remaining)) //CAS操作共享变量安全
                    return remaining;
            }
        }

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

        final void reducePermits(int reductions) {
            for (;;) {
                int current = getState();
                int next = current - reductions;
                if (next > current) // underflow
                    throw new Error("Permit count underflow");
                if (compareAndSetState(current, next))
                    return;
            }
        }

        final int drainPermits() {
            for (;;) {
                int current = getState();
                if (current == 0 || compareAndSetState(current, 0))
                    return current;
            }
        }
    }

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); //获取非公平的共享的许可
        }
    }