Ключом к решению вашей проблемы является то, что вам нужен один набор блокировок для MyStructure
экземпляра.Ваш аспект, однако, одиночен.Поэтому вам нужно либо использовать другую схему создания экземпляров аспектов (именно это я и буду использовать в своем ответе), либо вести ручную бухгалтерию в рамках одноэлементного аспекта, сохраняя набор блокировок и добавляя новый элемент в этот набор всякий раз, когда объект MyStructure
создан.
Чтобы лучше понять мой ответ, пожалуйста, обратитесь к руководству AspectJ за информацией о создании экземпляров .
Прежде чем мы начнем, несколько комментариев относительно вашего кодаи почему я немного его изменил:
- Ваш
Writer
уже является подклассом Thread
, нет необходимости заключать его в другой экземпляр потока.(Я знаю, что вы, вероятно, просто сделали это для того, чтобы иметь возможность именовать поток, но этого можно было бы достичь, добавив в ваш класс конструктор, принимающий аргумент name и передавший его конструктору суперкласса.) - Вы не должны вызывать переменную типа
JoinPoint
pointcut
, потому что точка соединения не является точечной с точки зрения AOP. - Я учел логирование в его собственном вспомогательном методе и немного его улучшил, чтобы мы могли видетьболее ясно, что происходит, когда.
- Я решил заменить каждую пару до и после совета на общий совет.Это необязательно, конечно, но я предпочитаю видеть поток управления в одном месте в этом случае.Кстати, будьте осторожны, чтобы изменить тип возвращаемого значения для советника на
Object
и на самом деле вернуть что-то, если вы хотите использовать целевые методы.Здесь это не является необходимым, потому что в обоих случаях у нас есть пустые методы. - Я также решил встроить pointcuts, что также необязательно, но делает пример кода несколько более кратким для демонстрационных целей.
- Я добавил класс
Reader
и использую его для того, чтобы показать разницу между повторным входящим чтением и блокировками записи. - Я также позаботился о том, чтобы
MyStructure
экземпляров можно было именовать и печатать, чтобы определить цельобъекты в журнале легче. - Я рандомизировал порядок выполнения потоков чтения / записи, чтобы смешивать их в более реальном мире.Чтобы избежать исключений, загрязняющих журнал при чтении из только что созданного
MyStructure
перед записью в него, я убедился, что MyStructure
получает элемент по умолчанию прямо в конструкторе.Я не хотел перехватывать здесь исключения для упрощения примера кода. - Я поместил аспект в другой пакет, чем код приложения, чтобы продемонстрировать, чем обычно нужно использовать полные имена классов, когдаиспользование AspectJ в стиле аннотаций (в собственном синтаксисе будет достаточно импорта).
Теперь, каково решение?В основном именно это, потому что упомянутые выше изменения только улучшают код или тестовую программу ближе к реальным ситуациям:
@Aspect("pertarget(execution(de.scrum_master.app.MyStructure.new(..)))")
public class LocksAspect { // (...)
Это создает один экземпляр аспекта для MyStructure
объекта.Именно поэтому мы можем назначить значения readWriteLock
, readLock
и writeLock
напрямую, а не использовать специальную пару pointcut + advice, как в вашем одноэлементном аспекте.
Вот полный рефакторизованный примеркод:
Код приложения + приложение драйвера:
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();
}
}
Формат:
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
);
}
}
Пример журнала вывода:
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