Как заставить поток Java закрыть соединение с локальной нитью

При использовании подключения к локальной сети с потоком требуется соединение с соединением, если существует поток.

Это я могу сделать, только если я могу переопределить метод run() вызывающего потока. Даже это не является отличным решением, так как во время выхода я не знаю, было ли соединение когда-либо открыто этим потоком.

Проблема на самом деле более общая: как заставить поток вызывать некоторый метод финализации потока-локального объекта при его выходе.

Я посмотрел на источники java 1.5 и обнаружил, что локальная карта потока установлена ​​в нуль, что в конечном итоге приведет к тому, что сбор мусора вызовет finalize(), но я не хочу рассчитывать на сборщик мусора.

Следующее переопределение кажется неизбежным, чтобы убедиться, что соединение с базой данных закрыто:

@Override 
public void remove() {
    get().release(); 
    super.remove(); 
}

где release() закрывает соединение с базой данных, если оно было открыто. Но мы не знаем, использовал ли поток этот поток-локальный. Если get() никогда не был вызван этим потоком, здесь здесь будет довольно тратить силы: Будет вызван ThreadLocal.initialValue(), в этом потоке будет создана карта и т.д.

-

Дальнейшее разъяснение и пример в соответствии с комментарием Торбьерна:

java.lang.ThreadLocal - это тип factory для объекта, связанного с потоком. Этот тип имеет getter для объекта и метод factory (обычно написанный пользователем). Когда геттер называется, он вызывает метод factory только в том случае, если этот поток никогда не вызывался этим потоком.

Использование ThreadLocal позволяет разработчику связывать ресурс с потоком, даже если код потока был написан третьей стороной.

Пример: Скажем, у нас есть тип ресурса, называемый MyType, и мы хотим иметь один и только один из них для каждого потока.

Определите в используемом классе:   private static ThreadLocal resourceFactory = new ThreadLocal() {       @Override       protected MyType initialValue() {           return new MyType();       }   }

Использовать в локальном контексте в этом классе:   public void someMethod() {       Ресурс MyType = resourceFactory.get();       resource.useResource();   }

get() может вызывать initialValue() только один раз в жизненном цикле вызывающего потока. В этот момент экземпляр MyType создается и привязывается к этому потоку. Последующие вызовы get() этим потоком снова обращаются к этому объекту.

Классический пример использования - это когда MyType - это неформатный текстовый формат/формат даты /xml.

Но такие форматы обычно не нужно выпускать или закрывать, подключения к базе данных делать, и я использую java.lang.ThreadLocal, чтобы иметь одно соединение с базой данных для каждого потока.

То, как я его вижу, java.lang.ThreadLocal почти идеально подходит для этого. Почти потому, что нет способа гарантировать закрытие ресурса, если вызывающий поток принадлежит стороннему приложению.

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

В случае нормального выхода потока сборщик мусора закрывает соединение (поскольку MyType переопределяет finalize()). На самом деле это происходит довольно быстро, но это не идеально.

Если бы у меня был свой путь, был бы другой метод в java.lang.ThreadLocal:

protected void release() throws Throwable {}

Если этот метод существовал на java.lang.ThreadLocal, вызванном JVM при любом выходе/смерти потока, то в моем собственном переопределении я мог закрыть мое соединение (и придет спаситель к Сиону).

В отсутствие такого метода я ищу другой способ подтверждения закрытия. Способ, который не будет полагаться на сборку мусора JVM.

Спасибо

Ответ 1

Если вы чувствительны, откиньтесь сейчас.

Я бы не ожидал, что это будет очень хорошо масштабироваться; он эффективно удваивает количество потоков в системе. Там могут быть некоторые варианты использования, где это приемлемо.

public class Estragon {
  public static class Vladimir {
    Vladimir() { System.out.println("Open"); }
    public void close() { System.out.println("Close");}
  }

  private static ThreadLocal<Vladimir> HOLDER = new ThreadLocal<Vladimir>() {
    @Override protected Vladimir initialValue() {
      return createResource();
    }
  };

  private static Vladimir createResource() {
    final Vladimir resource = new Vladimir();
    final Thread godot = Thread.currentThread();
    new Thread() {
      @Override public void run() {
        try {
          godot.join();
        } catch (InterruptedException e) {
          // thread dying; ignore
        } finally {
          resource.close();
        }
      }
    }.start();
    return resource;
  }

  public static Vladimir getResource() {
    return HOLDER.get();
  }
}

Лучшая обработка ошибок и т.д. остается как упражнение для исполнителя.

Вы также можете посмотреть отслеживание потоков/ресурсов в ConcurrentHashMap с другим опросом потока isAlive. Но это решение является последним средством отчаяния - объекты, вероятно, в конечном итоге будут проверяться слишком часто или слишком редко.

Я не могу думать ни о чем другом, что не связано с инструментами. АОП может работать.

Пул соединений будет моим предпочтительным вариантом.

Ответ 2

Оберните свой Runnable новым Runnable с помощью

try {
  wrappedRunnable.run();
} finally {
  doMandatoryStuff();
}

и пусть THAT будет выполняться вместо этого.

Вы даже можете сделать это в методе, например:

  Runnable closingRunnable(Runnable wrappedRunnable) {
    return new Runnable() {
      @Override
      public void run() {
        try {
          wrappedRunnable.run();
        } finally {
          doMandatoryStuff();
        }
      }
    };
  }

и вызовите этот метод, проходящий в исполняемом файле, который вы ищете.

Возможно, вы также захотите рассмотреть возможность использования Исполнителя. Это упрощает управление Runable и Callables.

Если вы используете ExecutorService, вы можете использовать его как executor.submit(closingRunnable(normalRunnable))

Если вы знаете, что вы отключите весь свой ExecutorService и хотите, чтобы соединения закрылись в этот момент, вы можете установить поток factory, который также закрывает "после выполнения всех задач и выключения, вызванных исполнителем", например:

  ExecutorService autoClosingThreadPool(int numThreads) {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // same as Executors.newFixedThreadPool
    threadPool.setThreadFactory(new ThreadFactory() {
      @Override
      public Thread newThread(Runnable r) {
        return new Thread(closingRunnable(r)); // closes it when executor is shutdown
      }
    });
    return threadPool;
  }

С точки зрения того, действительно лиMandatoryStuff может знать, было ли соединение открыто или нет ранее, одна вещь, которая приходит на ум, имеет второй ThreadLocal, который просто отслеживает, был ли он открыт или нет (например: когда соединение открыть, затем установить AtomicInteger на 2, во время очистки, проверить, не по-прежнему ли он по умолчанию, скажем, 1...)

Ответ 3

Обычная практика JDBC заключается в том, что вы закрываете ConnectionStatement и ResultSet) в том же блоке метода, который вы его приобрели.

В коде:

Connection connection = null;

try {
    connection = getConnectionSomehow();
    // Do stuff.
} finally {
    if (connection != null) {
        try {
            connection.close();
        } catch (SQLException e) {
            ignoreOrLogItButDontThrowIt(e);
        }
    }
}

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

Если ваше первоначальное намерение заключалось в улучшении производительности соединения, вам нужно взглянуть на объединение пулов. Вы можете использовать, например, C3P0 API. Или, если это веб-приложение, используйте сервер приложений, встроенный в пул объединения, в стиле DataSource. Подробнее см. В документации по конкретным приложениям.

Ответ 4

Я не понимаю, почему вы не используете традиционный пул соединений. Но я предполагаю, что у вас есть свои причины.

Сколько у вас свободы? Поскольку некоторые структуры DI поддерживают циклы жизненного цикла объекта и переменные с областью действия (все хорошо проксированные). Не могли бы вы использовать один из них? Я думаю, что Spring сделает это все из коробки, в то время как Guice понадобится сторонняя библиотека для обработки жизненных циклов и областей потоков.

Далее, какой контроль у вас есть при создании переменной ThreadLocal или создании потоков? Я предполагаю, что у вас есть полный контроль над ThreadLocal, но он не ограничен созданием потоков?

Можете ли вы использовать Aspect-ориентированное программирование для мониторинга новых Runnable или Threads, расширяющих метод run(), чтобы включить очистку? Вам также потребуется расширить ThreadLocal, чтобы он мог зарегистрироваться.

Ответ 5

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

Ответ 6

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

В конкретном случае, если вы вызываете потоки с этой степенью, вы можете передать соединение с вашими методами в начале потока либо с помощью метода, который принимает пользовательский параметр, либо через некоторую форму Injection Dependency. Затем, поскольку у вас есть код, который передает соединение, у вас есть код, который удаляет его.

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

Ответ 7

Переопределите метод get() в ThreaedLocal, чтобы он задал свойство List в подклассе. Это свойство можно легко запросить, чтобы определить, был ли метод get() вызван для определенного потока. Затем вы можете получить доступ к ThreadLocal, чтобы очистить его в этом случае.

Обновлено в ответ на комментарий

Ответ 8

То, что мы сделали, было

@Override
public void run() {
  try {
    // ...
  } finally {
    resource.close();
  }
}

В основном просто (возможно, открывать и затем) закрывать его для всех путей через поток. Если это кому-то помогает:)

Ответ 9

Я смотрю на ту же проблему. Похоже, что до сих пор вы должны использовать finalize(), хотя теперь это устарело. Поскольку задачи передаются какому-либо исполнителю, вы никогда не узнаете, когда поток точно выйдет, если только исполнитель не покажет вам это, что означает, что вы в какой-то степени можете контролировать исполнителя.

Например, если вы строите Executor путем расширения ThreadPoolExecutor, вы можете переопределить методы afterExecution() и terminated(), чтобы выполнить это. Первый для потока, выходящего ненормально, а второй для нормального.