[笔记][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;从而避开线程阻塞,在使用公平锁的使用要注意此方法。会破坏公平性

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-13 23:36:42

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

[笔记][Java7并发编程实战手册]3.3 资源的多副本并发访问控制Semaphore

[笔记][Java7并发编程实战手册]系列目录 简介 本文继续学习信号量Semaphore机制. 在3.2中其实已经讲解完了,之前对于信号量并发的使用场景不知道,看了本章节才想到一些: 下面就以 租车为列子来讲解并发访问的控制.(示例都很简单或许不符合现实逻辑) 信号量(非二进制信号量)是不保证同步的,需要额外的同步 示例 场景:有一个出租车公司,有三台车,有十个司机,每个司机工作的时间不一致,可以说是司机等待着别人还车后,接着租用汽车. /** * Created by zhuqiang on

[笔记][Java7并发编程实战手册]4.3 创建固定的线程执行器newFixedThreadPool线程池

[笔记][Java7并发编程实战手册]系列目录 简介 newFixedThreadPool(int nThreads, ThreadFactory threadFactory) 创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程,在需要时使用提供的 ThreadFactory 创建新线程. newCachedThreadPool()创建的线程池的特性是:自动回收不使用的线程(终止并从缓存中移除那些已有 60 秒钟未被使用的线程),(在无可用线程的情况下)自动的为新来的task创

[笔记][Java7并发编程实战手册]2.5使用Lock实现同步二

[笔记][Java7并发编程实战手册]系列目录 概要 接上一篇文章,练习修改锁的公平性,和在所中使用条件. 修改锁的公平性ReentrantLock /** *构造一个锁对象,默认为非公平锁 */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); } 根据ReentrantLock的构造可以看出来,默认会构造非公平锁: 公平锁与非公平锁有什么区别 公平锁 :有多个线程并发访

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

Java7并发编程实战手册 这一本实战的书籍.本笔记记录是看了该书.并且简化了书中的示例.的一些随笔记录 我觉得能给我更好的感觉.我觉得先看博客中转载的多线程系列 Java多线程系列-目录源码分析和理论.有时候真的觉得好烦躁.可是,没有这些理论实战中又觉得太多的未知. 所以本人觉得.先粗略的过一遍理论和源码分析.再来看学习实战,在写代码的过程中,去回想和联想理论就能更好的把知识串联起来了: 也可以看到本人在记录这些笔记的时候也会引用到博客中转载的多线程系列文章. [笔记][Java7并发编程实战

[笔记][Java7并发编程实战手册]第三章-线程同步辅助类-概要

[笔记][Java7并发编程实战手册]系列目录 有点着急了,没有太注重质量,自己也没有理解透,从本章起,读书和随笔笔记的质量会更好. 第三章 在本章中,我们将学习: 资源的并发访问控制 资源的多副本的并发访问控制 等待多个并发事件的完成 在集合点的同步 并发阶段任务的运行 并发阶段任务中的阶段交换 并发任务间的数据交换 回顾 在第二章中主要学习了以下接口 synchronized关键字 Lock接口以及实现类,如ReentrantLock.ReentrantReadWriteLock中的Read

[笔记][Java7并发编程实战手册]4.4 在执行器中执行任务并返回结果Callable、Future

[笔记][Java7并发编程实战手册]系列目录 简介 执行框架(Executor Framework)的优势之一就是,可以在运行并发任务的时候返回结果.但是需要以下两个类来实现功能: 1. 接口 Callable<V> 返回结果并且可能抛出异常的任务.实现者定义了一个不带任何参数的叫做 call 的方法. Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的.但是 Runnable 不会返回结果,并且无法抛出经过检查的异常. Executors 类

[笔记][Java7并发编程实战手册]4.5 运行多个任务并处理第一个结果ThreadPoolExecutor

[笔记][Java7并发编程实战手册]系列目录 简介 看到这个标题的时候,我也很纳闷,因为没有明白所表达的意思. ok,并发编程比较常见的一个问题是:当采用多个并发任务来解决一个问题的时候,往往只需要关心这个任务的第一个结果,例如:验证一个算法的时候,假如一个执行5个算法,那么最先返回结果的,就是最快的. 在本章将会学习,如何使用ThreadPoolExecutor来实现类似场景: 本章ThreadPoolExecutor使用心得 使用 ThreadPoolExecutor.invokeAny(

Java 7 并发编程实战手册目录

Java 7 并发编程实战手册目录 第一章线程管理 第二章线程同步基础 第三章线程同步辅助类 第四章线程执行器 第五章 Fork/Join框架 第六章并发集合 第七章定制并发类 第八章所有代码下载https://github.com/Wang-Jun-Chao/java-concurrency 版权声明:本文为博主原创文章,未经博主允许不得转载.

java并发编程实战手册(一)线程管理

本文主要是以知识点的形式对java多线程进行了解,学习java多线程的基础,本文参考书籍<java并发编程实战手册>,若有兴趣想研究跟高级的多线程思想,可以阅读<java并发编程实战>. 1.线程的创建和运行 java线程的创建有三种方式,可能大部分人只知道常用的两种: 1.继承Thread类,并且覆盖run()方法. 2.创建一个实现Runnable接口的类.使用带参数的Thread构造器来创建Thread对象. 3.使用Callable与Future来创建启动线程 1.创建Ca