2009-11-23 2 views
0

Я пишу класс (Foo), который при создании экземпляра может быть вызван из нескольких потоков.java не обнаруживает других потоков в объекте

Большинство методов класса Foo можно безопасно вызывать несколькими потоками параллельно. Один из методов этого класса (logout()) требует, чтобы все остальные потоки выполнялись.

Прежде чем выйти из системы, ссылка на foo удаляется из поточно-безопасной коллекции. Таким образом, ни один новый поток не получит ссылку на объект Foo. Тем не менее, могут существовать существующие потоки, которые работают с ссылками на объект Foo, который должен быть выведен из системы.

У меня может быть счетчик, который увеличивается каждый раз, когда поток входит в объект и уменьшается каждый раз, когда нить уходит. В logout() я мог бы вращаться во время (counter! = 0);

Но я думаю, что есть, вероятно, более четкий способ/шаблон, чтобы сделать это. Ищите мудрость сообщества stackoverflow здесь.

ответ

5

Если вы просто хотите, метод выхода из системы(), чтобы блокировать, пока все нити сделаны, вы можете использовать Object.wait() и Object.notify() методов, например:

public class Foo { 

    private long threadCount; 

    public synchronized void enter() { 
     threadCount++; 
    } 

    public synchronized void exit() { 
     threadCount--; 
     notifyAll(); 
    } 

    public synchronized void doStuff() { 
     ... 
    } 

    public synchronized void logout() { 
     while(threadCount > 0) { 
      try { 
       wait(); 
      } catch (InterruptedException e) { 
       ... 
      } 
     } 
    } 

} 

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

Редактирование: только что поняли, что вам понадобятся отдельные методы для ввода/выхода потока.

+0

Мне это нравится. Это то, что я искал. Спасибо, Джаред. – rouble

+0

Но что, если есть более 2^31-1 потоков, обращающихся к объекту? ;) –

1

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

+0

нити не умирают после того, как они сделаны с использованием Foo. Они болтаются, чтобы больше работать, они больше не будут использовать выписанный экземпляр Foo. – rouble

+0

Кажется, что вы можете искать синхронизацию или что-то вроде механизмов блокировки, доступных через java.util.concurrent. Я не полностью понимаю ваш случай использования, чтобы дать лучший ответ. –

0

Прежде всего, не заняты ожидание (вращение на while (counter != 0)), так как это просто съедает циклы процессора.

Если ваше требование является то, что ни один из потоков не могут назвать logout(), пока все потоки не выполняется с помощью Foo, то вы могли бы рассмотреть возможность добавления checkout() и done() методы, так что потоки могут уведомить свой Foo объект, когда они начинают работать на Foo и когда они полны. Затем вы можете проверить logout(), чтобы убедиться, что нитки не «проверили» Foo и еще не завершили свою работу. Однако это шаткий дизайн.

Но если ваше требование состоит в том, что только один поток может позвонить по номеру logout() (либо по одному, либо по одному потоку, который можно когда-либо назвать), тогда используйте ключевое слово .

+0

мое требование - первое. То есть вызов logout() должен блокироваться до тех пор, пока все потоки не выйдут из объекта. В модели checkout() done(), что вы предлагаете мне сделать в logout()? Что-то вроде этого: while (checkedOut()) { Thread.sleep (1000); } // Начните с выхода. – rouble

+0

, если checkout() отслеживает потоки, которые его называют, тогда вы можете просто отключить logout() .join() каждый из этих потоков, как предлагает Тейлор. –

+0

join() не помогает мне, потому что эти потоки не умирают. Они продолжают работать над другими активными объектами Foo. – rouble

1

Чтобы решить эту проблему, вы можете использовать ReadWriteLock: Когда поток хочет «использовать» объект, он получает блокировку чтения. Несколько потоков могут одновременно получать блокировку чтения. В методе logout() поток должен был бы получить блокировку записи. Этот поток блокируется до тех пор, пока не будут освобождены все остальные блокировки, а затем выполнит операцию выхода из системы и освободит блокировку записи.

Как и в других реализациях Lock в пакете java.util.concurrent, вам необходимо убедиться, что блокировка освобождена потоками, когда они закончили использование объекта; например

foo.readLock().lock(); 
try { 
    foo.doSomething(); 
    foo.doSomethingElse(); 
} finally { 
    foo.readLock().release(); 
} 
0

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

0

Посмотрите на Lock#tryLock():

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

Приобретает замок, если он доступен, и немедленно возвращается с значением true. Если блокировка недоступна, этот метод немедленно возвращает со значением false.

Типичная идиома использование этого метода будет:

 Lock lock = ...; 
     if (lock.tryLock()) { 
      try { 
       // manipulate protected state 
      } finally { 
       lock.unlock(); 
      } 
     } else { 
      // perform alternative actions 
     } 

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

1

Если вы используете Java 1.5 или новее, обязательно прочитайте файл java.util.concurrent от Doug Lea. У этого есть некоторые чрезвычайно приятные возможности, которые вы можете использовать, и, как говорит Джошуа Блох в Effective Java: «Учитывая сложность использования wait и уведомления правильно, вместо этого вы должны использовать утилиты параллелизма более высокого уровня». (стр. 273).

Один подход к рассмотрению java.util.concurrent заключается в использовании ThreadPoolExecutor, который ставит в очередь набор одновременных задач (Runnables) и распределяет их по пулу рабочих потоков. После этого выйдите из системы ExecutorService.shutdown(), чтобы остановить выполнение новых задач. Затем вы можете позвонить ExecutorService.isTerminated(), чтобы определить, когда завершилась последняя задача.

Смежные вопросы