Как стать автором
Обновить

Комментарии 40

Рассматривали ли вы вариант AOP? Думаю, что для решения вашей задачи он очень хорошо подходит. Единственный момент — нужно будет использовать load-time weaving (так как 1) вызовы идут из скриптинга и 2) класс Thread относится к JRE). Советую почитать документацию к AspectJ — www.eclipse.org/aspectj/ довольно полезная штука.
нет, не рассматривал. Было бы великолепно если бы смогли привести альтернативный вариант реализации с использованием AOP! будет прям два решения в одном топике.
Попробовал сделать с использованием AspectJ, но оказалось, что он не поддерживает weaving для классов из Bootclassloader-а. Возможно, у других реализаций AOP таких проблем нет, но я пока с ними не знаком. Попробую потом на свежую голову.
Из того, что сделать аспектами — добавить поле (или пару — для обратной связи ) в класс Thread и инициализировать его в конструкторе (изменение конструктора + «использование не threadQ, а добавленного поля» — переложить на AOP). Ну и использовать CFLOW чтобы отслеживать только то, что вызывается из вашего скриптинга и не трогать другие части программы.
Сорри, если сумбурно изложил свои мысли — в сон уже клонит.
добавить поле не получится — redefineClasses будет ругаться, я в статье описал что: переопределение класса не должно добавлять, удалять новые поля или методы, менять сигнатуру методов или иерархию наследования, можно менять только тело методов, структура класса меняться не должна.

а можно поподробнее про CFLOW и контроль вызова классов в разных частях?

Этот механизм позволяет изменять код только для случаев, когда выполнение началось из определенной точки.
Например, мы имеем конфигурацию
    <cflow-stack name="mainConstructor">
        <called expr="threadtree.Main->new(..)" />
    </cflow-stack>

    <bind pointcut="execution(void threadtree.Main->init())" cflow="mainConstructor">
        <interceptor class="threadtree.aspects.MyInterceptor" />
    </bind>

public class Main {
    public Main() {
        init();
    }
    public void init() {
    }
    public static void main(String[] args) throws Exception {
        Main m = new Main(); 
        m.init();
    }
}

В результате наш «перехватчик» будет вызван только при вызове init() из конструктора, но не при прямом вызове. То есть механизм, позволяющий смотреть на стек вызова.
хочу поправиться — не рассматривалось решение на базе AspectJ, в статье приведен пример решения на базе библиотеки javassist, которая как раз таки и реализует AOP.
Согласен, javassist предоставляет низкоуровневые возможности для АОП, чем вы по-сути и пользуетесь в статье. Попытка решения с JBoss AOP тоже ни к чему не привела. Что и как там делалось я описал у себя в блоге «для истории» — можете взглянуть, если будет интересно (текста там немного, но в комментарий не уместилось).
А почему бы не сделать класс ManagedThread extends Thread c полем parentThread и конструкторами
ManagedThread(...) {
super(...);
this.parentThread = currentThread();
}

и далее разрешать из стороннего кода инстанциировать только его?

threadQ может в теле класса не использовать, но использоваться самой JVM в native коде

Да, имхо в этом случае не стоит этот самый threadQ трогать. Может, стоит воспользоваться каким-нить другим костылем, например каким-нить общим инстансом WeakHashMap?
тоже интересная мысль, т.е. можно сделать свой менеджер потоков синглтон, в который при создании нового потока пихать инфу про взаимосвязи.
хорошо заметили относительно threadQ — есть опасения про использование в native. Так ка информации я не нашел, придется проверять на «собственной шкуре».
в добавок не хотелось бы обрезать доступность класса стандартного Thread, так как на него завязано много других стандартных классов классов типа TimerThread и прочие.

если можно опишите детальнее как вы предполагали запретить инстанциирование стандартного Thread, возможно это пригодится в дальнейшем в других местах…
Что касается ThreadTimer, то он спокойно примет в качестве аргумента ManagedThread т.к. он расширяет Thread. Сам класс будет доступен, надо лишь запретить вызывать new Thread() (и, возможно, несколько методов из java.util.concurrent)

Запрет инстациирования делается через security manager (вот например: www.javaworld.com/javaworld/jw-11-1997/jw-11-hood.html). Именно через него сделан запред инстациирования класса Thread в GoogleAppEngine (и во всех остальных application container'ах он тоже используется, если надо запрещать что-то инстанциировать или к чему-то обращатся).
как же TimerThread примет ManagedThread если у него по умолчанию нет для этого интерфейса?
При запрете вызова new Thread() — я запрещу вызывать конструктор new TimerThread(TaskQueue taskqueue).

Относительно запрета инстациирования спасибо, так и предполагалось, даже исходники гугля просмотрел :), думал может еще есть какие альтернативные варианты.

ну т.е. запретив вызывать new Thread() — автоматически заблокируются все вызовы этого конструктора из всех стандартных классов, т.о. много стандартных классов будут нерабочими.
Ну, например все классы из java.util.concurrent опционально принимают ThreadFactory. Да, видимо классы типа Timer и прочие нужно будет тоже запретить.

Что, в принципе, нормально. Потому что я не могу представить ситуацию, когда какому-нибудь бизнес-приложению в application container'е на скриптовом языке надо активно оперерировать тредами.
интересно дать максимальную свободу действий, ну а запретить всегда можно ;).
Тоже сразу такая же мысль возникла. Пост читал с неким недоумением, хотя там и дисклеймер про бубен есть =)
А ведь можно по-другому.
Есть такой класс — InheritableThreadLocal. У него есть метод childValue(), который вызывается в родительской нити, до того как дочерняя нить стартует. Ясно, что таким образом можно сделать так, чтобы дети знали про своих родителей. PROFIT!!!

НЛО прилетело и опубликовало эту надпись здесь
Ну да, ребёнок станет отцом своего ребёнка.
НЛО прилетело и опубликовало эту надпись здесь
Да, так не получится… Хотя, если ребёнок хотя бы теоретически дёргает какой-либо системный (контролируемый нами) метод, то там можно это перехватывать…

примерчик приведите, не понял ход мысли…
Хотел как-то про это даже пост написать, но решил, что никому не интересно.
Очень рад, что могу с кем-то поделиться ноу-хау. Ловите:

public class ThreadingTest {
  private static InheritableThreadLocal<Thread> threadTracker = new InheritableThreadLocal<Thread>() {

    @Override
    protected Thread childValue(Thread parentValue) {
      return Thread.currentThread();
    }
  };
  
  
  public static void main(String[] args)
  {
    new Thread("parent")
    {

      @Override
      public void run() {
        System.out.println("parent: my parent is " + threadTracker.get() );
        new Thread("child")
        {
          @Override
          public void run() {
            System.out.println("child: my parent is " + threadTracker.get() );
            
            new Thread("grand_child")
            {
              @Override
              public void run() {
                System.out.println("grand_child: my parent is " + threadTracker.get() );
              }
            }.start();
          }
        }.start();
      }
      
    }.start();
  }
}

* This source code was highlighted with Source Code Highlighter.
Красиво?
щас проверим, выглядит очень привлекательно.
решение хорошее, но не универсальное.

вопрос вот в чем, получается что я могу получить ссылку на поток-родитель только изнутри ребенка, а в моем конкретном случае надо иметь связи извне, т.е. контроль производит внешний класс, который перебирает потоки и хочет знать кто родитель каждого потока.
Это правда.

Вот что можно ещё сделать: сказать пользователям, что нити можно запускать только через какой-нибудь MyTrackingManager.createThread(). Ну фреймворк такой, лучше чем ничего.
А отлеживать, что пользователи не запустят нить откуда-либо ещё можно через этот самый childValue(), так как вызывается он из родительской нити (и у Ваc будет стек-трейс вызова).
да, можно и так. но тогда будет запрет использовать многие стандартные классы, в коментах немного выше обсуждается такой вариант решения.
НЛО прилетело и опубликовало эту надпись здесь
запрет это крайняя мера присечения. проблемы всегда были и будут. были проблемы куда похлеще рассмотренной, но все они постепенно решились.

> Например, некоторые действия порожденной нити вызвали появление глобального пула объектов, пополняемого уже нитью этого пула. И эта нить будет включена в иерархию вместо того, что бы быть обособленной.

честно говоря недогнал суть проблемы, может примерчиком объясните?
НЛО прилетело и опубликовало эту надпись здесь
да, но пул конектов к БД как раз таки всегда инициализируется до сервиса скриптинга, сами скрипты лежат в БД, т.е. запуск скрипа на выполнение уже предполагает наличие инициализированного пула. да и в целом система построена так, что пул конектов к БД всегда инициализируется раньше каких либо пользовательских скриптов.

Действительно, в системе, предполагается принудительное завершение дочерних потоков, которые не уложись в таймаут, все что породили пользовательские скрипты умрет после порога ожидания.

Возможно, выплывут какие-то дополнительные подводные камни, о которых пока не известно. Переключить галочку на запрет всегда успеется. Но, на данный момент на горизонте нет препятствий.
НЛО прилетело и опубликовало эту надпись здесь
считаю это достойным вариантом решения, как альтернатива в случае всплывания конкретных граблей с низким уровнем.
Насколько я понял исходная задача — иметь возможность привязать все потоки порожденные приложением к этому приложению.
Для этого проще всего использовать ThreadGroup.
Создайте корневой поток приложения в отдельном ThreadGroup. Все пороженные им потоки будут добавлены в этот ThreadGroup. Если же поток создаст свой ThreadGroup то он также будет добавлен в родительский ThreadGroup, и обходя дерево групп можно перечислить все потоки приложения, ну или обратная задача — имея конкретный поток сказать какой группе он принадлежит.

основной поток создается веб сервером, нет возможности управлять его группой. как вариант из основного потока создавать новый поток в отдельной группе и из под него уже запускать скриптинг. Спасибо за наводку, буду думать в эту сторону тоже.
Нифига не понял.
Во-первых, вместо javaagent лучше исправить java.lang.Thread и запустить java с bootclasspath.

Во-вторых, если задача — контролировать код внутри jvm, нужно создать свой sandbox, написать для него свой ClassLoader, установить SecurityManager и выполнять код внутри на подобие как это делают аплеты.

Что касается тредов, то посмотрите внимательно конструктор java.sun.com/j2se/1.5.0/docs/api/java/lang/Thread.html#Thread(java.lang.ThreadGroup, java.lang.Runnable, java.lang.String)
Там в явную идет security проверка на access к текущему threadGroup. При помощи этого можно контролировать возможность создания тредов.
1) чем лучше? думаю в случае в javaagent целостность решения лучше, все происходит в одном месте, приложение само под себя настраивает VM
2) а по другому наверно и нет возможности, только писать свой SecurityManager

задача стоит так: нужно не запретить, а иметь возможность держать контроль над потоками
спасибо еще раз за наводку на access к текущему threadGroup. Она пригодилась.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации