Author: oheger
Date: Mon May 13 20:24:12 2013
New Revision: 1482082

URL: http://svn.apache.org/r1482082
Log:
Added ReadWriteSynchronizer class.

This is an implementation of the Synchronizer interfaced based on the
java.util.concurrent.locks.ReentrantReadWriteLock. It can be used to make
configurations thread-safe if they are accessed concurrently.

Added:
    
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/sync/ReadWriteSynchronizer.java
    
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/
    
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/TestReadWriteSynchronizer.java

Added: 
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/sync/ReadWriteSynchronizer.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/sync/ReadWriteSynchronizer.java?rev=1482082&view=auto
==============================================================================
--- 
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/sync/ReadWriteSynchronizer.java
 (added)
+++ 
commons/proper/configuration/trunk/src/main/java/org/apache/commons/configuration/sync/ReadWriteSynchronizer.java
 Mon May 13 20:24:12 2013
@@ -0,0 +1,99 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.sync;
+
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * <p>
+ * A special implementation of {@code Synchronizer} based on the JDK's
+ * {@code ReentrantReadWriteLock} class.
+ * </p>
+ * <p>
+ * This class manages a {@code ReadWriteLock} object internally. The methods of
+ * the {@code Synchronizer} interface are delegated to this lock. So this class
+ * behaves in the same way as documented for {@code ReentrantReadWriteLock}.
+ * </p>
+ * <p>
+ * Using this {@code Synchronizer} implementation is appropriate to make
+ * configuration objects thread-safe. This means that multiple threads can read
+ * configuration data in parallel; if one thread wants to update the
+ * configuration, this happens with an exclusive lock.
+ * </p>
+ *
+ * @version $Id: $
+ * @since 2.0
+ */
+public class ReadWriteSynchronizer implements Synchronizer
+{
+    /** The lock object used by this Synchronizer. */
+    private final ReadWriteLock lock;
+
+    /**
+     * Creates a new instance of {@code ReadWriteSynchronizer} and initializes
+     * it with the given lock object. This constructor can be used to pass a
+     * lock object which has been configured externally. If the lock object is
+     * <b>null</b>, a default lock object is created.
+     *
+     * @param l the lock object to be used (can be <b>null</b>)
+     */
+    public ReadWriteSynchronizer(ReadWriteLock l)
+    {
+        lock = (l != null) ? l : createDefaultLock();
+    }
+
+    /**
+     * Creates a new instance of {@code ReadWriteSynchronizer} and initializes
+     * it with a lock object of type {@code ReentrantReadWriteLock}.
+     */
+    public ReadWriteSynchronizer()
+    {
+        this(null);
+    }
+
+    public void beginRead()
+    {
+        lock.readLock().lock();
+    }
+
+    public void endRead()
+    {
+        lock.readLock().unlock();
+    }
+
+    public void beginWrite()
+    {
+        lock.writeLock().lock();
+    }
+
+    public void endWrite()
+    {
+        lock.writeLock().unlock();
+    }
+
+    /**
+     * Returns a new default lock object which is used if no lock is passed to
+     * the constructor.
+     *
+     * @return the new default lock object
+     */
+    private static ReadWriteLock createDefaultLock()
+    {
+        return new ReentrantReadWriteLock();
+    }
+}

Added: 
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/TestReadWriteSynchronizer.java
URL: 
http://svn.apache.org/viewvc/commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/TestReadWriteSynchronizer.java?rev=1482082&view=auto
==============================================================================
--- 
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/TestReadWriteSynchronizer.java
 (added)
+++ 
commons/proper/configuration/trunk/src/test/java/org/apache/commons/configuration/sync/TestReadWriteSynchronizer.java
 Mon May 13 20:24:12 2013
@@ -0,0 +1,298 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration.sync;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.Random;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+/**
+ * Test class for {@code ReadWriteSynchronizer}.
+ *
+ * @version $Id: $
+ */
+public class TestReadWriteSynchronizer
+{
+    /** Constant for the total amount of money in the system. */
+    private static final long TOTAL_MONEY = 1000000L;
+
+    /**
+     * Tests whether a lock passed to the constructor is used.
+     */
+    @Test
+    public void testInitLock()
+    {
+        ReadWriteLock lock = EasyMock.createMock(ReadWriteLock.class);
+        Lock readLock = EasyMock.createMock(Lock.class);
+        EasyMock.expect(lock.readLock()).andReturn(readLock);
+        readLock.lock();
+        EasyMock.replay(lock, readLock);
+        ReadWriteSynchronizer sync = new ReadWriteSynchronizer(lock);
+        sync.beginRead();
+        EasyMock.verify(lock, readLock);
+    }
+
+    /**
+     * Tests whether the synchronizer is reentrant. This is important for some
+     * combined operations on a configuration.
+     */
+    @Test
+    public void testReentrance()
+    {
+        Synchronizer sync = new ReadWriteSynchronizer();
+        sync.beginWrite();
+        sync.beginRead();
+        sync.beginRead();
+        sync.endRead();
+        sync.endRead();
+        sync.beginWrite();
+        sync.endWrite();
+        sync.endWrite();
+    }
+
+    /**
+     * Performs a test of the synchronizer based on the classic example of
+     * account objects. Money is transferred between two accounts. If 
everything
+     * goes well, the total amount of money stays constant over time.
+     */
+    @Test
+    public void testSynchronizerInAction() throws InterruptedException
+    {
+        final int numberOfUpdates = 10000;
+        final int numberOfReads = numberOfUpdates / 2;
+        final int readThreadCount = 3;
+        final int updateThreadCount = 2;
+
+        Synchronizer sync = new ReadWriteSynchronizer();
+        Account account1 = new Account();
+        Account account2 = new Account();
+        account1.change(TOTAL_MONEY / 2);
+        account2.change(TOTAL_MONEY / 2);
+
+        UpdateThread[] updateThreads = new UpdateThread[updateThreadCount];
+        for (int i = 0; i < updateThreads.length; i++)
+        {
+            updateThreads[i] =
+                    new UpdateThread(sync, numberOfUpdates, account1, 
account2);
+            updateThreads[i].start();
+        }
+        ReaderThread[] readerThreads = new ReaderThread[readThreadCount];
+        for (int i = 0; i < readerThreads.length; i++)
+        {
+            readerThreads[i] =
+                    new ReaderThread(sync, numberOfReads, account1, account2);
+            readerThreads[i].start();
+        }
+
+        for (UpdateThread t : updateThreads)
+        {
+            t.join();
+        }
+        for (ReaderThread t : readerThreads)
+        {
+            t.join();
+            assertEquals("Got read errors", 0, t.getErrors());
+        }
+        sync.beginRead();
+        assertEquals("Wrong sum of money", TOTAL_MONEY,
+                sumUpAccounts(account1, account2));
+        sync.endRead();
+    }
+
+    /**
+     * Helper method to calculate the sum over all accounts.
+     *
+     * @param accounts the accounts to check
+     * @return the sum of the money on these accounts
+     */
+    private static long sumUpAccounts(Account... accounts)
+    {
+        long sum = 0;
+        for (Account acc : accounts)
+        {
+            sum += acc.getAmount();
+        }
+        return sum;
+    }
+
+    /**
+     * A class representing an account.
+     */
+    private static class Account
+    {
+        /** The amount stored in this account. */
+        private long amount;
+
+        /**
+         * Returns the amount of money stored in this account.
+         *
+         * @return the amount
+         */
+        public long getAmount()
+        {
+            return amount;
+        }
+
+        /**
+         * Changes the amount of money by the given delata.
+         *
+         * @param delta the delta
+         */
+        public void change(long delta)
+        {
+            amount += delta;
+        }
+    }
+
+    /**
+     * A thread which performs a number of read operations on the bank's
+     * accounts and checks whether the amount of money is consistent.
+     */
+    private static class ReaderThread extends Thread
+    {
+        /** The acounts to monitor. */
+        private final Account[] accounts;
+
+        /** The synchronizer object. */
+        private final Synchronizer sync;
+
+        /** The number of read operations. */
+        private final int numberOfReads;
+
+        /** Stores errors detected on read operations. */
+        private volatile int errors;
+
+        /**
+         * Creates a new instance of {@code ReaderThread}.
+         *
+         * @param s the synchronizer to be used
+         * @param readCount the number of read operations
+         * @param accs the accounts to monitor
+         */
+        public ReaderThread(Synchronizer s, int readCount, Account... accs)
+        {
+            accounts = accs;
+            sync = s;
+            numberOfReads = readCount;
+        }
+
+        /**
+         * Performs the given number of read operations.
+         */
+        @Override
+        public void run()
+        {
+            for (int i = 0; i < numberOfReads; i++)
+            {
+                sync.beginRead();
+                long sum = sumUpAccounts(accounts);
+                sync.endRead();
+                if (sum != TOTAL_MONEY)
+                {
+                    errors++;
+                }
+            }
+        }
+
+        /**
+         * Returns the number of errors occurred during read operations.
+         *
+         * @return the number of errors
+         */
+        public int getErrors()
+        {
+            return errors;
+        }
+    }
+
+    /**
+     * A test thread for updating account objects. This thread executes a 
number
+     * of transactions on two accounts. Each transaction determines the account
+     * containing more money. Then a random number of money is transferred from
+     * this account to the other one.
+     */
+    private static class UpdateThread extends Thread
+    {
+        /** The synchronizer. */
+        private final Synchronizer sync;
+
+        /** Account 1. */
+        private final Account account1;
+
+        /** Account 2. */
+        private final Account account2;
+
+        /** An object for creating random numbers. */
+        private final Random random;
+
+        /** The number of transactions. */
+        private final int numberOfUpdates;
+
+        /**
+         * Creates a new instance of {@code UpdateThread}.
+         *
+         * @param s the synchronizer
+         * @param updateCount the number of updates
+         * @param ac1 account 1
+         * @param ac2 account 2
+         */
+        public UpdateThread(Synchronizer s, int updateCount, Account ac1,
+                Account ac2)
+        {
+            sync = s;
+            account1 = ac1;
+            account2 = ac2;
+            numberOfUpdates = updateCount;
+            random = new Random();
+        }
+
+        /**
+         * Performs the given number of update transactions.
+         */
+        @Override
+        public void run()
+        {
+            for (int i = 0; i < numberOfUpdates; i++)
+            {
+                sync.beginWrite();
+                Account acSource;
+                Account acDest;
+                if (account1.getAmount() < account2.getAmount())
+                {
+                    acSource = account1;
+                    acDest = account2;
+                }
+                else
+                {
+                    acSource = account2;
+                    acDest = account1;
+                }
+                long x =
+                        Math.round(random.nextDouble()
+                                * (acSource.getAmount() - 1)) + 1;
+                acSource.change(-x);
+                acDest.change(x);
+                sync.endWrite();
+            }
+        }
+    }
+}


Reply via email to