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

StackOverflow https://stackoverflow.com/questions/1700212

Вопрос

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

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

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

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

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

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

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

-

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

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

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

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

Определите в классе использования:частный статический ThreadLocal resourceFactory = new ThreadLocal(){ @override защищенный MyType initialValue(){ возвращает новый MyType();} }

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

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

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

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

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

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

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

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

protected void release() throws Throwable {}

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

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

Спасибо

Это было полезно?

Решение

Если у вас чувствительный характер, отведите сейчас взгляд.

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

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 с другим опросом потока жив.Но это решение — последнее средство для отчаявшихся: объекты, вероятно, будут проверяться слишком часто или слишком редко.

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

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

Другие советы

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

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

строительство, и пусть вместо этого выполняется ЭТО.

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

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

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

Вместо этого вы также можете рассмотреть возможность использования Executor.Значительно упрощает управление Runable и Callables.

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

Если вы знаете, что закроете всю службу ExecutorService и хотите, чтобы соединения закрылись в этот момент, вы можете установить фабрику потоков, которая также выполняет закрытие «после того, как все задачи выполнены и завершение работы вызвано на исполнителе», пример:

  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;
  }

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

Обычная практика 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.Подробности смотрите в документации по конкретному серверу приложений.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top