ReentrantReadWriteLock with AspectJ pointcut for every initialized type of MyStructure

500 Views Asked by At

I am struggling to create a ReentrantReadWriteLock with AspectJ for every single object that is constructed and is a type of Mystructure. Here is my source code.

The aspect class

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Aspect
public class LocksAspect {
    private ReentrantReadWriteLock rwLock;
    private Lock acquireReadLock;
    private Lock acquireWriteLock;

    @Before("!within(LocksAspect)&&execution(*.new(..))")
    public void LookupBefores() {
        rwLock = new ReentrantReadWriteLock();
        acquireReadLock = rwLock.readLock();
        acquireWriteLock = rwLock.writeLock();
    }

    @Pointcut("call(void MyStructure.Insert(String))")
    public void InsertPointcut() {
    }

    @Pointcut("call(void MyStructure.Read(int))")
    public void ReadPointcut() {
    }

    @Before("InsertPointcut()")
    public void InsertPointcutBefore(JoinPoint pointcut) throws InterruptedException {
        acquireWriteLock.lock();
        String thrdName = Thread.currentThread().getName();
        System.out.println(thrdName + "  is entering in critical Section {} ");
        Thread.sleep(10000);
    }


    @After("InsertPointcut()")
    public void InsertPointcutAfter(JoinPoint pointcut) {
        String thrdName = Thread.currentThread().getName();
        System.out.println(thrdName + " received notification and is exiting critical Section {} ");
        acquireWriteLock.unlock();
    }

    @Before("ReadPointcut()")
    public void ReadPointcutBefore(JoinPoint pointcut) throws InterruptedException {
        acquireReadLock.lock();
        String thrdName = Thread.currentThread().getName();
        System.out.println(thrdName + "  is entering in critical Section {} ");
        Thread.sleep(1000);
    }


    @After("ReadPointcut()")
    public void ReadPointcutAfter(JoinPoint pointcut) {
        String thrdName = Thread.currentThread().getName();
        System.out.println(thrdName + " received notification and is exiting critical Section {} ");
        acquireReadLock.unlock();
    }
}

The Thread writer class.(The Reader thread class is not important because my problem is different so i omitted it)

public class Writer extends Thread{
   private MyStructure myStructure;
    public Writer(MyStructure myStructure) {
        this.myStructure=myStructure;
    }

    @Override
    public void run() {
        this.myStructure.Insert("example");
    }
}

My structure class

import java.util.ArrayList;

public class MyStructure {
    ArrayList<String> examplelist;

    public MyStructure() {
        examplelist = new ArrayList<String>();
    }

    public void Insert(String value) {
        examplelist.add(value);
    }

    public void Read(int pos) {
        examplelist.get(pos);
    }
}

The main

MyStructure structure = new MyStructure();
        MyStructure structure1 = new MyStructure();
        new Thread(new Writer(structure), "Thread1").start();
        new Thread(new Writer(structure1), "Thread2").start();

The output

Thread2  is entering in critical Section {} 
Thread2 received notification and is exiting critical Section {} 
Thread1  is entering in critical Section {} //Thread1 will wait for Thread2 to release the lock in critical section   which is wrong
Thread1 received notification and is exiting critical Section {} 

Now my problem is How I will get a new ReentrantReadWriteLock for each object of Mystructure that created. For example, if we run the above example both Thread1 and Thread2 must be able to access the critical section because they have different references of the object, but this should not have happened. My problem is that the Thread2 will block and wait for Thread1 to finish which is wrong. How can I bypass this problem of construction with Aspect4j?

1

There are 1 best solutions below

5
On BEST ANSWER

The key to your problem's solution is that you need one set of locks per MyStructure instance. Your aspect is a singleton, though. So either you need to use another aspect instantiation scheme (which is what I will use in my answer) or do manual bookkeeping within the singleton aspect by keeping a set of locks and add a new element into that set whenever a MyStructure object is created.

In order to better understand my answer, please refer to the AspectJ manual for information about aspect instantiation.

Before we start, a few comments concerning your code and why I have changed it a bit:

  • Your Writer is already a Thread subclass, no need to wrap it into another thread instance. (I know you probably just did it for being able to name the thread, but that could have been achieved by adding a constructor in your class taking the name argument and passing it through to the super class constructor.)
  • You should not call a variable of type JoinPoint pointcut because a joinpoint is not a pointcut AOP-wise.
  • I factored out the logging into its own helper method and improved it a bit so we can see more clearly what happens when.
  • I decided to replace each pair of before and after advice by an around advice. This is optional, of course, but I prefer to see the control flow in one place in this case. BTW, be careful to change the around advice's return type to Object and actually return something if you want to target non-void methods. Here it is not necessary because in both cases we have void methods.
  • I also decided to inline the pointcuts, which is also optional, but makes the sample code somewhat more concise for demonstration purposes here.
  • I added a Reader class and use it in order to show the difference between reentrant read vs write locks.
  • I also took care of making MyStructure instances nameable and printable in order to identify the target objects more easily in the log.
  • I randomised the execution order of reader/writer threads so as to mix them in a more real-world fashion. In order to avoid exceptions polluting the log when reading from a newly created MyStructure before writing into, I made sure that MyStructure gets a default element right in the constructor. I did not want to catch exceptions here to in order to keep the sample code simple.
  • I put the aspect in another package than the application code in order to demonstrate than generally you need to use fully qualified class names when using annotation-style AspectJ (in native syntax imports would be enough).

Now what is the solution? Basically just this, because the changes mentioned above just make the code better or the test program closer to real-life situations:

@Aspect("pertarget(execution(de.scrum_master.app.MyStructure.new(..)))")
public class LocksAspect { // (...)

This creates one aspect instance per MyStructure object. This is also why we can assign the values of readWriteLock, readLock and writeLock directly instead of using a special pointcut + advice pair like in your singleton aspect.

Here is the full, refactored sample code:

Application code + driver application:

package de.scrum_master.app;

import java.util.ArrayList;
import java.util.List;

public class MyStructure {
  private String name;
  private List<String> myList;

  public MyStructure(String name) {
    this.name = name;
    myList = new ArrayList<String>();
    myList.add("dummy element to permit reading");
  }

  public void insert(String value) {
    myList.add(value);
  }

  public void read(int pos) {
    myList.get(pos);
  }

  @Override
  public String toString() {
    return "MyStructure[" + name + "]";
  }
}
package de.scrum_master.app;

public class Writer extends Thread {
  private MyStructure myStructure;

  public Writer(MyStructure myStructure) {
    this.myStructure = myStructure;
  }

  @Override
  public void run() {
    myStructure.insert("example");
  }
}
package de.scrum_master.app;

public class Reader extends Thread {
  private MyStructure myStructure;

  public Reader(MyStructure myStructure) {
    this.myStructure = myStructure;
  }

  @Override
  public void run() {
    myStructure.read(0);
  }
}
package de.scrum_master.app;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class Application {
  public static void main(String[] args) {
    MyStructure structureA = new MyStructure("One");
    MyStructure structureB = new MyStructure("Two");
    List<Thread> threads = Arrays.asList(
      new Writer(structureA), new Writer(structureB), new Writer(structureA), new Writer(structureB),
      new Reader(structureA), new Reader(structureB), new Reader(structureA), new Reader(structureB),
      new Reader(structureA), new Reader(structureB), new Reader(structureA), new Reader(structureB)
    );
    Collections.shuffle(threads);
    for (Thread thread : threads)
      thread.start();
  }
}

Aspect:

package de.scrum_master.aspect;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import de.scrum_master.app.MyStructure;

@Aspect("pertarget(execution(de.scrum_master.app.MyStructure.new(..)))")
public class LocksAspect {
  private static final long startTime = System.currentTimeMillis();

  private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  private Lock readLock = readWriteLock.readLock();
  private Lock writeLock = readWriteLock.writeLock();

  @Around("target(myStructure) && execution(void insert(String))")
  public void InsertPointcutBefore(ProceedingJoinPoint thisJoinPoint, MyStructure myStructure) throws Throwable {
    writeLock.lock();
    log("entering write section", myStructure);
    try {
      Thread.sleep(1000);
      thisJoinPoint.proceed();
    } finally {
      log("exiting write section", myStructure);
      writeLock.unlock();
    }
  }

  @Around("target(myStructure) && execution(void read(int))")
  public void ReadPointcutBefore(ProceedingJoinPoint thisJoinPoint, MyStructure myStructure) throws Throwable {
    readLock.lock();
    log("entering read section", myStructure);
    try {
      Thread.sleep(1000);
      thisJoinPoint.proceed();
    } finally {
      log("exiting read section", myStructure);
      readLock.unlock();
    }
  }

  private static void log(String message, Object targetObject)  {
    System.out.printf(
      "%8d ms | %-25s | %-17s | %s%n",
      System.currentTimeMillis() - startTime,
      Thread.currentThread(),
      targetObject,
      message
    );
  }
}

Sample log output:

       4 ms | Thread[Thread-3,5,main]   | MyStructure[Two]  | entering write section
       4 ms | Thread[Thread-6,5,main]   | MyStructure[One]  | entering read section
       4 ms | Thread[Thread-8,5,main]   | MyStructure[One]  | entering read section
       4 ms | Thread[Thread-4,5,main]   | MyStructure[One]  | entering read section
       4 ms | Thread[Thread-10,5,main]  | MyStructure[One]  | entering read section
    1019 ms | Thread[Thread-3,5,main]   | MyStructure[Two]  | exiting write section
    1020 ms | Thread[Thread-8,5,main]   | MyStructure[One]  | exiting read section
    1020 ms | Thread[Thread-4,5,main]   | MyStructure[One]  | exiting read section
    1020 ms | Thread[Thread-11,5,main]  | MyStructure[Two]  | entering read section
    1020 ms | Thread[Thread-5,5,main]   | MyStructure[Two]  | entering read section
    1020 ms | Thread[Thread-6,5,main]   | MyStructure[One]  | exiting read section
    1020 ms | Thread[Thread-10,5,main]  | MyStructure[One]  | exiting read section
    1025 ms | Thread[Thread-2,5,main]   | MyStructure[One]  | entering write section
    2023 ms | Thread[Thread-11,5,main]  | MyStructure[Two]  | exiting read section
    2024 ms | Thread[Thread-5,5,main]   | MyStructure[Two]  | exiting read section
    2025 ms | Thread[Thread-1,5,main]   | MyStructure[Two]  | entering write section
    2026 ms | Thread[Thread-2,5,main]   | MyStructure[One]  | exiting write section
    2026 ms | Thread[Thread-0,5,main]   | MyStructure[One]  | entering write section
    3026 ms | Thread[Thread-1,5,main]   | MyStructure[Two]  | exiting write section
    3026 ms | Thread[Thread-7,5,main]   | MyStructure[Two]  | entering read section
    3026 ms | Thread[Thread-9,5,main]   | MyStructure[Two]  | entering read section
    3028 ms | Thread[Thread-0,5,main]   | MyStructure[One]  | exiting write section
    4028 ms | Thread[Thread-7,5,main]   | MyStructure[Two]  | exiting read section
    4029 ms | Thread[Thread-9,5,main]   | MyStructure[Two]  | exiting read section