[笔记][Java7并发编程实战手册]3.2 资源的并发访问控制Semaphore信号量

[笔记][Java7并发编程实战手册]系列目录


简介

本文学习信号量Semaphore机制。


Semaphore

  1. 本质是一个共享锁
  2. 内部维护一个可用的信号集,获取信号量之前需要先申请获取信号数量;用完之后,则需要释放信号量;如果不释放,那么其他等待线程则一直阻塞直到获取信号量或则被中断为止
  3. 本人的理解是:互斥锁是同一时间只能一个线程访问,而在这里,是同一时间允许获取到了信号量的线程并发访问,而没有获取到信号量的则必须等待信号量的释放;
  4. 将信号量初始化为 1,使得它在使用时最多只有一个可用的许可,从而可用作一个相互排斥的锁。这通常也称为二进制信号量,因为它只能有两种状态:一个可用的许可,或零个可用的许可。按此方式使用时,二进制信号量具有某种属性(与很多 Lock 实现不同),即可以由线程释放“锁”,而不是由所有者(因为信号量没有所有权的概念)。
  5. 同样信号量也分为“公平信号量”和“非公平信号量”,他们的关系就和普通的一致,不过也是包裹了一层“信号量”

下面通过以前的获取苹果的价格的列子来演示信号量的使用

示例1:先来看看上面第3点

public class Client {
    public static void main(String[] args) {
        final GoodInfo gi = new GoodInfo();
        Thread[] read = new Thread[10]; //读线程,读取10次商品信息
        for (int i = 0; i < 10; i++) {
            read[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    gi.getInfo();
                }
            });
        }
        final Thread[] write = new Thread[3]; //写线程,修改3次价格
        for (int i = 0; i < 3; i++) {
            final int finalI = i;
            write[i] = new Thread(new Runnable() {
                @Override
                public void run() {
                    gi.setPrice();
                }
            });
        }

        for (Thread t : write) {
            t.start();
        }
        for (Thread t : read) {
            t.start();
        }
    }
}

/** 商品信息类 */
class GoodInfo {
    private String name = "苹果"; //商品名称
    private double price = 10;   //商品价格
    private final Semaphore sh = new Semaphore(3);  //申请3个信号量

    /**
     * 读取商品信息
     *
     * @return
     */
    public void getInfo() {
        try {
            sh.acquire(1); //获取1个信号量,如果不够,则当前线程等待
            String name = this.name;
            double price = this.price;
            System.out.println(Thread.currentThread().getName() + "线程获取了商品信息:" + name + ":" + price);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {  
            sh.release();  //释放信号量
        }
    }

    /**
     * 修改商品价格,每次都自增1
     *
     */
    public void setPrice() {
        try {
            sh.acquire(1);
            this.price = this.price + 1;  //目的是:多个线程并发的时候会出现错误数据(数据竞争来验证获取了信号量的线程是并发线程)
            System.out.println("--------------" + Thread.currentThread().getName() + "线程修改了商品价格:" + name + ":" + price);
            Thread.sleep(20);
        } catch (Exception e) {
            e.printStackTrace();
        } finally { 
            sh.release();  //如果把这里注释掉,则可以看出,其他等待获取信号量的是获取不到信号量了。因为一单这里的三个线程把可用的信号量占用完之后,其他线程就只能等待可用的信号量了
        }
    }
}

某一次的运行结果:

--------------Thread-11线程修改了商品价格:苹果:11.0
Thread-5线程获取了商品信息:苹果:10.0
Thread-7线程获取了商品信息:苹果:11.0
Thread-0线程获取了商品信息:苹果:11.0
Thread-1线程获取了商品信息:苹果:11.0
Thread-2线程获取了商品信息:苹果:11.0
Thread-4线程获取了商品信息:苹果:11.0
Thread-3线程获取了商品信息:苹果:11.0
--------------Thread-10线程修改了商品价格:苹果:12.0
Thread-6线程获取了商品信息:苹果:12.0
--------------Thread-12线程修改了商品价格:苹果:13.0
Thread-8线程获取了商品信息:苹果:13.0
Thread-9线程获取了商品信息:苹果:13.0

如果把写价格的释放信号量注释掉,运行结果是

--------------Thread-12线程修改了商品价格:苹果:11.0
Thread-8线程获取了商品信息:苹果:10.0
Thread-0线程获取了商品信息:苹果:11.0
Thread-4线程获取了商品信息:苹果:11.0
Thread-2线程获取了商品信息:苹果:11.0
Thread-3线程获取了商品信息:苹果:11.0
Thread-1线程获取了商品信息:苹果:11.0
Thread-6线程获取了商品信息:苹果:11.0
Thread-5线程获取了商品信息:苹果:11.0
Thread-7线程获取了商品信息:苹果:11.0
--------------Thread-10线程修改了商品价格:苹果:12.0
--------------Thread-11线程修改了商品价格:苹果:13.0

线程没有运行完,没有获取到信号量的线程,则一直等待。造成了死锁。


说明
上面的运行结果可以看出来:
线程11修改了商品结果,但是线程5和线程11 并发的获取了原有的价格10,导致了线程5获取到了错误的数据信息。

示例2:实现二进制信号量

把以上示例的 构造信号量对象的参数置为1

new Semaphore(1);   /创建二进制信号量

某一次的运行结果是

--------------Thread-10线程修改了商品价格:苹果:11.0
--------------Thread-11线程修改了商品价格:苹果:12.0
Thread-1线程获取了商品信息:苹果:12.0
Thread-2线程获取了商品信息:苹果:12.0
Thread-5线程获取了商品信息:苹果:12.0
Thread-6线程获取了商品信息:苹果:12.0
Thread-9线程获取了商品信息:苹果:12.0
--------------Thread-12线程修改了商品价格:苹果:13.0
Thread-0线程获取了商品信息:苹果:13.0
Thread-4线程获取了商品信息:苹果:13.0
Thread-3线程获取了商品信息:苹果:13.0
Thread-7线程获取了商品信息:苹果:13.0
Thread-8线程获取了商品信息:苹果:13.0

可以看出来,而互斥锁的效果一致。


而这个类的使用场景。我暂时还没想明白。为什么有这样的机制。至于公平信号量和非公平信号量就不演示了。很简单的。知道互斥锁和信号量的区别是什么就行了


另外的acquire方法

  1. acquireUninterruptibly
    其实差不多就是acquire(),底层还是调用了tryAcquireShared(arg)方法,从此信号量中获取许可,在有可用的许可前将其阻塞。
  2. tryAcquire
    尝试过去信号量,能获取就返回true,不能就返回false;从而避开线程阻塞,在使用公平锁的使用要注意此方法。会破坏公平性
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值