Когда несколько потоков обращаются к одному и тому же коду? - PullRequest
0 голосов
/ 21 мая 2019

Мои вопросы:

  1. Является ли Java-программа по умолчанию причиной создания только 1 потока?
  2. Если да, и если мы создаем многопоточную программу, когда несколько потоков обращаются к одному и тому же коду объекта Java?

Например, у меня есть программа на Java с 2 методами - add () и sub (). В каком сценарии 2 или более потоков будут запускать метод add ()?

Разве код не всегда безопасен для потоков, так как несколько потоков будут обращаться к разным разделам кода?

Если нет, приведите пример программы, в которой важна безопасность потоков.

Ответы [ 4 ]

1 голос
/ 21 мая 2019

Не думайте о «фрагментах кода», думайте о том, где находятся данные и сколько потоков обращаются к этим фактическим данным.

  • Локальные переменные находятся в стеке потока, в котором они используются, и являются потокобезопасными, поскольку они представляют собой разные «контейнеры» данных для потока.

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

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

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

import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        new Thread(r).start();
        new Thread(r).start();
    }
    public static class MyRunnable implements Runnable {
        // imagine this list living out in the heap and both threads messing with it
        // this is really just a reference, but the actual data is in the heap
        private List<Integer> list = new ArrayList<>();
        {  for (int i = 0; i < 100; i++) list.add(i);  }

        @Override public void run() {
            while (list.size() > 0) System.out.println(list.remove(list.size() - 1));
        }
    }
}
0 голосов
/ 21 мая 2019

Ваш вопрос говорит о том, что вы, возможно, не полностью понимаете, что означает "нить".

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

Хорошо, но когда мы говорим о многопоточных программах, уже недостаточно говорить, что «компьютер» выполняет наш код. Теперь мы говорим, что потоков выполняет наш код. Каждый поток имеет свое собственное представление о том, где он находится в вашей программе, и если два или более потоков выполняются в одной и той же функции в одно и то же время, то каждый из них имеет свою собственную частную копию аргументов функции и локальных переменных.

Итак, Вы спросили:

Является ли Java-программа по умолчанию причиной создания только 1 потока?

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

Программисты часто называют этот начальный поток "основным потоком". Вероятно, они называют это так, потому что это вызывает main(), но будьте осторожны! Название может вводить в заблуждение: JVM не обрабатывает «основной поток» иначе, чем любой другой поток в многопоточной Java-программе.

если мы создаем многопоточную программу, когда несколько потоков обращаются к одному и тому же коду объекта Java?

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

... Прежде всего, как создать многопоточную программу?

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

class MyRunnable implements Runnable {
    public void run() {
        DoSomeUsefulThing();
        DoSomeOtherThing();
    }
}
MyRunnable r = new MyRunnable();
Thread t = new Thread(r);
t.start();
...

Java создает новый поток, когда какой-то другой поток в вашей программе вызывает t.start(). (ПРИМЕЧАНИЕ! Экземпляр Thread, t, не является потоком . Это всего лишь дескриптор , который ваша программа может использовать для запуска потока и запроса о состоянии его потока и управляй им.)

Когда новый поток начинает выполнять программные инструкции, он начинает с вызова r.run(). Как видите, тело r.run() приведет к тому, что новый поток будет DoSomeUsefulThing(), а затем DoSomeOtherThing(), прежде чем r.run() вернется.

Когда возвращается r.run(), поток завершается (a.k.a., "terminated", a.k.a., "dead").

Итак,

когда несколько потоков обращаются к одному и тому же коду объекта Java?

Когда ваш код заставляет их это делать. Давайте добавим строку к примеру выше:

...
Thread t = new Thread(r);
t.start();
DoSomeUsefulThing();
...

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

, пожалуйста, покажите пример программы, где важна безопасность потоков

Я только что сделал.

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

У этого есть большой потенциал, чтобы не получиться хорошо.

Один из способов исправить это - сообщить функции, над какими данными работать.

class MyDataClass { ... }
Class MyRunnable implements Runnable {
    private MyDataClass data;

    public MyRunnable(MyDataClass data) {
        this.data = data;
    }

    public void run() {
        DoSomeUsefulThingWITH(data);
        DoSomeOtherThingWITH(data);
    }
}
MyDataClass dat_a = new MyDataClass(...);
MyDataClass dat_b = new MyDataClass(...);
MyRunnable r = new MyRunnable(dat_a);
Thread t = new Thread(r);
t.start();
DoSomeUsefulThingWITH(dat_b);

Там!Теперь два потока делают одно и то же, но делают это с разными данными.

Но что, если вы захотите , чтобы они работали с одними и теми же данными?

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

0 голосов
/ 21 мая 2019

1) Является ли Java-программа по умолчанию причиной создания только 1 потока?

Действительно зависит от того, что делает ваш код. Простой System.out.println() вызов может , вероятно, просто создать один поток. Но как только вы, например, откроете окно графического интерфейса Swing, появится как минимум еще один поток («поток диспетчера событий», который реагирует на ввод данных пользователем и заботится об обновлениях пользовательского интерфейса).

2) Если да, и если мы создаем многопоточную программу, когда несколько потоков обращаются к одному и тому же коду объекта Java?

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

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

0 голосов
/ 21 мая 2019
  1. Зависит от реализации.Только один поток («основной поток») будет вызывать метод public static void main(String[]), но это не означает, что другие потоки не были запущены для других задач.

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

    import java.util.ArrayList;
    import java.util.List;
    
    public class Main {
    
        public static void main(String[] args) throws InterruptedException {
            List<Object> list = new ArrayList<>();
    
            Runnable action = () -> {
                while (true) {
                    list.add(new Object());
                }
            };
    
            Thread thread1 = new Thread(action, "tread-1");
            thread1.setDaemon(true); // don't keep JVM alive
    
            Thread thread2 = new Thread(action, "thread-2");
            thread2.setDaemon(true); // don't keep JVM alive
    
            thread1.start();
            thread2.start();
    
            Thread.sleep(1_000L);
        }
    
    }
    

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...