java 使用semaphore控制并发访问资源

是什么?为什么?怎么做?

 

一、是什么

一看到这个semaphore,就会想到操作系统中的信号量。在操作系统中,往往利用信号量的P/V解决进程间的同步和互斥问题。

同样的,在多线程环境下,信号量也是个重要概念,概念和用法也差不多。它负责协调各个线程, 以保证它们能够正确、合理的使用公共资源。

当一个线程想要访问某个共享资源,首先,它必须获得semaphore。如果semaphore的内部计数器的值大于0,那么semaphore减少计数器的值并允许线程访问共享的资源。计数器的值大于0表示目前有可以自由使用的资源,所以线程可以访问并使用它们。

如果semaphore的计数器的值等于0,semaphore就会让线程进入休眠状态,一直到计数器大于0。计数器的值等于0表示全部的共享资源都正被线程们使用。

线程使用完共享资源时,为了让其他线程可以访问共享资源,必须放出semaphore。这个操作会增加semaphore的内部计数器的值。

1.举个停车场的例子

假设停车场只有三个车位,一开始三个车位都是空的。

这时如果同时来了五辆车,看门人允许其中三辆不受阻碍的进入,然后放下车拦,剩下的车则必须在入口等待,后来的车也都不得不在入口处等待。

这时,有一辆车离开停车场,看门人得知后,打开车拦,放入一辆,如果又离开两辆,则又可以放入两辆,如此往复。

这里的车位就是公共资源,车就是线程,而看门人起到了信号量的作用。

2.信号量起到的作用

信号量是一个非负整数(车位数),所有通过它的线程(车辆)都会将该整数减一(通过它当然是为了使用资源),当该整数值为零时,所有试图通过它的线程都将处于等待状态。

在信号量上我们定义两种操作:Wait(等待)和Release(释放)。当一个线程调用Wait(等待)操作时,它要么通过然后将信号量减一,要么一直等下去,直到信号量大于一或超时。Release(释放)实际上是在信号量上执行加操作,对应于车辆离开停车场,该操作之所以叫做“释放”是因为加操作实际上是释放了由信号量守护的资源。

二、为什么

公共资源是有限的,且公共资源被使用时,不可共享。

就像一台打印机,不能同时打印多份文件。所以在执行一个打印任务时,其他的打印任务就必须等待执行完毕,才能继续下一个任务。

根据这个情景实现思路大致是这样:实例化一个binary semaphores。这个semaphores可以保护访问共享资源的独立特性,内部计数器的值只能是1或者0。如果当前打印机获得了一个任务,那么信号量就从1变成0,别的任务无法获得获得信号量,只能等待。如果任务执行完毕了,那么打印机资源就被释放了,信号量就从0变成1,某一任务可以获取这个信号量,开始执行,别的任务则需要继续等待

这里的binary semaphores是不是很像之前的lock?和之前获取锁,释放锁的思想非常相似,可以实现简单的互斥锁的功能,既然是互斥锁,那么同样可以实现公平和不公平,这里就不展开了(具体方法和以前差不多)。

semaphore可以协调线程之间对公共资源的使用,保证安全合理。

三、怎么做

1.简单例子

最重要的是semaphore.acquire()方法和semaphore.release()方法。

semaphore.acquire()方法尝试获取信号量,如果无法获取,则当前线程等待。如果获取,则信号量-1,线程执行。执行完毕后,semaphore.release()方法使信号量+1。

执行结果:

顺带一提,可以使用:

实现公平模式。结果如下:

这里有个问题,为什么会出现0?那是因为semaphore.release()之后,刚刚释放的令牌马上被别的线程获取了,这时候令牌数再次为0,semaphore.availablePermits()为0,所以输出为0。

虽然我们使用了公平模式,但是依然存在不公平的现象(公平模式下线程的执行顺序不一定有保证,不一定是绝对的先来先得,只能尽量保证公平)。

2.实现打印机的例子

实现思路大致如下:

先实现一个打印机类,打印机同时只能执行一个任务,所以信号量为1:

打印机的功能是什么?是打印,所以实现一个打印方法:

同时存在很多打印的任务,所以写一个task类,实现并发:

task类要做什么?让打印机打印。所以调用打印机的print方法:

实现思路大概就是这样。最终运行结果为:

在这里,使用信号量实现了互斥锁的功能。

四、总结

信号量非常重要,需要好好掌握。

发表评论

电子邮件地址不会被公开。 必填项已用*标注