TA的每日心情 | 开心 2021-3-12 23:18 |
---|
签到天数: 2 天 [LV.1]初来乍到
|
一、信号量 信号量通过一个计数器控制对共享资源的访问。如果计数器大于0,则访问被允许,如果为0,则访问被禁止。 计数器计算的结果是允许访问共享资源的通行证。因此,为了访问共享资源,线程必须从信号量得到通行证, 如果该信号量的计数大于0,则此线程获得一个通行证,这将导致信号量的计数递减,否则,此线程将阻塞直到获得一个通行证为止。当此线程不再需要访问共享资源时,它释放该通行证,这导致信号量的计数递增,如果
另一个线程等待通行证,则那个线程将在那时获得通行证。java的Semaphore类实现了这种机制。
Semaphore的两个构造函数
Semaphore(int num)
Semaphore(int num,boolean how)
num指定初始通行证计数。因此,num指定了每次能够访问共享资源的线程数,如果num是1,则只有一个线程每次都能够访问共享资源。默认情况下,以未定义次序授予等待线程的通行证,如果将how设为true, 可确保以请求访问的次序授予等待线程的通行证。 要获得通行证,调用acquire()方法 void acquire() throws InterruptedException
void acquire(int num) throws InterruptedException 第一种形式获得一个通行证,第二种形式获得num个通行证。如果该通行证无法在调用时授予,则调用线程暂停,直到获得通行证。 要释放通行证,调用repease方法,
void release()
void release(int num) 分别释放一个或num个通行证。 为了用一个信号量控制对资源的访问,使用该资源的每个线程在访问该资源之前,首先要调用acquire(), 当一个线程结束对资源的使用后,它必须调用release()。
二、下面的例子示范如何使用信号量:- import java.util.concurrent.*;
-
- class SemDemo {
-
- public static void main(String args[]) {
- Semaphore sem = new Semaphore(1);
-
- new IncThread(sem, "A");
- new DecThread(sem, "B");
-
- }
- }
-
- // A shared resource.
- class Shared {
- static int count = 0;
- }
-
- // A thread of execution that increments count.
- class IncThread implements Runnable {
- String name;
- Semaphore sem;
-
- IncThread(Semaphore s, String n) {
- sem = s;
- name = n;
- new Thread(this).start();
- }
-
- public void run() {
-
- System.out.println("Starting " + name);
-
- try {
- // First, get a permit.
- System.out.println(name + " is waiting for a permit.");
- sem.acquire();
- System.out.println(name + " gets a permit.");
-
- // Now, access shared resource.
- for(int i=0; i < 5; i++) {
- Shared.count++;
- System.out.println(name + ": " + Shared.count);
-
- // Now, allow a context switch -- if possible.
- Thread.sleep(10);
- }
- } catch (InterruptedException exc) {
- System.out.println(exc);
- }
-
- // Release the permit.
- System.out.println(name + " releases the permit.");
- sem.release();
- }
- }
-
- // A thread of execution that deccrements count.
- class DecThread implements Runnable {
- String name;
- Semaphore sem;
-
- DecThread(Semaphore s, String n) {
- sem = s;
- name = n;
- new Thread(this).start();
- }
-
- public void run() {
-
- System.out.println("Starting " + name);
-
- try {
- // First, get a permit.
- System.out.println(name + " is waiting for a permit.");
- sem.acquire();
- System.out.println(name + " gets a permit.");
-
- // Now, access shared resource.
- for(int i=0; i < 5; i++) {
- Shared.count--;
- System.out.println(name + ": " + Shared.count);
-
- // Now, allow a context switch -- if possible.
- Thread.sleep(10);
- }
- } catch (InterruptedException exc) {
- System.out.println(exc);
- }
-
- // Release the permit.
- System.out.println(name + " releases the permit.");
- sem.release();
- }
- }
- 程序的输出:
- C:java>java SemDemo
- 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.
复制代码 这个程序使用信号量控制对Shared类中静态变量count的访问,为了防止这两个线程同时访问Shared.count,仅当线程从控制信号量获得通行证之后才允许访问,访问结束后,该通行证被释放。通过这种方式,保证一次只有一个线程访问Shared.count. 在run()中,sleep()的调用导致调用线程每次访问Shared.count之后都暂停,这通常会使第二个线程运行, 但在这里,由于信号量的原因,第二个线程必须等待第一个线程释放通行证,而这要等到第一个线程对共享资源的所有访问都结束之后才能发生。
三、使用两个信号量同步生产者和消费者线程。
- import java.util.concurrent.Semaphore;
-
- class Q { //仓库
- int n; //仓库中的产品
-
- // Start with consumer semaphore unavailable.
- static Semaphore semCon = new Semaphore(0);
- static Semaphore semProd = new Semaphore(1);
-
- void get() {
- try {
- semCon.acquire();
- } catch(InterruptedException e) {
- System.out.println("InterruptedException caught");
- }
-
- System.out.println("Got: " + n);
- semProd.release();
- }
-
- void put(int n) {
- try {
- semProd.acquire();
- } catch(InterruptedException e) {
- System.out.println("InterruptedException caught");
- }
-
- this.n = n;
- System.out.println("Put: " + n);
- semCon.release();
- }
- }
-
- class Producer implements Runnable { //生产者
- Q q;
-
- Producer(Q q) {
- this.q = q;
- new Thread(this, "Producer").start();
- }
-
- public void run() {
- for(int i=0; i < 20; i++) q.put(i);
- }
- }
-
- class Consumer implements Runnable { //消费者
- Q q;
-
- Consumer(Q q) {
- this.q = q;
- new Thread(this, "Consumer").start();
- }
-
- public void run() {
- for(int i=0; i < 20; i++) q.get();
- }
- }
-
- class ProdCon { //测试类
- public static void main(String args[]) {
- Q q = new Q();
- new Consumer(q);
- new Producer(q);
- }
- }
- 程序的部分输出:
- C:java>java ProdCon
- Put: 0
- Got: 0
- Put: 1
- Got: 1
- Put: 2
- Got: 2
- Put: 3
- Got: 3
- Put: 4
- Got: 4
- Put: 5
- Got: 5
- Put: 6
- Got: 6
- Put: 7
- Got: 7
- Put: 8
- Got: 8
- Put: 9
- Got: 9
- Put: 10
- Got: 10
复制代码 可以看到,put()调用和get()调用是同步的。每次调用一个put()都会随后调用一个get(),不会有失配的值。如果没有信号量,put()的多次调用可能会没有与其匹配的get()调用。从而导致出现失配的值,可删除信号量代码验证。 put()和get()调用通过两个信号量来处理:semProd和semCon。在put()生成一个值之前,它必须从semProd获得一个通行证。它设置该值后释放semCon。在get()使用一个值之前,它必须从semCon获得一个通过证。它使用该值后,必须释放semPord。这种“给与受”的机制确保put()的每次调用必须跟随一个get()调用。 semCon在初始化的时候无法获得通行证,从而保证了put()首先运行。设置初始同步状态的能力是信号量的强大表现之一。
源码下载:http://file.javaxxz.com/2014/11/5/000002515.zip |
|