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