Протекающие потоки Java Thread.sleep?

Так что я унаследовал часть кода, ожидающего связи с сетевым источником.

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

Wed Jan 18 09:14:40 PST 2012
2012-01-18 09:14:50
Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.0-b11 mixed mode):

"Thread-69" daemon prio=10 tid=0x00007f01a047c800 nid=0x3725 waiting on condition [0x00007f019eaf4000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.unitt.framework.websocket.simple.NetworkSocket.run(NetworkSocket.java:304)
        at java.lang.Thread.run(Thread.java:662)

"Thread-68" daemon prio=10 tid=0x00007f01a0500000 nid=0x371c waiting on condition [0x00007f019ecf6000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at com.unitt.framework.websocket.simple.NetworkSocket.run(NetworkSocket.java:304)
        at java.lang.Thread.run(Thread.java:662)

Рассматриваемый код:

public class NetworkSocket implements NetworkSocketFacade, Runnable
{

... removed many irrelevant methods

public void run()
{
    byte[] readBuffer = new byte[512 * 1024];
    while (isRunning)
    {
        //ioLogger.debug("in while(isRunning) loop");
        try
        {
            int length = input.available();
            if (length > 0)
            {
                int read = input.read(readBuffer, 0, readBuffer.length);

                if (read < 0)
                {
                    isRunning = false;
                    //@todo: do we disconnect?
                    ioLogger.debug("setting isRunning FALSE after read < 0");
                }
                else
                {
                   //read data and process
                }
            }
            else
            {
                //ioLogger.debug("nothing to read, sleeping");
                try
                {
                    Thread.sleep( 10 );
                }
                catch ( InterruptedException e )
                {
                    //do nothing, keep going
                }
            }
        }
    // some catch blocks and logging after this

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

Есть ли у кого-нибудь понимание этого поведения? Я бы не подумал, что что-то столь же простое, как Thread.sleep(), вызовет такие проблемы.


person AWT    schedule 18.01.2012    source источник
comment
Какова предполагаемая механика такой утечки потока? Thread.sleep() никогда не вернется?   -  person NPE    schedule 18.01.2012
comment
Я не был уверен, как еще это назвать, когда я смотрю на количество потоков jconsole, оно поднимается под углом 30 градусов в течение 18 часов. Постоянно создаются новые потоки, а все старые застревают в месте ожидания по условию, которое я указал выше.   -  person AWT    schedule 18.01.2012


Ответы (4)


Проблема не в Thread.sleep(), а в логике потока.

Из кода, который вы разместили, поток завершится, когда isRunning = false. Теперь единственный способ установить isRunning в false — это когда input.available() возвращает положительное значение, а затем input.read() возвращает отрицательное значение.

Не похоже, чтобы было какое-либо состояние мира, когда это имело бы место.

В результате все потоки, использующие этот метод run(), будут жить столько же, сколько живет процесс, проводя большую часть своего времени в Thread.sleep().

P.S. Это основано на коде, который вы опубликовали. Если существуют существующие способы установки isRunning на false, которые вы сейчас не показываете, обновите свой вопрос.

person NPE    schedule 18.01.2012
comment
Привет @aix, спасибо за помощь. Просматривая код, нет другого способа установить для isRunning значение false, единственная ссылка на isRunning вне этого кода — это объявление. Проверяем, является ли это дефектом конструкции или просто недостатком. - person AWT; 18.01.2012
comment
Оказалось, что проблема была именно в этом. По неопытности с Java я интерпретировал результаты своей трассировки стека, чтобы думать, что Thread.sleep() зависает, хотя на самом деле у меня было много потоков, которые бездействовали на неопределенный срок. Я добавил некоторую проверку ошибок, дал потоку время жизни в ожидании данных на сетевом порту, и проблема исчезла. Спасибо всем за ответы. - person AWT; 19.01.2012

Thread.sleep() точно не проблема. Он не создает никаких потоков или чего-то подобного.

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

Кстати, вместо того, чтобы постоянно вызывать available и засыпать, поток может просто заблокироваться на input.read(). Код был бы намного проще и отзывчивее.

person Tomasz Nurkiewicz    schedule 18.01.2012
comment
Я просматриваю код сейчас. Я предполагаю, что меня поразило то, что все потоки застряли в Thread.sleep(). - person AWT; 18.01.2012
comment
+1. Я предполагаю, что available всегда возвращает 0 по той или иной причине (например, сброс соединения) и что поток бесконечно зацикливается вместо того, чтобы читать и получать IOException. доступный просто ненадежен, и его не следует использовать. - person JB Nizet; 18.01.2012

Thread.sleep() ничего не "форкает" и не может приниматься во внимание при поиске утечки потока...

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

person Grooveek    schedule 18.01.2012
comment
Хорошая точка зрения. Я думаю, что нашел кое-что интересное, дайте мне разобраться, а затем я опубликую это здесь. - person AWT; 18.01.2012

Распространенной ошибкой является забывание сделать isRunning логическое значение volatile Без этого ключевого слова вы можете изменить его в одном потоке, и нет никакой гарантии, что другой поток увидит это изменение. Таким образом, вы можете установить для isRunning значение false, но поток продолжает работать.

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

public void close() throws IOException {
    closed = true;
    input.close();
}

public void run() {
  byte[] readBuffer = new byte[512 * 1024];
  try {
     // you wouldn't keep looping after an exception.
     int len;
     while ((len = input.read(readBuffer)) > 0) {
           //read data and process
     }
  } catch (IOException ioe) {
     if (!closed)
        // log unexpected exception
  }
}

Чем проще вы это сделаете, тем больше шансов, что это сработает. ;)

person Peter Lawrey    schedule 18.01.2012