java中Semaphore的作用是什么?如何使用?

信号量

简单理解,java中的Semaphore类的作用就是限制某个资源最多同时允许设定数量的线程访问.
这个资源可以是一个代码块,可以是数据库连接.

使用示例

  1. 使用多线程并发执行四次任务. 使用信号量限制do something代码块最多有两个线程并发访问.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
   @Test
    public void test1() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 设置信号量为2
        Semaphore semaphore = new Semaphore(2);

        // 并发执行4次任务
        for (int i = 0; i < 4; i++) {
            executorService.execute(() -> {
            try {
                    System.out.println("task start time:"+new Date());
                    semaphore.acquire();
                    // do something
                    Thread.sleep(1000);
                    // do something
                    System.out.println("task end time:"+new Date());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release();
                }
            });
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

输出结果

1
2
3
4
5
6
7
8
task start time:Sun Apr 19 21:28:22 CST 2020
task start time:Sun Apr 19 21:28:22 CST 2020
task start time:Sun Apr 19 21:28:22 CST 2020
task start time:Sun Apr 19 21:28:22 CST 2020
task end time:Sun Apr 19 21:28:23 CST 2020
task end time:Sun Apr 19 21:28:23 CST 2020
task end time:Sun Apr 19 21:28:24 CST 2020
task end time:Sun Apr 19 21:28:24 CST 2020

可以看到四个任务并发开始执行.同一秒开始启动.但是由于do something代码块使用信号量进行了限制.do something代码块的实际执行是先有两个线程 获得了许可并发执行了do something代码块并释放了许可其他两个线程获得许可才开始执行do something代码块.

  1. 限制数据库连接的访问.设置连接池最大连接数量,当没有空闲连接的时候,新的获取数据库连接的请求进行等待,等待超过一定时间进行放弃.

定义数据库连接类.具有执行sql和关闭连接方法.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
   /**
    * 数据库连接
    */
   public class Connection{
       // 信号量
       private Semaphore semaphore;

       public Connection(Semaphore semaphore) {
           this.semaphore = semaphore;
       }

       /**
        * 执行sql
        * @param sql
        */
       public void runSql(String sql){
           try {
               Thread.sleep(1000);
               System.out.println(new Date()+" exec sql:"+sql);
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }

       /**
        * 关闭数据库连接
        */
       public void closeConnection(){
           semaphore.release();
       }
   }

定义数据库连接池.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
    public class DataBaseConnPool{
        // 数据库链接最大连接数
        private Integer maxPoolSize;
        // 获取数据库连接的超时时间.
        private Long timeOut;

        // 信号量
        private Semaphore semaphore;

        public DataBaseConnPool(Integer maxPoolSize, Long timeOut) {
            this.maxPoolSize = maxPoolSize;
            this.timeOut = timeOut;
            semaphore = new Semaphore(maxPoolSize);
        }

        /**
         * 获取数据库连接
         * @return
         */
        public Connection getConnection(){
            boolean getConn = false;
            try {
                getConn = semaphore.tryAcquire(timeOut, TimeUnit.MILLISECONDS);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (getConn){
                return new Connection(semaphore);
            }else {
                throw new RuntimeException("获取数据库链接超时!");
            }
        }

    }

创建连接池执行sql

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
    @Test
    public void test2() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 连接池数量设置为5,超时时间是6s
        DataBaseConnPool dataBaseConnPool = new DataBaseConnPool(5, 6000L);
        // 并发执行36次任务
        for (int i = 0; i < 36; i++) {
            executorService.execute(() -> {
                // 获取连接
                Connection connection = dataBaseConnPool.getConnection();
                // 执行sql
                connection.runSql("select name from tbl_user");
                // 关闭连接
                connection.closeConnection();
            });
        }
        try {
            Thread.sleep(8000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

输出结果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
Sun Apr 19 22:30:59 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 31 availablePool: 0
Sun Apr 19 22:30:59 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 31 availablePool: 0
Sun Apr 19 22:30:59 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 31 availablePool: 0
Sun Apr 19 22:30:59 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 31 availablePool: 0
Sun Apr 19 22:30:59 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 27 availablePool: 0
Sun Apr 19 22:31:00 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 26 availablePool: 0
Sun Apr 19 22:31:00 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 26 availablePool: 0
Sun Apr 19 22:31:00 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 26 availablePool: 0
Sun Apr 19 22:31:00 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 26 availablePool: 0
Sun Apr 19 22:31:00 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 22 availablePool: 0
Sun Apr 19 22:31:01 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 21 availablePool: 0
Sun Apr 19 22:31:01 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 21 availablePool: 1
Sun Apr 19 22:31:01 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 19 availablePool: 0
Sun Apr 19 22:31:01 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 21 availablePool: 0
Sun Apr 19 22:31:01 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 17 availablePool: 0
Sun Apr 19 22:31:02 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 16 availablePool: 0
Sun Apr 19 22:31:02 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 16 availablePool: 0
Sun Apr 19 22:31:02 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 15 availablePool: 1
Sun Apr 19 22:31:02 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 14 availablePool: 1
Sun Apr 19 22:31:02 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 16 availablePool: 0
Sun Apr 19 22:31:03 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 11 availablePool: 0
Sun Apr 19 22:31:03 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 11 availablePool: 0
Sun Apr 19 22:31:03 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 11 availablePool: 0
Sun Apr 19 22:31:03 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 9 availablePool: 0
Sun Apr 19 22:31:03 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 11 availablePool: 1
Sun Apr 19 22:31:04 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 6 availablePool: 0
Sun Apr 19 22:31:04 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 6 availablePool: 1
Sun Apr 19 22:31:04 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 5 availablePool: 1
Sun Apr 19 22:31:04 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 5 availablePool: 2
Sun Apr 19 22:31:04 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 6 availablePool: 0
Exception in thread "pool-1-thread-36" java.lang.RuntimeException: 获取数据库链接超时!
	at com.kklt.test.juc.SemaphoreTest$DataBaseConnPool.getConnection(SemaphoreTest.java:101)
	at com.kklt.test.juc.SemaphoreTest.lambda$test2$1(SemaphoreTest.java:55)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:748)
Sun Apr 19 22:31:05 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 0 availablePool: 0
Sun Apr 19 22:31:05 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 0 availablePool: 1
Sun Apr 19 22:31:05 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 0 availablePool: 1
Sun Apr 19 22:31:05 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 0 availablePool: 1
Sun Apr 19 22:31:05 CST 2020 exec sql:select name from tbl_user getWaitQueueLength: 0 availablePool: 3

可以看到,同时可以使用的数据库连接资源为5.获取数据库连接等待超过六秒的线程获取失败.