Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java synchronization: Lock without blocking

I have a synchronization exercise where i need to synchronize a read method so that any number of threads can execute it as long as no write methods are being executed. This has to be done from scratch, so i cannot use java.util.concurrent.locks ect.

For this i need some sort of mechanism to guard, but not block the read method, so the reading thread is blocked by a write, but not by other reads. I can't use a normal lock for this because invoking the lock method in the read method would cause other read threads to wait.

The rules should be as such: When a thread is inside write(), no other threads must enter read() or write() When a thread is inside read(), no other threads must enter write(), but they may enter read()

I have tried building a couple of homemade locks to deal with this problem. WriteLock is a fairly standard reenterant lock, except that it blocks if read is being executed (Using the readcounter) ReadLock should only cause threads to wait, if write() is being traversed. Otherwise it should simply allow the thread to go about its business and increment WriteLocks counter.

Code:

package sync;

public class SyncTest {
    Long testlong = new Long(0L);
    int reads = 0;
    int writes = 0;
    WriteLock w = new WriteLock();
    ReadLock r = new ReadLock(w);

    public SyncTest() {
        // TODO Auto-generated constructor stub
    }

    public static void main(String args[]){

        final SyncTest s = new SyncTest();

        for(int i = 0 ; i<3 ; i++){ //Start a number of threads to attack SyncTest
            final int ifinal = i;
            new Thread(){
                int inc = ifinal;
                @Override
                public void run() {
                    System.out.println("Starting "+inc);
                    long starttime = System.currentTimeMillis();
                    try {
                    while(System.currentTimeMillis()-starttime < 10){

                        if (inc < 2){

                            s.readLong();

                        }else{
                            s.writeLong(inc+1);
                        }
                    }
                    System.out.println(inc + " done");
                    if(inc == 0){
                        Thread.sleep(1000);
                        System.out.println(s.reads+" "+s.writes);
                    }
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }

                    // TODO Auto-generated method stub

                }
                @Override
                public String toString() {
                    // TODO Auto-generated method stub
                    return "Thread "+inc+" "+super.toString();
                }



            }.start();
        }
    }

    public Long readLong() throws InterruptedException{

        Long l;
        r.lock(); //Lock for reading
        //System.out.println("Read "+reads);
        l =  testlong;
        reads++;
        r.unlock(); //Unlock after reading
        return l;   
        }

    public void writeLong(int i) throws InterruptedException{

        w.lock(); //Lock for writing
        //System.out.println("Write "+writes);
        int curreads = reads;
        int curwrites = writes;
        testlong = testlong + i;
        writes++;

        Thread.sleep(100); //Simulate long write
        if(curreads != reads){
            System.out.println("Reads did not lock");
        }

        if(curwrites+1 != writes){
            System.out.println("Writes did not lock");
        }
        w.unlock(); //Unlock writing
    }

    protected class WriteLock{
        boolean isLocked = false;
        Thread lockedBy = null;
        int lockedCount = 0;
        int readers = 0; //The number of readers currently through the reading lock.

        public synchronized void lock() throws InterruptedException {
            System.out.println("Locking: "+Thread.currentThread());
            Thread callingThread = Thread.currentThread();
            while ((isLocked && lockedBy != callingThread) || readers > 0) { //Wait if locked or readers are in read()
                wait();
            }
            isLocked = true;
            lockedCount++;
            lockedBy = callingThread;
            System.out.println("Is locked: "+Thread.currentThread());
        }

        public synchronized void unlock() {
            //System.out.println("Unlocking: "+Thread.currentThread());
            if (Thread.currentThread() == this.lockedBy) {
                lockedCount--;

                if (lockedCount == 0) {
                    System.out.println("Is unlocked: "+Thread.currentThread());
                    isLocked = false;
                    notify();
                }
            }
        }

    }

    protected class ReadLock{
        WriteLock lock;

        public ReadLock(WriteLock lock) {
            super();
            this.lock = lock;
        }

        public synchronized void lock() throws InterruptedException { //If write() then wait
            System.out.println("Waiting to read: "+Thread.currentThread());
            Thread callingThread = Thread.currentThread();
            while (lock.isLocked && lock.lockedBy != callingThread) {
                wait();
            }
            lock.readers++; //Increment writelocks readers
            System.out.println("Reading: "+Thread.currentThread());

        }

        public synchronized void unlock() {
            lock.readers--; //Subtract from writelocks readers
            notify();
        }

    }

}

This is not working however, the reading lock works so far that it locks readers when a thread is writing, but it doesn't release them when WriteLock unlocks, as far as i can tell.

Is this just not conceptually sound, or is there something i don't understand with monitors? Or something else?

like image 538
Martin Nielsen Avatar asked Dec 05 '22 12:12

Martin Nielsen


1 Answers

(Answered before the question was edited in terms of it being an exercise.)

It sounds like you want a ReadWriteLock implementation.

A ReadWriteLock maintains a pair of associated locks, one for read-only operations and one for writing. The read lock may be held simultaneously by multiple reader threads, so long as there are no writers. The write lock is exclusive.

One implementation is ReentrantReadWriteLock.

For concurrency in particular, it's always worth looking in the existing libraries (java.util.concurrent et al) before you try to implement your own code. If you're anything like me, even if you can make it correct in terms of concurrency, it won't be as efficient as the code written by experts... and of course it's all work to start with ;)

like image 135
Jon Skeet Avatar answered Dec 28 '22 18:12

Jon Skeet