001/*
002 * HA-JDBC: High-Availability JDBC
003 * Copyright (c) 2004-2007 Paul Ferraro
004 * 
005 * This library is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Lesser General Public License as published by the 
007 * Free Software Foundation; either version 2.1 of the License, or (at your 
008 * option) any later version.
009 * 
010 * This library is distributed in the hope that it will be useful, but WITHOUT
011 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
012 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Lesser General Public License
016 * along with this library; if not, write to the Free Software Foundation, 
017 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018 * 
019 * Contact: ferraro@users.sourceforge.net
020 */
021package net.sf.hajdbc.util.concurrent;
022
023import java.util.concurrent.Semaphore;
024import java.util.concurrent.TimeUnit;
025import java.util.concurrent.locks.Condition;
026import java.util.concurrent.locks.Lock;
027import java.util.concurrent.locks.ReadWriteLock;
028
029/**
030 * Simple {@link java.util.concurrent.locks.ReadWriteLock} implementation that uses a semaphore.
031 * A read lock requires 1 permit, while a write lock requires all the permits.
032 * Lock upgrading and downgrading is not supported; nor are conditions.
033 * 
034 * @author Paul Ferraro
035 */
036public class SemaphoreReadWriteLock implements ReadWriteLock
037{
038        private static final int DEFAULT_PERMITS = Integer.MAX_VALUE;
039        
040        private final Lock readLock;
041        private final Lock writeLock;
042        
043        public SemaphoreReadWriteLock(boolean fair)
044        {
045                this(DEFAULT_PERMITS, fair);
046        }
047
048        public SemaphoreReadWriteLock(int permits, boolean fair)
049        {
050                Semaphore semaphore = new Semaphore(permits, fair);
051                
052                this.readLock = new SemaphoreLock(semaphore);
053                this.writeLock = new SemaphoreWriteLock(semaphore, permits);
054        }
055        
056        /**
057         * @see java.util.concurrent.locks.ReadWriteLock#readLock()
058         */
059        @Override
060        public Lock readLock()
061        {
062                return this.readLock;
063        }
064
065        /**
066         * @see java.util.concurrent.locks.ReadWriteLock#writeLock()
067         */
068        @Override
069        public Lock writeLock()
070        {
071                return this.writeLock;
072        }
073        
074        private static class SemaphoreWriteLock implements Lock
075        {
076                private final Semaphore semaphore;
077                private final int permits;
078                
079                SemaphoreWriteLock(Semaphore semaphore, int permits)
080                {
081                        this.semaphore = semaphore;
082                        this.permits = permits;
083                }
084                
085                /**
086                 * Helps avoid write lock starvation, when using an unfair acquisition policy by draining all available permits.
087                 * @return the number of drained permits
088                 */
089                private int drainPermits()
090                {
091                        return this.semaphore.isFair() ? 0 : this.semaphore.drainPermits();
092                }
093                
094                /**
095                 * @see java.util.concurrent.locks.Lock#lock()
096                 */
097                @Override
098                public void lock()
099                {
100                        int drained = this.drainPermits();
101                        
102                        if (drained < this.permits)
103                        {
104                                this.semaphore.acquireUninterruptibly(this.permits - drained);
105                        }
106                }
107
108                /**
109                 * @see java.util.concurrent.locks.Lock#lockInterruptibly()
110                 */
111                @Override
112                public void lockInterruptibly() throws InterruptedException
113                {
114                        int drained = this.drainPermits();
115                        
116                        if (drained < this.permits)
117                        {
118                                try
119                                {
120                                        this.semaphore.acquire(this.permits - drained);
121                                }
122                                catch (InterruptedException e)
123                                {
124                                        if (drained > 0)
125                                        {
126                                                this.semaphore.release(drained);
127                                        }
128                                        
129                                        throw e;
130                                }
131                        }
132                }
133
134                /**
135                 * @see java.util.concurrent.locks.Lock#tryLock()
136                 */
137                @Override
138                public boolean tryLock()
139                {
140                        // This will barge the fairness queue, so there's no need to drain permits
141                        return this.semaphore.tryAcquire(this.permits);
142                }
143
144                /**
145                 * @see java.util.concurrent.locks.Lock#tryLock(long, java.util.concurrent.TimeUnit)
146                 */
147                @Override
148                public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException
149                {
150                        int drained = this.drainPermits();
151                        
152                        if (drained == this.permits) return true;
153                        
154                        boolean acquired = false;
155                        
156                        try
157                        {
158                                acquired = this.semaphore.tryAcquire(this.permits - drained, timeout, unit);
159                        }
160                        finally
161                        {
162                                if (!acquired && (drained > 0))
163                                {
164                                        this.semaphore.release(drained);
165                                }
166                        }
167                        
168                        return acquired;
169                }
170
171                /**
172                 * @see java.util.concurrent.locks.Lock#unlock()
173                 */
174                @Override
175                public void unlock()
176                {
177                        this.semaphore.release(this.permits);
178                }
179                
180                /**
181                 * @see java.util.concurrent.locks.Lock#newCondition()
182                 */
183                @Override
184                public Condition newCondition()
185                {
186                        throw new UnsupportedOperationException();
187                }
188        }
189}