Удержание темы

Я читаю Java Concurrency на практике и вроде как путаюсь с концепцией ограничения потока. Книга говорит, что

Когда объект ограничен потоком, такое использование автоматически поточно-ориентировано, даже если сам ограниченный объект не является

Так, когда объект ограничен потоком, никакой другой поток не может иметь к нему доступ? Это то, что значит быть ограниченным потоком? Как сохранить объект в потоке?

Редактировать: Но что, если я все еще хочу поделиться объектом с другим потоком? Предположим, что после того, как поток A завершает работу с объектом O, поток B хочет получить доступ к O. В этом случае, может ли O по-прежнему ограничиваться B после того, как A закончил с ним?

Использование локальной переменной является одним из примеров, но это просто означает, что вы не делитесь своим объектом с другим потоком (ВСЕ). В случае пула соединений JDBC, не передается ли одно соединение из одного потока в другой, когда поток завершает работу с этим соединением (совершенно не догадываясь об этом, потому что я никогда не использовал JDBC).

8 ответов

Решение

Так, когда объект ограничен потоком, никакой другой поток не может иметь к нему доступ?

Нет, все наоборот: если вы гарантируете, что ни один другой поток не имеет доступа к объекту, то говорят, что этот объект ограничен одним потоком.

Не существует механизма уровня языка или JVM, который бы ограничивал объект одним потоком. Вы просто должны убедиться, что никакая ссылка на объект не попадет в место, к которому может обратиться другой поток. Есть инструменты, которые помогают избежать утечки ссылок, такие как ThreadLocal класс, но ничего, что не гарантирует, что ни одна ссылка не просочится никуда.

Например: если единственной ссылкой на объект является локальная переменная, то объект определенно ограничен одним потоком, поскольку другие потоки никогда не смогут получить доступ к локальным переменным.

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

Редактирование объявления. На практике вы можете иметь объект, доступ к которому осуществляется только одним потоком за раз в течение срока его службы, но для которого этот отдельный поток изменяется (JDBC Connection Объект из пула соединений является хорошим примером).

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

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

Наиболее очевидный пример - использование локального хранилища потоков. Смотрите пример ниже:

class SomeClass {
    // This map needs to be thread-safe
    private static final Map<Thread,UnsafeStuff> map = new ConcurrentHashMap<>();

    void calledByMultipleThreads(){
        UnsafeStuff mystuff = map.get(Thread.currentThread());
        if (mystuff == null){
            map.put(Thread.currentThread(),new UnsafeStuff());
            return;
        }else{
            mystuff.modifySomeStuff();
        }
    }
}

UnsafeStuff объекты сами по себе "могут быть разделены" с другими потоками в том смысле, что если вы передадите какой-то другой поток вместо Thread.currentThread() во время выполнения к карте get метод, вы получите объекты, принадлежащие другим потокам. Но вы решили не делать этого. Это "использование, которое ограничено потоком". Другими словами, условия выполнения таковы, что объекты фактически никогда не разделяются между разными потоками.

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

class SomeClass {
    void calledByMultipleThreads(){
        UnsafeStuff mystuff = new UnsafeStuff();
        mystuff.modifySomeStuff();
        System.out.println(mystuff.toString());
    }
}

Здесь UnsafeStuff выделяется внутри метода и выходит из области видимости, когда метод возвращается. Другими словами, спецификация Java статически гарантирует, что объект всегда ограничен одним потоком. Таким образом, не условие выполнения или способ его использования обеспечивает ограничение, а скорее спецификацию Java.

Фактически, современная JVM иногда выделяет такие объекты в стеке, в отличие от первого примера (лично я не проверял это, но я не думаю, что по крайней мере текущие JVM делают).

Иными словами, в первом примере JVM не может быть уверена, что объект ограничен потоком, просто заглянув внутрь calledByMultipleThreads() (кто знает, что другие методы возиться с SomeClass.map). В последнем примере это возможно.


Редактировать: Но что, если я все еще хочу поделиться объектом с другим потоком? Предположим, что после того, как поток A завершает работу с объектом O, поток B хочет получить доступ к O. В этом случае, может ли O по-прежнему ограничиваться B после того, как A закончил с ним?

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

Так, когда объект ограничен потоком, никакой другой поток не может иметь к нему доступ?

Вот что означает ограничение потока - объект может когда-либо быть доступен только одному потоку.

Это то, что значит быть ограниченным потоком?

Смотри выше.

Как сохранить объект в потоке?

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

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

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

Простой пример:

public String s;

public void run() {
  StringBuilder sb = new StringBuilder();
  sb.append("Hello ").append("world");
  s = sb.toString();
}

Экземпляр StringBuilder является потокобезопасным, поскольку он ограничен потоком (который выполняет этот метод run)

Одним из способов является "ограничение стека", при котором объект является локальной переменной, ограниченной стеком потока, поэтому никакой другой поток не может получить к нему доступ. В методе ниже list является локальной переменной и не выходит из метода. Список не должен быть потокобезопасным, поскольку он ограничен стеком выполняющегося потока. Никакой другой поток не может изменить его.

public String foo(Item i, Item j){
    List<Item> list = new ArrayList<Item>();
    list.add(i);
    list.add(j);
    return list.toString();
}

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

private static final ThreadLocal<DateFormat> df
                 = new ThreadLocal<DateFormat>(){
    @Override
    protected DateFormat initialValue() {
        return new SimpleDateFormat("yyyyMMdd");
    }
  };

Дальнейшее чтение

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

В этом случае объект не должен быть "потокобезопасным"

Смотрите: http://codeidol.com/java/java-concurrency/Sharing-Objects/Thread-Confinement/

Более формальным средством поддержания ограничения потока является ThreadLocal, который позволяет связать значение для потока с объектом, содержащим значение. Thread-Local предоставляет методы доступа get и set, которые поддерживают отдельную копию значения для каждого потока, который его использует, поэтому get возвращает самое последнее значение, переданное для установки из текущего выполняющегося потока.

Он содержит копию объекта на один поток, поток A не может получить доступ к копии потока B и нарушил его инварианты, если вы сделаете это специально (например, присвойте значение ThreadLocal статической переменной или предоставьте его другими методами)

Это именно то, что это значит. Доступ к самому объекту осуществляется только одним потоком, и поэтому он безопасен для потоков. ThreadLocal объекты являются своего рода объектами, которые связаны с единственным потоком

Другие вопросы по тегам