@@ -25,7 +25,7 @@ public class ConnectionPool {
2525 private int minConnections ;
2626 private long timeoutMs ;
2727 private PrintWriter logWriter ;
28- private Semaphore semaphore ; //controls total connections
28+ private final AtomicInteger totalConnections = new AtomicInteger ( 0 ) ; // Lock-free connection counting
2929 private PoolConnectionEventListener poolConnectionEventListener ;
3030
3131 // Health monitoring and validation
@@ -47,6 +47,10 @@ public class ConnectionPool {
4747 private volatile PooledConnection connectionInTransition ; // a PooledConnection which is currently within a PooledConnection.getConnection() call, or null
4848 private Database database ;
4949
50+ // Validation caching for performance optimization
51+ private final ConcurrentHashMap <PooledConnection , Long > validationCache = new ConcurrentHashMap <>();
52+ private static final long VALIDATION_CACHE_TTL = 30000 ; // 30 seconds
53+
5054
5155 /**
5256 * Thrown in {@link #getConnection()} or {@link #getValidConnection()} when no free connection becomes
@@ -145,8 +149,8 @@ public ConnectionPool(ConnectionPoolDataSource dataSource, int maxConnections,
145149 this .validationQuery = validationQuery ;
146150 this .validationTimeout = validationTimeout ;
147151
148- // Initialize semaphore to control total connections
149- this .semaphore = new Semaphore ( maxConnections , true );
152+ // Initialize atomic counter for lock-free connection management
153+ this .totalConnections . set ( 0 );
150154
151155 try { logWriter = dataSource .getLogWriter (); }
152156 catch (SQLException e ) {}
@@ -287,24 +291,27 @@ private java.sql.Connection acquireConnection(long timeoutMs) throws SQLExceptio
287291 }
288292
289293 // No recycled connection available, try to create a new one
290- // Use semaphore to atomically control connection limit
291- try {
292- if (semaphore .tryAcquire (100 , TimeUnit .MILLISECONDS )) {
294+ // Use atomic counter for lock-free connection limit control
295+ int currentTotal = totalConnections .get ();
296+ if (currentTotal < maxConnections ) {
297+ if (totalConnections .compareAndSet (currentTotal , currentTotal + 1 )) {
293298 try {
294299 java .sql .Connection conn = createNewConnectionInternal ();
295300 if (conn != null ) {
296301 return conn ;
297302 } else {
298- // Connection creation failed, release the permit
299- semaphore . release ();
303+ // Connection creation failed, decrement counter
304+ totalConnections . decrementAndGet ();
300305 }
301306 } catch (SQLException e ) {
302- // Connection creation failed, release the permit
303- semaphore . release ();
307+ // Connection creation failed, decrement counter
308+ totalConnections . decrementAndGet ();
304309 // Continue to next iteration to try again
305310 }
306311 }
307- // If we couldn't acquire a permit, wait a bit and try again
312+ }
313+ // If we couldn't create a connection, wait a bit and try again
314+ try {
308315 Thread .sleep (10 );
309316 } catch (InterruptedException e ) {
310317 Thread .currentThread ().interrupt ();
@@ -323,10 +330,10 @@ private java.sql.Connection tryGetRecycledConnection() throws SQLException {
323330
324331 PooledConnection pconn = wrapper .connection ;
325332
326- // Smart validation: skip validation for recently used connections if no validation query is configured
327- boolean needsValidation = ( validationQuery != null && ! validationQuery . trim (). isEmpty () ) ||
328- wrapper .isExpired ( connectionMaxAgeMs ) ||
329- wrapper . isIdle ( connectionIdleTimeoutMs );
333+ // Smart validation: skip validation for recently used connections
334+ boolean needsValidation = wrapper . isExpired ( connectionMaxAgeMs ) ||
335+ wrapper .isIdle ( connectionIdleTimeoutMs ) ||
336+ ! isRecentlyValidated ( pconn );
330337
331338 if (needsValidation && !validateConnection (pconn )) {
332339 // Connection is invalid, dispose it properly
@@ -389,6 +396,7 @@ private java.sql.Connection createNewConnectionInternal() throws SQLException {
389396 connectionInTransition = pconn ;
390397 conn = pconn .getConnection ();
391398 activeConnections .incrementAndGet ();
399+ // totalConnections was already incremented in acquireConnection
392400 return conn ;
393401 } catch (SQLException e ) {
394402 connectionInTransition = null ;
@@ -718,11 +726,15 @@ private void performHealthCheck() {
718726 while (currentRecycled < minConnections && total < maxConnections && !isDisposed .get () && attempts < maxAttempts ) {
719727 attempts ++;
720728
721- // Try to acquire a semaphore permit for the warm-up connection
722- if (!semaphore .tryAcquire ()) {
723- log ("No semaphore permits available for pool warm-up" );
729+ // Try to increment totalConnections for the warm-up connection
730+ int currentTotal = totalConnections .get ();
731+ if (currentTotal >= maxConnections ) {
732+ log ("Maximum connections reached for pool warm-up" );
724733 break ;
725734 }
735+ if (!totalConnections .compareAndSet (currentTotal , currentTotal + 1 )) {
736+ continue ; // Try again if CAS failed
737+ }
726738
727739 PooledConnection pconn = null ;
728740 try {
@@ -737,16 +749,16 @@ private void performHealthCheck() {
737749 total = currentActive + currentRecycled ;
738750 log ("Pool warm-up: added connection " + currentRecycled + "/" + minConnections );
739751 } else {
740- // If validation fails, dispose the connection and release the permit
752+ // If validation fails, dispose the connection and decrement counter
741753 disposeConnection (pconn );
742754 pconn = null ; // Ensure pconn is null so finally block doesn't try to close it again
743755 }
744756 } catch (SQLException e ) {
745757 log ("Failed to create or validate connection during warm-up: " + e .getMessage ());
746758 if (pconn != null ) {
747- disposeConnection (pconn ); // Dispose and release permit
759+ disposeConnection (pconn ); // Dispose and decrement counter
748760 } else {
749- semaphore . release (); // Release permit if connection creation failed before pconn was assigned
761+ totalConnections . decrementAndGet (); // Decrement counter if connection creation failed before pconn was assigned
750762 }
751763 }
752764 }
@@ -764,11 +776,33 @@ private void performHealthCheck() {
764776 * Validates a connection using the configured validation query.
765777 * This method is completely isolated from the pool lifecycle to prevent race conditions.
766778 */
779+ private boolean isRecentlyValidated (PooledConnection pooledConnection ) {
780+ if (validationQuery == null || validationQuery .trim ().isEmpty ()) {
781+ return true ; // No validation query configured, consider it valid
782+ }
783+
784+ Long lastValidated = validationCache .get (pooledConnection );
785+ if (lastValidated == null ) {
786+ return false ; // Never validated
787+ }
788+
789+ long now = System .currentTimeMillis ();
790+ return (now - lastValidated ) < VALIDATION_CACHE_TTL ;
791+ }
792+
767793 private boolean validateConnection (PooledConnection pooledConnection ) {
768794 if (validationQuery == null || validationQuery .trim ().isEmpty ()) {
769795 return true ;
770796 }
771797
798+ // Check validation cache first
799+ Long lastValidated = validationCache .get (pooledConnection );
800+ long now = System .currentTimeMillis ();
801+
802+ if (lastValidated != null && (now - lastValidated ) < VALIDATION_CACHE_TTL ) {
803+ return true ; // Recently validated, skip actual validation
804+ }
805+
772806 try {
773807 // Temporarily remove the event listener to prevent validation from triggering pool events
774808 pooledConnection .removeConnectionEventListener (poolConnectionEventListener );
@@ -785,7 +819,12 @@ private boolean validateConnection(PooledConnection pooledConnection) {
785819 try (java .sql .PreparedStatement stmt = tempConn .prepareStatement (validationQuery )) {
786820 stmt .setQueryTimeout (validationTimeout );
787821 try (java .sql .ResultSet rs = stmt .executeQuery ()) {
788- return rs .next ();
822+ boolean isValid = rs .next ();
823+ if (isValid ) {
824+ // Cache successful validation
825+ validationCache .put (pooledConnection , now );
826+ }
827+ return isValid ;
789828 }
790829 }
791830 // Note: We don't close tempConn here because it belongs to the PooledConnection
@@ -858,8 +897,9 @@ private void recycleConnection (PooledConnection pconn) {
858897 private void disposeConnection (PooledConnection pconn ) {
859898 pconn .removeConnectionEventListener (poolConnectionEventListener );
860899
861- // Remove from wrapper map
900+ // Remove from wrapper map and validation cache
862901 connectionWrappers .remove (pconn );
902+ validationCache .remove (pconn );
863903
864904 // Try to remove from recycled connections
865905 boolean foundInRecycled = false ;
@@ -884,10 +924,10 @@ private void disposeConnection (PooledConnection pconn) {
884924 // This can happen with validation connections or connections that failed during creation
885925 }
886926
887- // Only release a semaphore permit when disposing a connection (not recycling)
927+ // Only decrement totalConnections when disposing a connection (not recycling)
888928 // This ensures that the total connection count is properly managed
889929 if (!foundInRecycled ) {
890- semaphore . release ();
930+ totalConnections . decrementAndGet ();
891931 }
892932
893933 closeConnectionAndIgnoreException (pconn );
@@ -906,7 +946,7 @@ private void log (String msg) {
906946 String s = "ConnectionPool: " +msg ;
907947 try {
908948 if (logWriter == null ) {
909- // System.err.println(s);
949+ System .err .println (s );
910950 }
911951 else {
912952 logWriter .println (s );
@@ -918,7 +958,7 @@ private void log (String msg) {
918958 private void assertInnerState () {
919959 int active = activeConnections .get ();
920960 int recycled = recycledConnections .size ();
921- int total = active + recycled ;
961+ int total = totalConnections . get () ;
922962
923963 if (active < 0 ) {
924964 throw new AssertionError ("Active connections count is negative: " + active );
@@ -984,8 +1024,8 @@ PooledConnectionWrapper markUsed() {
9841024 public PoolStatistics getPoolStatistics () {
9851025 int active = activeConnections .get ();
9861026 int recycled = recycledConnections .size ();
987- int total = active + recycled ;
988- int available = semaphore . availablePermits () ;
1027+ int total = totalConnections . get () ;
1028+ int available = maxConnections - total ;
9891029
9901030 return new PoolStatistics (
9911031 active ,
0 commit comments