Pull to refresh

«Танцы с бубном» вокруг Thread

Reading time3 min
Views7.4K

При разработке cloud платформы веб-приложений был реализован сервис серверной логики на базе java scripting технологии для более гибкого управления другими сервисами платформы.

Соответственно «стал ребром» вопрос контроля над порождением, жизнью и использованием ресурсов платформы дочерними потоками создаваемыми из скриптинга. Потоки через скриптинг могут создаваться всеми доступными способами. В GoogleAppEngine проблему дочерних потоков решили простым запретом их порождения. В нашем случае хочется иметь более гибкое решение. Поэтому необходимо иметь контроль над дочерными потоками создаваемыми из главного потока запроса.

Изначально предполагалось что задача тривиальна и что в Java есть стандартные для этого средства. Но ожидания не оправдались.

Стандартные средства в Java не позволяют этого сделать?! Странно, но почему-то в классе java.lang.Thread отсутствует связь или ссылка с родительским потоком, который породил текущий поток. Долго искав информацию в инете про варианты реализации получения ссылки на родительский поток (или на список дочерних) было найдено ровно ноль решений.

Лезем в исходники java.lang.Thread… После анализа стало ясно что действительно никаких взаимосвязей родитель-потомок в классе нет. Что делать? естественно дорабатывать то чего не хватает.

Решение


Для решения задачи выбран следующий путь: JDK6 позволяет «на лету» подключить javaagent. После подключения javaagent переопределяем класс Thread, добавляя к нему ссылку на родителя. Однако есть ограничения: переопределение класса не должно добавлять, удалять новые поля или методы, менять сигнатуру методов или иерархию наследования. Можно менять только тело методов, структура класса меняться не должна.
Т.е. нам надо ссылку на родительский поток вложить в уже ранее определенное в исходном классе поле! Смотрим в java.lang.Thread — и о чудо! внутри есть поле private Thread threadQ; которое нигде не используется в теле класса. Хм, как будто разработчики Java для нас оставили это поле :).

Решение в коде


//модифицируем Thread.class
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get("java.lang.Thread");

for (CtMethod ctMethod : ctClass.getDeclaredMethods()) {
 if (ctMethod.getName().equals("init")) {
   //дорабатываем метод init, сохраняем ссылку на текущий поток (родительский)
   ctMethod.insertBefore("threadQ = currentThread();");
   break;
 }
}

//загружаем javaagent
String agentPath = "/path/javaageent.jar";

VirtualMachineDescriptor vmd = ...;
VirtualMachine vm = VirtualMachine.attach(vmd);

vm.loadAgent(agentPath);

//переопределяем Thread.class
ClassDefinition classDef = new ClassDefinition(Thread.class, ctClass.toBytecode());
Agent.redefineClasses(classDef);

//получаем ссылку на поле класса
Field parentThreadField = Thread.class.getDeclaredField("threadQ");
parentThreadField.setAccessible(true);

//создадим 10 дочених потоков
for (int i = 0; i < 10; i++) {
 final int n = i;
 new Thread(new Runnable() {
  public void run() {
   try {
    Thread.sleep(10000);
    System.out.println("thred #" + n);
   } catch (Exception ex) {
   }
  }
 }).start();
}

Thread currentThread = Thread.currentThread();
ThreadGroup threadGroup = currentThread.getThreadGroup();
Thread[] threads = new Thread[threadGroup.activeCount()];
threadGroup.enumerate(threads);
for (Thread t : threads) {
  //достаем родительский поток используя parentThreadField.get(t)
  if (currentThread.equals(parentThreadField.get(t))) {
    System.out.println(t);
  }
}


* This source code was highlighted with Source Code Highlighter.


На решение с использованием javaagent и reflection натолкнула статься Java Instrumentation with JDK 1.6.X, Class Re-definition after loading. Там же исходный код класса Agent, который используется в примере.

P.S.: В глубине сознания где-то таится мысль что есть какой-то простой способ решить описанную проблему и она настолько тривиальная что про нее никто ничего не пишет в инете.

UPD1: Есть опасения что переменная thredQ используется в native коде JVM, тогда альтернативным вариантом может служить создание своего менеджера потоков (singleton), в который при создании нового потока пихать инфу про взаимосвязи (нужно также переопределять метод init класса Thread).

UPD2: apple_fan привел очень интересное решение, за что ему спасибо. смотрим комментарий ниже про ограничения указанного решения.

UPD3: amosk подсказал еще один вариант решения, на мой взгляд наиболее рациональный для решения моей частной задачи.

UPD4: Throwable предлагает свой вариант решения, я думаю очень даже отличный вариант, по такому пути предполагалось идти изначально.
Tags:
Hubs:
+20
Comments40

Articles