• Tags
  •         
  • www.breakyizhan.com
  •    

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

    Java中信号量Semaphore的工作

    通常,要使用信号量,要访问共享资源的线程会尝试获取许可证。

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

    Java 在实现此机制的java.util.concurrent包中提供了Semaphore类,因此您不必实现自己的信号量。

    流程图 :

    Semaphore类中的构造函数:Semaphore类中有两个构造函数。

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

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

    使用信号量Semaphore作为锁

    我们可以使用信号量来锁定对资源的访问,每个想要使用该资源的线程必须首先调用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
                {
                    // 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 B to execute
                        Thread.sleep(10);
                    }
                } catch (InterruptedException exc) {
                        System.out.println(exc);
                    }
            
                    // Release the permit.
                    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
        {
            // creating a Semaphore object
            // with number of permits 1
            Semaphore sem = new Semaphore(1);
            
            // creating two threads with name A and B
            // Note that thread A will increment the count
            // and thread B will decrement the count
            MyThread mt1 = new MyThread(sem, "A");
            MyThread mt2 = new MyThread(sem, "B");
            
            // stating threads A and B
            mt1.start();
            mt2.start();
            
            // waiting for threads A and B
            mt1.join();
            mt2.join();
            
            // count will always remain 0 after
            // both threads will complete their execution
            System.out.println("count: " + Shared.count);
        }
    }
    

    输出:

    Starting A
    Starting B
    B is waiting for a permit.
    B gets a permit.
    A is waiting for a permit.
    B: -1
    B: -2
    B: -3
    B: -4
    B: -5
    B releases the permit.
    A gets a permit.
    A: -4
    A: -3
    A: -2
    A: -1
    A: 0
    A 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。

     
    转载请保留页面地址:https://www.breakyizhan.com/java/4969.html