资讯专栏INFORMATION COLUMN

wait、notify实战之实现连接池

fuchenxuan / 1174人阅读

摘要:如果允许,则创建一个新的连接并返回给线程。然后在归还连接成功之后,调用方法通知所有正在等待的线程可以继续获取连接了,但是继续获取连接时,还必须继续抢夺锁,只有占锁成功的线程,才能继续执行获取连接操作。

前言

在了解到wait、notify的使用方式之后,我们使用wait、notify来实现一个连接池。如果还有不清楚wait、notify使用的,请进入传送门:https://segmentfault.com/a/1190000019104391

连接池原理

首先我们先来了解下连接池的基本原理

线程调用连接池的方法来获取连接

连接池内部判断连接池是否足够,如果足够则直接返回一个连接给线程

连接不够的情况下,判断连接池中是否允许新建连接(比如连接数小于最大连接数)。如果允许,则创建一个新的连接并返回给线程。

如果不允许新建连接,则判断是否允许等待空闲的连接,如果不允许,则未拿到连接

如果允许等待,之后就判断等待是否超时。如果超时,也是未拿到连接,如果未超时,则继续等待,一直循环。

代码实现

首先我们有一个空实现的连接类Connection

@Data
static class Connection {
    private String connectionName;
    public Connection(String connectionName) {
        this.connectionName = connectionName;
    }
}

此处只为了测试,该类只有一个名字属性。另外使用了lombok来自动生成set、get方法
接下来就是连接池的基本实现

@Data
public class ConnectionPoolOfWaitNotify {

    private Integer capacity = 4;//连接池中的连接数
    LinkedList linkedList = new LinkedList<>(); //连接容器

    public ConnectionPoolOfWaitNotify() {
        IntStream.rangeClosed(1, capacity).forEach(i -> {//构造方法中初始化所有连接
            linkedList.addLast(new Connection("connection-" + i));
        });
    }

    //获取连接
    public Connection getConnectioin(long time) throws InterruptedException {
        synchronized (linkedList) {
            if (!linkedList.isEmpty()) {//如果存在,拿走第一个
                return linkedList.removeFirst();
            }

            if (time <= 0) {//等到拿到连接再返回
                while (linkedList.isEmpty()) {
                    linkedList.wait();
                }
                return linkedList.removeFirst();
            }

            long lastTime = System.currentTimeMillis() + time;
            long sleepTime = time;
            while (linkedList.isEmpty() && sleepTime > 0) {
                linkedList.wait(sleepTime);
                sleepTime = lastTime - System.currentTimeMillis();
            }

            if (!linkedList.isEmpty()) {
                return linkedList.removeFirst();
            } else {
                return null;
            }
        }
    }

    //归还连接
    public void revertConnection(Connection connection) {
        synchronized (linkedList) {
            linkedList.addLast(connection);
            linkedList.notifyAll();//归还连接后通知其他拿连接的线程
        }
    }
}

连接池中,主要有两个方法。一个是获取连接(可控制超时时间,超时时间小于等于0则永不超时),一个是归还连接。
主要的逻辑在获取连接的方法里面,当获取不到连接时,使用wait()方法来使得当前获取连接的线程进入等待状态。然后在归还连接成功之后,调用notifyAll()方法通知所有正在等待的线程可以继续获取连接了,但是继续获取连接时,还必须继续抢夺锁,只有占锁成功的线程,才能继续执行获取连接操作。

测试

之后执行以下测试代码

public static void main(String[] args) {
    int allNum = 100;
    AtomicInteger successNum = new AtomicInteger(0);
    ConnectionPoolOfWaitNotify connectionPoolOfWaitNotify = new ConnectionPoolOfWaitNotify();
    IntStream.rangeClosed(1, allNum).parallel().forEach(i -> {
        Connection connection = null;
        try {
            connection = connectionPoolOfWaitNotify.getConnectioin(100);
            if (null != connection) {
                successNum.addAndGet(1);
                System.out.println("线程" + i + "拿到连接" + connection.getConnectionName());
                Thread.sleep(200);
            } else {
                System.out.println("线程" + i + "没有拿到连接");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (null != connection) {
                connectionPoolOfWaitNotify.revertConnection(connection);
            }
        }
    });

    System.out.println("总共拿连接次数:" + allNum + ",拿到连接次数:" + successNum.get());
}

allNum为总共获取连接次数,successNum当中记录获取成功的次数。每次拿连接的超时时间为100毫秒,拿到连接后200的休眠(模拟业务处理)之后归还连接。而且在构造函数中直接初始化了所有的连接(读者可以考虑下如何做到按需来初始化的话)。运行后结果如下

线程23拿到连接connection-1
线程69没有拿到连接
线程10拿到连接connection-2
线程74拿到连接connection-4
线程25拿到连接connection-3
线程6没有拿到连接
线程70没有拿到连接
线程72没有拿到连接
线程71没有拿到连接
线程73没有拿到连接
线程75拿到连接connection-1
总共拿连接次数:100,拿到连接次数:25

100次只能拿到25次,那我们如果设置永不超时呢?调用方式如下,修改超时时间为0即可

connection = connectionPoolOfWaitNotify.getConnectioin(0);

之后再次运行结果如下

线程70拿到连接connection-1
线程58拿到连接connection-2
线程16拿到连接connection-3
线程60拿到连接connection-4
线程71拿到连接connection-1
线程59拿到连接connection-4
线程67拿到连接connection-3
线程68拿到连接connection-2
总共拿连接次数:100,拿到连接次数:100

OK,全部拿到,没毛病

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74496.html

相关文章

  • Java并发编程笔记(二)

    摘要:本文探讨并发中的其它问题线程安全可见性活跃性等等。当闭锁到达结束状态时,门打开并允许所有线程通过。在从返回时被叫醒时,线程被放入锁池,与其他线程竞争重新获得锁。 本文探讨Java并发中的其它问题:线程安全、可见性、活跃性等等。 在行文之前,我想先推荐以下两份资料,质量很高:极客学院-Java并发编程读书笔记-《Java并发编程实战》 线程安全 《Java并发编程实战》中提到了太多的术语...

    NickZhou 评论0 收藏0
  • Java并发总结

    摘要:限期阻塞调用方法等待时间结束或线程执行完毕。终止状态线程执行完毕或出现异常退了。和都会检查线程何时中断,并且在发现中断时提前放回。工厂方法将线程池的最大大小设置为,而将基本大小设置为,并将超时大小设置为分钟。 wait()、notify()、notifyAll() Object是所有类的基类,它有5个方法组成了等待、通知机制的核心:notify()、notifyAll()、wait()...

    szysky 评论0 收藏0
  • 【面试】Java相关

    摘要:可能会持有相同的值对象但键对象必须是唯一的。当有新任务到达时,线程池没有线程则创建线程处理,处理完成后该线程缓存秒,过期后回收,线程过期前有新任务到达时,则使用缓存的线程来处理。解决死锁问题的三种方法预防死锁检测死锁及避免死锁。 最近辞职准备面试,顺便整理一下面试题分享给大家,如有错误欢迎指出 01. 你对面向对象思想的理解? 面向对象编程简称OOP,是开发程序的一种方法、思想。面向...

    icattlecoder 评论0 收藏0
  • Javag工程师成神路(2019正式版)

    摘要:结构型模式适配器模式桥接模式装饰模式组合模式外观模式享元模式代理模式。行为型模式模版方法模式命令模式迭代器模式观察者模式中介者模式备忘录模式解释器模式模式状态模式策略模式职责链模式责任链模式访问者模式。 主要版本 更新时间 备注 v1.0 2015-08-01 首次发布 v1.1 2018-03-12 增加新技术知识、完善知识体系 v2.0 2019-02-19 结构...

    Olivia 评论0 收藏0
  • Java并发

    摘要:饥饿和公平一个线程因为时间全部被其他线程抢走而得不到运行时间,这种状态被称之为饥饿。线程需要同时持有对象和对象的锁,才能向线程发信号。现在两个线程都检查了这个条件为,然后它们都会继续进入第二个同步块中并设置为。 1、死锁 产生死锁的四个必要条件:(1) 互斥条件:一个资源每次只能被一个进程使用。(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。(3) 不剥夺条...

    venmos 评论0 收藏0

发表评论

0条评论

fuchenxuan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<