java high concurrency Semaphore (Semaphore)

Posted by sunder on Tue, 25 Jan 2022 13:03:32 +0100

semaphore
Pronunciation: English[ ˈ sem ə f ɔː ®] Beauty[ ˈ sem ə f ɔː r]

Semaphore (semaphore) provides a more powerful control method for multi-threaded cooperation, synchronized and reentrant lock. These two locks can only allow one thread to access one resource at a time, and semaphore can control how many threads can access specific resources.

Semaphore common scenario: current limiting

for instance:
For example, there is a parking lot with five vacant spaces. There is a guard at the door. The five keys in his hand correspond to the locks on the five parking spaces. When a car comes, the guard will give the driver a key, then go in and find the corresponding parking space and stop. When he goes out, the driver will return the key to the guard. The business of the parking lot is quite good. At the same time, there are 100 Liang cars. The guard has only five keys in his hand, and only five cars can enter at the same time. Other cars can only wait until someone returns the keys to the guard before allowing other vehicles to enter.

In the above example, the guard is equivalent to Semaphore, the car key is equivalent to license, and the car is equivalent to thread.

Semaphore main methods

Semaphore(int permits): Construction method. The parameter represents the number of licenses and is used to create semaphores

Semaphore(int permits,boolean fair): Construction method, when fair be equal to true A count semaphore with a given number of permissions is created and set as a fair semaphore

void acquire() throws InterruptedException: Before obtaining a permission from this semaphore, the thread will be blocked all the time, which is equivalent to a car occupying a parking space. This method will respond to the thread interrupt, indicating the calling thread interrupt Method, which causes the method to throw InterruptedException abnormal

void acquire(int permits) throws InterruptedException : and acquire()Method is similar, and the parameter indicates the number of licenses to be obtained; For example, a large truck needs to enter the parking lot. Because the car is relatively large, it needs to apply for three parking spaces before it can be parked

void acquireUninterruptibly(int permits) : and acquire(int permits) Method is similar, but does not respond to thread interrupts

boolean tryAcquire(): Try to obtain 1 license, and return immediately regardless of whether it can be obtained successfully, true Indicates success, false Indicates that the acquisition failed

boolean tryAcquire(int permits): and tryAcquire(),Indicates an attempt to get permits Licenses

boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException: Try to obtain 1 license within the specified time, and the success is returned true,Unable to obtain the license after the specified time, return false

boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException: and tryAcquire(long timeout, TimeUnit unit)Similar, one more permits Parameter indicating an attempt to get permits Licenses

void release(): Releasing a permit and returning it to the semaphore is equivalent to returning the key to the guard when the car leaves the parking lot

void release(int n): release n Licenses

int availablePermits(): Number of licenses currently available

Example 1: simple use of Semaphore

package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zjf
 */
public class SemaphoreDemo {

   static Semaphore semaphore=new Semaphore(5);

    public static void main(String[] args) {
        for (int i=0;i<20;i++){
            new T("t-"+i).start();
        }
    }

    public static class T extends Thread{
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis()+","+thread.getName());
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                semaphore.release();
                System.out.println(System.currentTimeMillis()+","+thread.getName());
            }

        }
    }

}

Output:

624425447147,t-0
1624425447147,t-3
1624425447147,t-4
1624425447147,t-2
1624425447147,t-1
1624425450153,t-4
1624425450153,t-11
1624425450153,t-7
1624425450153,t-1
1624425450153,t-6
1624425450153,t-0
1624425450153,t-5
1624425450153,t-3
1624425450153,t-2
1624425450153,t-10
1624425453163,t-6
1624425453163,t-7
1624425453163,t-12
1624425453163,t-13
1624425453163,t-14
1624425453163,t-11
1624425453163,t-8
1624425453163,t-5
1624425453163,t-9
1624425453163,t-10
1624425456177,t-15
1624425456177,t-19
1624425456177,t-14
1624425456177,t-18
1624425456177,t-13
1624425456177,t-12
1624425456177,t-8
1624425456177,t-9
1624425456177,t-16
1624425456177,t-17
1624425459190,t-19
1624425459190,t-16
1624425459190,t-15
1624425459190,t-17
1624425459190,t-18

newSemaphore(5) in the code creates a semaphore with 5 licenses. Each thread obtains 1 license and allows five threads to obtain licenses. It can also be seen from the output that five threads can obtain licenses at the same time, and other threads need to wait for the licensed threads to release the license before running. The thread that obtains the license will block on the acquire() method and cannot continue until the license is obtained.

Example 2: do not release after obtaining a license
The Semaphore was a little stunned. The driver gave the key when he went in and didn't return it when he came out. The guard wouldn't say anything. The end result is that no other vehicles can enter.

package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zjf
 */
public class SemaphoreDemo {

   static Semaphore semaphore=new Semaphore(5);

    public static void main(String[] args) {
        for (int i=0;i<20;i++){
            new T("t-"+i).start();
        }
    }

    public static class T extends Thread{
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis()+","+thread.getName());
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

}

Output results:

1624425807833,t-0
1624425807833,t-1
1624425807833,t-4
1624425807833,t-2
1624425807833,t-3

After the above program runs, it can't end. Look at the code. After obtaining the license, the code that does not release the license eventually leads to the number of available licenses being 0, and other threads can't obtain the license, which will be displayed in semaphore acquire(); The program cannot end because of waiting at

Example 3: release the license to the correct pose
In example 1, is there a problem releasing the lock in finally?
If an exception occurs in the process of obtaining the lock, resulting in the failure of obtaining the lock, and finally the license is released, what will happen in the end, resulting in the increase of the number of licenses out of thin air.

package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zjf
 */
public class SemaphoreDemo {

   static Semaphore semaphore=new Semaphore(1);

    public static void main(String[] args) throws InterruptedException {
    /*    for (int i=0;i<20;i++){
            new T("t-"+i).start();
        }*/
        new T("t1").start();
        T t2 = new T("t2");
        t2.start();
        T t3 = new T("t3");
        t3.start();
        t2.interrupt();;
        t3.interrupt();
    }

    public static class T extends Thread{
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            try {
                semaphore.acquire();
                System.out.println(System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
                TimeUnit.SECONDS.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                semaphore.release();
                System.out.println(System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
            }
        }
    }
}

Output:

1624426961649,t1 Currently available licenses 0
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
	at thread.SemaphoreDemo$T.run(SemaphoreDemo.java:36)
java.lang.InterruptedException
1624426961651,t3 Currently available licenses 1
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
	at thread.SemaphoreDemo$T.run(SemaphoreDemo.java:36)
1624426961651,t2 Currently available licenses 2
1624427061663,t1 Currently available licenses 3

The number of signal quantity licenses in the program is 1. Three threads are created to obtain licenses. Thread t1 obtains licenses successfully, and then sleeps for 100 seconds. The other two threads are blocked in Semaphore acquire(); Method, the code sends interrupt signals to threads t2 and t3. Let's take a look at the source code of acquire in Semaphore:

 public void acquire() throws InterruptedException {

This method will respond to the thread interrupt. After sending the interrupt signal to t2 and t3 in the main thread, the acquire() method will trigger the InterruptedException exception. t2 and t3 finally did not obtain the license, but they all performed the operation of releasing the license in finally. Finally, the number of licenses changed to 2, resulting in an increase in the number of licenses. So there is a problem with the way the license is released in the program. It needs to be improved to release the lock after obtaining the license successfully

The correct way to release the lock is as follows:

package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zjf
 */
public class SemaphoreDemo {

   static Semaphore semaphore=new Semaphore(1);

    public static void main(String[] args) throws InterruptedException {
    /*    for (int i=0;i<20;i++){
            new T("t-"+i).start();
        }*/
        new T("t1").start();
        T t2 = new T("t2");
        t2.start();
        T t3 = new T("t3");
        t3.start();
        t2.interrupt();;
        t3.interrupt();
    }

    public static class T extends Thread{
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            //Judge whether the license is successful
            Boolean acquireSuccess= false;
            try {
                semaphore.acquire();
                acquireSuccess=true;
                System.out.println(System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (acquireSuccess){
                    semaphore.release();
                }
                System.out.println(System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
            }

        }
    }
}

Output:

java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
	at thread.SemaphoreDemo$T.run(SemaphoreDemo.java:37)
java.lang.InterruptedException1624427539263,t3 Currently available licenses 0

	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireSharedInterruptibly(AbstractQueuedSynchronizer.java:1302)
1624427539263,t2 Currently available licenses 0
	at java.util.concurrent.Semaphore.acquire(Semaphore.java:312)
	at thread.SemaphoreDemo$T.run(SemaphoreDemo.java:37)
1624427549273,t1 Currently available licenses 1

A variable acquireSuccess is added to the program to mark whether the license is obtained successfully. In finally, whether to release the license is determined according to whether this variable is true.

Example 4: you want to obtain a license within the specified time
The driver came to the parking lot and found that the parking lot was full. He could only go in after waiting outside for the internal car to come out, but he didn't know how long it would take. He hoped to wait 10 minutes. If he still couldn't go in, he wouldn't stop here.

Semaphore has two internal methods that can provide the function of obtaining licenses over time:

boolean tryAcquire(long timeout, TimeUnit unit) throws InterruptedException: Try to obtain 1 license within the specified time, and the success is returned true,Unable to obtain the license after the specified time, return false

boolean tryAcquire(int permits, long timeout, TimeUnit unit) throws InterruptedException: and tryAcquire(long timeout, TimeUnit unit)Similar, one more permits Parameter indicating an attempt to get permits Licenses
package thread;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author zjf
 */
public class SemaphoreDemo {

   static Semaphore semaphore=new Semaphore(1);

    public static void main(String[] args) throws InterruptedException {
    /*    for (int i=0;i<20;i++){
            new T("t-"+i).start();
        }*/
        new T("t1").start();
        T t2 = new T("t2");
        t2.start();
        T t3 = new T("t3");
        t3.start();
    }

    public static class T extends Thread{
        public T(String name) {
            super(name);
        }

        @Override
        public void run() {
            Thread thread = Thread.currentThread();
            //Judge whether the license is successful
            Boolean acquireSuccess= false;
            try {
                //Try to get a license for one second
                System.out.println(System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
                acquireSuccess=semaphore.tryAcquire(1,TimeUnit.SECONDS);
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                if (acquireSuccess){
                    System.out.println("License obtained successfully"+System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
                    semaphore.release();
                }else {
                    System.out.println("Failed to obtain license"+System.currentTimeMillis()+","+thread.getName()+"Currently available licenses"+semaphore.availablePermits());
                }

            }

        }
    }

}

Output:

1624428835029,t2 Currently available licenses 1
1624428835029,t3 Currently available licenses 1
1624428835029,t1 Currently available licenses 1
 License obtained successfully 1624428845039,t3 Currently available licenses 0
 Failed to obtain license 1624428846057,t1 Currently available licenses 1
 Failed to obtain license 1624428846057,t2 Currently available licenses 1

The number of licenses in the code is 1, semaphore tryAcquire(1,TimeUnit.SECONDS);: Indicates an attempt to obtain a license within 1 second. If the license is obtained successfully, true will be returned immediately. If the license is not obtained after 1 second, false will be returned. Thread t1 succeeds in obtaining the license, and then sleeps for 10 seconds. From the output, it can be seen that both t2 and t3 have tried for 1 second and failed to obtain the license

Other instructions

  • Semaphore creates unfair semaphores by default. What do you mean? This involves fairness and unfairness. For example: five parking spaces allow five vehicles to enter. When 100 vehicles come, only five can enter, and the other 95 are waiting in line outside. One car just came out, and 10 more cars came at this time. Did the 10 cars jump in front of the other 95 cars or queue behind the 95 cars? It's Fair for newcomers to queue up. It's unfair to jump in the queue and compete for the first one. For the parking lot, queuing must be better. However, for semaphores, unfair is more efficient, so it is unfair by default.
    A method with a throwsInterruptedException declaration indicates that the method will respond to a thread interrupt signal. What does it mean? It means that after calling the interrupt() method of the thread, these methods will trigger the InterruptedException exception. Even if these methods are blocked, they will return immediately, throw the InterruptedException exception, and the thread interrupt signal will be cleared.

Topics: Java Multithreading Concurrent Programming