wait & notify & notifyAll 等待超时模式实现简易版数据库连接池

322 阅读5分钟

wait & notify 简介

  1. wait & notify & notifyAll 都必须在同步块里(即被 synchronized 修饰的方法)调用,即当前线程必须拥有此对象的 monitor(即锁)

  2. 如果我们不从同步块中调用 wait & notify & notifyAll 方法,程序将会报 IllegalMonitorStateException 异常。

  3. 如果我们不从同步块中调用 wait & notify & notifyAll 方法,在竞争条件中可能会存在丢失通知的可能.以生产者消费者模式来说明一下:

     1. Producer 线程测试条件(缓冲区是是否已满)并确认是否需要等待(如果发现缓冲区已满则需要等待)。这里涉及判断以及设置等待(如有需要),name必然不是原子性操作.假如不在同步块里,**那么就有可能先执行了判断,但还来不及设置等待,就执行2步骤**
     2. Consumer 线程在使用缓冲区中的元素后,设置条件。
     3. Consumer 线程调用 notify & notifyAll 方法; 但是这个通知不会被 Producer 接收到,因为 Producer 线程还没有等待。
     4. Producer 线程调用 wait() 方法并进入等待状态。
     5. 所以就丢失了通知,导致生产者消费者模式不可用.
    
  4. 2和3能回答为什么 wait & notify & notifyAll 必须在同步块里(即被 synchronized 修饰的方法)调用

  5. wait & notify & notifyAll 都是Object 里的方法

  6. wait & notify & notifyAll 不仅仅是普通方法或同步工具,更重要的是它们是 Java 中两个线程之间的通信机制。Java 程序是天生的多线程程序.

  7. wait & notify & notifyAll 的本质是基于条件对象的,而且只能由已经获得锁的线程调用。每个对象都可上锁.

  8. 6和7能回答为什么 wait & notify & notifyAll 都是Object 里的方法而不是 Thread 类中的方法

  9. wait & notify & notifyAll 方法都是本地方法,并且为final方法,无法被重写。

  10. 当前线程必须拥有此对象的 monitor(即锁),才能调用某个对象的 wait 方法能让当前线程阻塞。wait 方法的调用会使当前线程释放 monitor(即锁)(这种阻塞是通过提前释放synchronized锁,重新去请求锁导致的阻塞,这种请求必须有其他线程通过notify()或者notifyAll()唤醒重新竞争获得锁

  11. 同理,当前线程必须拥有此对象的 monitor(即锁)才能调用这个对象的 notify & notifyAll 方法,notify & notifyAll 能够唤醒一个正在等待这个对象的 monitor 的线程,如果有多个线程都在等待这个对象的 monitor,则只能唤醒其中一个线程;(notify & notifyAll 方法并不是真正释放锁,必须等到synchronized方法或者语法块执行完才真正释放锁

  12. 调用 notifyAll() 方法能够唤醒所有正在等待这个对象的 monitor 的线程,唤醒的线程获得锁的概率是随机的,取决于cpu调度

实现简易版数据库连接池

实现思路:

  1. DBPool 中使用 LinkedList 充当连接池容器
  2. DBPool 中 实现一个取数据库连接和释放数据库连接的方法:fetchConn 和 releaseConn
  3. fetchConn:在mills时间内还拿不到数据库连接,返回一个null.releaseConn:释放连接
  4. 测试类启动50个线程去取数据库连接并进行数据库操作,最后释放连接.每个线程取20次
  5. 记录拿到以及拿不到连接的次数

public class DBPool {

    /**
     * 数据库连接池的容器
     */
    private static final LinkedList<Connection> POOL = new LinkedList<>();

    public DBPool(int initSize) {
        if (initSize > 0) {
            for (int i = 0; i < initSize; i++) {
                POOL.addLast(SqlConnectImpl.fetchConnection());
            }
        }
    }

    /**
     * 在mills时间内还拿不到数据库连接,返回一个null
     *
     * @param mills
     * @return
     * @throws InterruptedException
     */
    public Connection fetchConn(long mills) throws InterruptedException {
        synchronized (POOL) {
            if (mills <= 0) {
                while (POOL.isEmpty()) {
                    POOL.wait();
                }
                return POOL.removeFirst();
            } else {
                long overtime = System.currentTimeMillis() + mills;
                long remain = mills;
                while (POOL.isEmpty() && remain > 0) {
                    POOL.wait(remain);
                    //到了这一步证明wait方法返回了,即拿到了POOL的对象锁(POOL不为空了)或者等待超时了(remain<0)
                    remain = overtime - System.currentTimeMillis();
                }
                Connection result = null;
                //连接池不为空,直接拿
                if (!POOL.isEmpty()) {
                    result = POOL.removeFirst();
                }
                return result;
            }
        }
    }

    /**
     * 放回数据库连接
     *
     * @param conn
     */
    public void releaseConn(Connection conn) {
        if (conn != null) {
            synchronized (POOL) {
                POOL.addLast(conn);
                POOL.notifyAll();
            }
        }
    }


}



public class SqlConnectImpl implements Connection{
	
    public static final Connection fetchConnection(){
        return new SqlConnectImpl();
    }

	@Override
	public void commit() throws SQLException {
		SleepTools.ms(70);
	}
	@Override
	public Statement createStatement() throws SQLException {
		SleepTools.ms(1);
		return null;
	}
}

public class DBPoolTest {
    private static DBPool pool  = new DBPool(10);
    /**
     * 控制器:控制main线程将会等待所有Woker结束后才能继续执行
     */
    private static CountDownLatch end;

    public static void main(String[] args) throws Exception {
    	// 线程数量
        int threadCount = 50;
        end = new CountDownLatch(threadCount);
        //每个线程的操作次数
        int count = 20;
        //计数器:统计可以拿到连接的线程
        AtomicInteger got = new AtomicInteger();
        //计数器:统计没有拿到连接的线程
        AtomicInteger notGot = new AtomicInteger();

        //此处由于是Demo就不用线程池了,生产环境建议还是使用线程池比较安全
        //启动50个线程去拿数据库连接
        for (int i = 0; i < threadCount; i++) {
            Thread thread = new Thread(new Worker(count, got, notGot), 
            		"worker_"+i);
            thread.start();
        }
        end.await();// main线程在此处等待
        System.out.println("总共尝试了: " + (threadCount * count));
        System.out.println("拿到连接的次数:  " + got);
        System.out.println("没能连接的次数: " + notGot);
    }

    static class Worker implements Runnable {
        int count;
        AtomicInteger got;
        AtomicInteger notGot;

        Worker(int count, AtomicInteger got, AtomicInteger notGot) {
            this.count = count;
            this.got = got;
            this.notGot = notGot;
        }

        @Override
        public void run() {
            while (count > 0) {
                try {
                    // 从线程池中获取连接,如果1000ms内无法获取到,将会返回null
                    // 分别统计连接获取的数量got和未获取到的数量notGot
                    Connection connection = pool.fetchConn(1000);
                    if (connection != null) {
                        try {
                            //此处为模拟createStatement和commit,没有实际的逻辑
                            connection.createStatement();
                            connection.commit();
                        } finally {
                            pool.releaseConn(connection);
                            got.incrementAndGet();
                        }
                    } else {
                        notGot.incrementAndGet();
                        System.out.println(Thread.currentThread().getName()
                        		+"等待超时!");
                    }
                } catch (Exception ignored) {
                } finally {
                    count--;
                }
            }
            end.countDown();
        }
    }
}