Java中的信号量Semaphore

news/2024/5/20 7:19:02 标签: JUC, Semaphore, 信号量

翻译自geeksforgeeks。

信号量Semaphore 通过使用计数器counter来控制对共享资源的访问。如果计数器大于零,则允许访问。如果为零,则拒绝访问。计数器对共享资源的访问许可进行计数。因此,要访问资源,线程必须要从信号量得到许可。

信号量如何工作

一般而言,当一个线程需要访问共享资源时,需要获得许可,这里就要用到信号量Semaphore

  • 如果信号量的计数count大于零,则线程获取许可permit,导致信号量的计数递减。
  • 否则,线程将被阻塞,直到获得许可permit
  • 当线程不再需要访问共享资源时,它会释放许可permit,这会导致信号量的计数count递增。
  • 如果有另一个线程在等待许可,那么该线程将在那时获得许可。

Java在java.util.concurrent包中提供了实现此机制的Semaphore类,因此你不必实现自己的信号量
流程图在这里插入图片描述
Semaphore 的构造函数有两种:

Semaphore(int num)
Semaphore(int num, boolean how)

这里,num指定初始的许可计数。因此,它也就指定了一次可以同时访问共享资源的线程数。如果是1,那么同时只能有一个线程可以访问该资源。
默认情况下,所有等待的线程都以未定义的顺序被授予许可。通过设置how为true,可以确保等待线程按其请求访问的顺序被授予许可。

使用信号量作为锁(防止竞争条件)

我们可以使用信号量来锁定对资源的访问,每个想要使用该资源的线程在访问资源之前必须首先调用acquire()方法来获取锁。当线程不再需要资源时,它必须调用release()方法来释放锁。这里有个例子来说明:

// java program to demonstrate  
// use of semaphores Locks 
import java.util.concurrent.*; 
  
//A shared resource/class. 
class Shared  
{ 
    static int count = 0; 
} 
  
class MyThread extends Thread 
{ 
    Semaphore sem; 
    String threadName; 
    public MyThread(Semaphore sem, String threadName)  
    { 
        super(threadName); 
        this.sem = sem; 
        this.threadName = threadName; 
    } 
  
    @Override
    public void run() { 
          
        // run by thread A 
        if(this.getName().equals("A")) 
        { 
            System.out.println("Starting " + threadName); 
            try 
            { 
                // 首先获取许可
                System.out.println(threadName + " is waiting for a permit."); 
              
                // 获取锁 
                sem.acquire(); 
              
                System.out.println(threadName + " gets a permit."); 
          
                // 访问共享资源 
                // 其他等待线程会一直等到次线程释放锁  
                for(int i=0; i < 5; i++) 
                { 
                    Shared.count++; 
                    System.out.println(threadName + ": " + Shared.count); 
          
                    // 允许上下文切换
                    // 让线程B运行 
                    Thread.sleep(10); 
                } 
            } catch (InterruptedException exc) { 
                    System.out.println(exc); 
                } 
          
                // 释放许可 
                System.out.println(threadName + " releases the permit."); 
                sem.release(); 
        } 
          
        // run by thread B 
        else
        { 
            System.out.println("Starting " + threadName); 
            try 
            { 
                // First, get a permit. 
                System.out.println(threadName + " is waiting for a permit."); 
              
                // acquiring the lock 
                sem.acquire(); 
              
                System.out.println(threadName + " gets a permit."); 
          
                // Now, accessing the shared resource. 
                // other waiting threads will wait, until this  
                // thread release the lock 
                for(int i=0; i < 5; i++) 
                { 
                    Shared.count--; 
                    System.out.println(threadName + ": " + Shared.count); 
          
                    // Now, allowing a context switch -- if possible. 
                    // for thread A to execute 
                    Thread.sleep(10); 
                } 
            } catch (InterruptedException exc) { 
                    System.out.println(exc); 
                } 
                // Release the permit. 
                System.out.println(threadName + " releases the permit."); 
                sem.release(); 
        } 
    } 
} 
  
// Driver class 
public class SemaphoreDemo  
{ 
    public static void main(String args[]) throws InterruptedException  
    { 
        // 创建一个Semaphore对象
        // 许可数设置为1 
        Semaphore sem = new Semaphore(1); 
          
        // 创建名为A和B的两个线程 
        // 注意,线程A将递增count 
        // 线程B将递减count 
        MyThread mt1 = new MyThread(sem, "A"); 
        MyThread mt2 = new MyThread(sem, "B"); 
          
        // 启动线程A和B.
        mt1.start(); 
        mt2.start(); 
          
        // 等待线程A和B.  
        mt1.join(); 
        mt2.join(); 
          
        //两个线程都完成执行后count会始终为0
        System.out.println("count: " + Shared.count); 
    } 
} 

输出:

Starting A
A is waiting for a permit.
A gets a permit.
A: 1
Starting B
B is waiting for a permit.
A: 2
A: 3
A: 4
A: 5
A releases the permit.
B gets a permit.
B: 4
B: 3
B: 2
B: 1
B: 0
B releases the permit.
count: 0

注意:在上述程序的不同执行中输出可能不同,但count变量的最终值将始终保持为0。

解释

  • 程序使用信号量来控制对count变量的访问,count变量是Shared类中的静态变量。Shared.count由线程A递增五次,并由线程B递减五次。为了防止这两个线程同时访问Shared.count,只有在从控制信号量获取许可后才允许访问。访问完成后,许可将被释放。这样,一次只有一个线程将访问Shared.count,如输出所示。
  • 注意在MyThread类中的run()方法中调用sleep()方法,用于“证明”对Shared.count的访问被信号量同步了。在run()方法中,对sleep()的调用会导致线程在每次访问Shared.count之间暂停。这通常会使另一个线程运行。然而,由于信号量,第二个线程必须等到第一个线程释放许可,这只有在第一个线程的所有访问完成后才会发生。因此,Shared.count首先由线程A递增五次,然后由线程B递减五次。递增和递减不会在汇编代码中交叉执行。
  • 如果不使用信号量,两个线程对Shared.count的访问将同时发生,递增和递减会交叉执行。要确认这一点,可以尝试注释acquire()release()。运行程序时,会看到对Shared.count的访问不再同步,因此不会始终获得计数值为0。

http://www.niftyadmin.cn/n/1738957.html

相关文章

JVM如何工作? - JVM架构

翻译自geeksforgeeks。 JVM&#xff08;Java虚拟机&#xff09;充当运行Java应用程序的运行时引擎的角色。JVM实际上是调用java代码中的main方法的。JVM是JRE&#xff08;Java运行时环境&#xff09;的一部分。 Java应用程序号称WORA&#xff08;Write Once Run Anywhere&…

java中的可重入锁ReentrantLock

翻译自geeksforgeeks。 背景 在Java中实现线程同步的传统方法是使用synchronized关键字。 虽然它提供了基本同步功能&#xff0c;但synchronized的使用比较死板。 比如说&#xff0c;一个线程只能锁一次。同步块不提供任何等待队列的机制&#xff0c;并且在一个线程退出后&…

修改 .gitignore 后生效方法

修改 .gitignore 后生效方法 懒得每次网上查&#xff0c;记录到自己博客 git rm -r --cached . # 清除缓存 git add . # 追踪文件 git commit -m "更新.gitignore" # 注释提交 git push origin master # 推送远程

iview上传组件跨域问题

用iview上传组件&#xff0c;如下图&#xff1a; 后台地址&#xff1a;uplaodUrl: this.$host "/file/upload" 后端处理&#xff1a; 已经添加了CrossOrigin允许跨域请求&#xff08;其他post或get请求都可以通过&#xff09;&#xff0c;上传文件时却总出现跨域问…

ssh remote host identification has changed

ssh登录可能会遇到这样的错误。 最简单的解决方式&#xff1a; ssh-keygen -R <host>实例&#xff08;假设要连接到192.168.1.20&#xff09;&#xff1a; ssh-keygen -R 192.168.1.20-R hostname 从known_hosts文件中删除属于hostname的所有密钥。 引用自ssh keygen

启动mysql失败问题排查

在ubuntu 16.04上用systemctl start mysql启动mysql&#xff0c;启动失败。 查看/var/log/mysql/error.log文件&#xff0c;发现&#xff1a; [ERROR] unknown variable log_slow_queries/var/log/mysql/mysql-slow.log网上搜索了下&#xff0c;是因为设置慢查询配置语句有变化…

Ubuntu 18.04 msyql远程连接失败原因

端口未开放 用sudo ufw allow mysql开放3306端口配置问题 把/etc/mysql/mysql.conf.d/mysqld.cnf的bind-address 127.0.0.1注释掉user未设置远程访问 grant all privileges on *.* to 用户名% identified by 密码;

ubuntu不同版本配置ip

18.04&#xff1a; sudo vim /etc/netplan/50-cloud-init.yaml 配置完&#xff1a; sudo netplan apply 18.04前 sudo vim /etc/network/interfaces 配置完&#xff1a; sudo service networking restart