• Java Concurrency


    Semaphore 是一个控制访问多个共享资源的计数器。

    当一个线程想要访问某个共享资源,首先,它必须获得 semaphore。如果 semaphore 的内部计数器的值大于 0,那么 semaphore 减少计数器的值并允许访问共享的资源。计数器的值大于 0 表示,有可以自由使用的资源,所以线程可以访问并使用它们。另一种情况,如果 semaphore 的计数器的值等于 0,那么 semaphore 让线程进入休眠状态一直到计数器大于 0。计数器的值等于 0 表示全部的共享资源都正被线程们使用,所以此线程想要访问就必须等到某个资源成为自由的。当线程使用完共享资源时,它必须释放 semaphore 以便其他线程可以访问共享资源。这个操作会增加 semaphore 的内部计数器的值。

    二进制信号量

    二进制信号量(binary semaphores)是一种比较特殊的信号量,这种信号量只保护访问唯一的共享资源,它的内部计数器值只能是 1 或者 0。

    假设有若干个线程排队执行打印任务,打印机在同一时间只能执行单个任务,打印过程中其他线程只能挂起等待。

    public class Printer {
    
        private final Semaphore semaphore;
        
        public Printer() {
            semaphore = new Semaphore(1);
        }
        
        public void print(Object doc) {
            try {
                semaphore.acquire();
                Long duration = (long) (Math.random() * 10);
                System.out.printf("%s: PrintQueue: Printing a Job during %d seconds
    ", Thread.currentThread().getName(), duration);
                TimeUnit.SECONDS.sleep(duration);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                semaphore.release();
            }
        }
        
    }
    
    
    public class PrintJob implements Runnable {
        
        private Printer printer;
    
        public PrintJob(Printer printer) {
            this.printer = printer;
        }
        
        @Override
        public void run() {
            System.out.printf("%s: Going to print a job
    ", Thread.currentThread().getName());
            printer.print(new Object());
            System.out.printf("%s: The document has been printed
    ", Thread.currentThread().getName());        
        }
    
    }
    
    
    public class Main {
    
        public static void main (String args[]){
            Printer printer = new Printer();
            
            Thread[] threads = new Thread[10];
            for (int i = 0; i < 10; i++){
                threads[i] = new Thread(new PrintJob(printer), "Thread" + i);
                threads[i].start();
            }
        }
    
    }

    运行结果:

    Thread1: Going to print a job
    Thread6: Going to print a job
    Thread9: Going to print a job
    Thread7: Going to print a job
    Thread8: Going to print a job
    Thread4: Going to print a job
    Thread5: Going to print a job
    Thread3: Going to print a job
    Thread0: Going to print a job
    Thread2: Going to print a job
    Thread1: PrintQueue: Printing a Job during 7 seconds
    Thread1: The document has been printed
    Thread6: PrintQueue: Printing a Job during 5 seconds
    Thread6: The document has been printed
    Thread9: PrintQueue: Printing a Job during 4 seconds
    Thread9: The document has been printed
    Thread7: PrintQueue: Printing a Job during 8 seconds
    Thread7: The document has been printed
    Thread8: PrintQueue: Printing a Job during 6 seconds
    Thread8: The document has been printed
    Thread4: PrintQueue: Printing a Job during 2 seconds
    Thread4: The document has been printed
    Thread5: PrintQueue: Printing a Job during 0 seconds
    Thread5: The document has been printed
    Thread3: PrintQueue: Printing a Job during 7 seconds
    Thread3: The document has been printed
    Thread0: PrintQueue: Printing a Job during 0 seconds
    Thread0: The document has been printed
    Thread2: PrintQueue: Printing a Job during 5 seconds
    Thread2: The document has been printed

    这个例子的关键是 Printer 类的 print() 方法。此方法展示了当你使用 semaphore 来实现临界区以保护共享资源时必须遵守的三个步骤:

    1. 首先,调用 acquire() 方法获得信号量;
    2. 然后,操作共享资源;
    3. 最后,调用 release()  方法释放信号量。

    另一个重点是 Printer 类的构造方法和初始化 Semaphore 对象。将信号量初始化为 1,就是创建了一个二进制信号量。二进制信号量只保护一个共享资源,这个例子中就是 printer。当开始 10 个线程时,第一个获得 semaphore 的线程就获得临界区的访问权,其他线程都会被 semaphore 阻塞,直到获得 semaphore 的线程释放它。当 semaphore 被释放时,它会在等待的线程中选择其中一个并赋予其临界区的访问权。所有的打印任务都会被执行,只是它们需要排队,一个接一个地执行。

    控制并发访问多个共享资源

    在上个例子,使用二进制信号量来保护一个共享资源。但是,信号量也可以用来保护多个共享资源。下面的类使用信号量控制对内容池的访问:

    class Pool {
        private static final int MAX_AVAILABLE = 100;
        private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
    
        public Object getItem() throws InterruptedException {
            available.acquire();
            return getNextAvailableItem();
        }
    
        public void putItem(Object x) {
            if (markAsUnused(x))
                available.release();
        }
    
        // Not a particularly efficient data structure; just for demo
    
        protected Object[] items = ... whatever kinds of items being managed
        protected boolean[] used = new boolean[MAX_AVAILABLE];
    
        protected synchronized Object getNextAvailableItem() {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (!used[i]) {
                    used[i] = true;
                    return items[i];
                }
            }
            return null; // not reached
        }
    
        protected synchronized boolean markAsUnused(Object item) {
            for (int i = 0; i < MAX_AVAILABLE; ++i) {
                if (item == items[i]) {
                    if (used[i]) {
                        used[i] = false;
                        return true;
                    } else
                        return false;
                }
            }
            return false;
        }
    
    }

    公平模式

    与 ReentrantLock 一样,Semaphore 也提供一个接收 fair 参数的构造方法。当此参数置为 true 时,Semaphore 保证等待时间最久的线程优先获得信号量。

    Semaphore 的方法

    Semaphore 类有另外 2 个版本的 acquire() 方法:

    • acquireUninterruptibly():acquire()方法是当 semaphore 的内部计数器的值为 0 时,阻塞线程直到 semaphore 被释放。在阻塞期间,线程可能会被中断,然后此方法抛出 InterruptedException 异常。而此版本的 acquire 方法会忽略线程的中断而且不会抛出任何异常。
    • tryAcquire():此方法会尝试获取 semaphore。如果成功,返回true。如果不成功,返回 false 值,并不会被阻塞和等待 semaphore 的释放。

    acquire、acquireUninterruptibly、tryAcquire 和 release 方法有一个外加的包含一个 int 参数的版本。这个参数表示线程想要获取或者释放 semaphore 的许可数。

  • 相关阅读:
    geowebcache发布 arcgis 瓦片
    BLANK
    基于 SpringBoot 高仿某度网盘项目,前后端分离(含源码)
    基于SpringBoot+WebMagic实现一个的爬虫框架
    博客园主题
    vue el 自动计算时间加1天
    python报警告qt.gtimg.cn
    量化交易日志
    mybatis 一对多。对多对
    DBeaver执行SQL脚本,导入导出
  • 原文地址:https://www.cnblogs.com/huey/p/6019369.html
Copyright © 2020-2023  润新知