线程并发工具类(Semaphore、Exchange)

71 阅读4分钟

信号量(Semaphore)

  • 概述:对资源有限的场景做流量控制

    • 主要是用来做流控的,协调线程,完成功能

    • 场景:实现数据库连接池

    • 示意图:

      image-20220222220900743

  • 源码分析:

    • 构造实参:整数

      1. 近似理解一个许可证,当线程需要执行一个方法的时候,首先调用acquire方法,拿到一个许可证,然后做子线程自己的业务:

        • 这个只是做许可证的流量控制
      2. 执行自己的业务逻辑

      3. 执行完后,调用release,归还许可证:拿给其他线程用

    • 假设只有4个许可证:

      • 那么同时最多就只能有四个线程来执行任务,其他的线程需要等待归还许可证后才能执行自己的业务逻辑
      • 这个就可以实现流控
  • 实现数据库连接池

    • 实现思路:使用信号量进行限流操作

      • 设置两个信号量(useful与useless),当前可用连接数与当前已用连接数

        • 如果不设置已用连接:连接数不受限制了,你来几个就给你几个
        • 虽然先定义了许可证,但是在使用的时候,我们不调用acquire,而是直接调用这个release,这个时候如果new 出了一个信号量对象去调用(这种机制是被允许的)
        • 这个时候就需要加一个已用连接数
      • 为什么调用了信号量的acquire后,还要对数据库连接池加锁?

        • 不是一个概念,信号量就只是做线程的流控,实际操作的时候还是对数据库加锁
        • 其实也可以不用锁这个池子,将许可证设置为1,那么就只能有一个线程进行访问
        • 也可以不显示使用synchronized,用线程安全的容器;
      • semaphore中的acquire与release

        •  * 不要wait后notify/notifyAll了,
           * semaphore内部是已经实现了,等待通知的:这个是由AQS进行实现的
           *     调用acquire:拿不到就wait
           *     调用release:发起通知,
          
    • 实现代码

      • DBPoolSemaphore.java

         package cn.enjoyedu.ch2.tools.semaphore;
         ​
         import java.sql.Connection;
         import java.util.LinkedList;
         import java.util.concurrent.Semaphore;
         ​
         /**
          *类说明:演示Semaphore用法,一个数据库连接池的实现;
          * 不要wait后notify/notifyAll了,
          * semaphore内部是已经实现了,等待通知的:这个是由AQS进行实现的
          *    调用acquire:拿不到就wait
          *    调用release:发起通知,
          */
         public class DBPoolSemaphore {
            
            private final static int POOL_SIZE = 10;
            //两个指示器,分别表示池子还有可用连接和已用连接
            private final Semaphore  useful,useless;
            //存放数据库连接的容器,采用LinkedList
            private static LinkedList<Connection> pool = new LinkedList<Connection>();
            //初始化池
            static {
                 for (int i = 0; i < POOL_SIZE; i++) {
                     pool.addLast(SqlConnectImpl.fetchConnection());
                 }
            }
            //数据库连接池构造
            public DBPoolSemaphore() {
               this.useful = new Semaphore(10);
               this.useless = new Semaphore(0);
            }
            
            /*归还连接*/
            public void returnConnect(Connection connection) throws InterruptedException {
               if(connection!=null) {
                  System.out.println("当前有"+useful.getQueueLength()+"个线程等待数据库连接!!"
                        +"可用连接数:"+useful.availablePermits());
                  //执行归还连接操作,请求归还的信号量
                  useless.acquire();
                  synchronized (pool) {
                     pool.addLast(connection);
                  }
                  //执行归还连接操作,释放这个连接持有的信号量,释放许可证
                  useful.release();
               }
            }
            
            /*从池子拿连接*/
            public Connection takeConnect() throws InterruptedException {
               //先请求连接,没有信号量,当前线程就阻塞,有就证明拿到了 ,这个时候就锁这个pool
               useful.acquire();
               Connection connection;
               synchronized (pool) {
                  connection = pool.removeFirst();
               }
               useless.release();
               return connection;
            }
            
         }
        
      • AppTest

         package cn.enjoyedu.ch2.tools.semaphore;
         ​
         import cn.enjoyedu.tools.SleepTools;
         ​
         import java.sql.Connection;
         import java.util.Random;
         ​
         /**
          *类说明:测试数据库连接池
          */
         public class AppTest {
         ​
            private static DBPoolSemaphore dbPool = new DBPoolSemaphore();
            
            private static class BusiThread extends Thread{
               @Override
               public void run() {
                  Random r = new Random();//让每个线程持有连接的时间不一样
                  long start = System.currentTimeMillis();
                  try {
                     Connection connect = dbPool.takeConnect();
                     System.out.println("Thread_"+Thread.currentThread().getId()
                           +"_获取数据库连接共耗时【"+(System.currentTimeMillis()-start)+"】ms.");
                     SleepTools.ms(100+r.nextInt(100));//模拟业务操作,线程持有连接查询数据
                     System.out.println("查询数据完成,归还连接!");
                     dbPool.returnConnect(connect);
                  } catch (InterruptedException e) {
                  }
               }
            }
            //50个线程并发去拿数据库连接池
            public static void main(String[] args) {
                 for (int i = 0; i < 50; i++) {
                     Thread thread = new BusiThread();
                     thread.start();
                 }
            }
            
         }
        
      • 运行截图:

        image-20220222222253751

  • Exchange

    • 概述:两个线程之间的协作(数据交换),处理同种数据的交换工作

    • 工作表现:

      • 线程A执行,拿到数据后,调用exchange方法阻塞;等线程B准备好数据(执行业务),

        线程调用exchange方法,因为之前的线程A是调用了exchange方法阻塞了的;此时两个线程进行数据交换;那么才继续执行

      • JDK内部保证,这线程安全