Это нормально делать с потоками и блокировать очереди?

Мне было интересно, нормально ли вам предоставить поток доступ к экземпляру класса, чтобы вы могли выполнять операции над определенными членами/переменными этого класса.

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

Однако, что, если в какой-то момент в будущем я решаю делать операции с x в основном потоке? Или просто чтение из x. Что делать, если и другой поток, и основной поток хотят читать x одновременно?

Это вообще не так, как я его структурировал в моем коде?

package test;

import java.lang.Thread;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

class AThread extends Thread {

 Test test;

 AThread(Test test) {
 this.test = test;
 }
 BlockingQueue<string> queue = new LinkedBlockingQueue<string>();

 public void run() {
 String msg;
 while ((msg = queue.poll()) != null) {
 // Process the message
 //System.out.println(msg); //should print "hello"
 if (msg.equals("up")) {
 test.setX(test.getX()+1);
 System.out.println(test.getX());
 }
 }
 }
}

public class Test {
 AThread aThread;
 private int x = 5;

 void setX(int x){
 this.x = x;
 }

 int getX(){
 return x;
 }

 Test() throws InterruptedException{
 System.out.println("MainThread");
 aThread = new AThread(this);
 aThread.start();
 while (true) {
 aThread.queue.put("up");

 }
 }

 public static void main(String[] args) throws InterruptedException {
 new Test();
 }
}
</string></string>

И не только член "х", но также может быть больше членов класса "Тест", которые я хотел бы иметь возможность выполнять такие операции, как чтение/запись.

Является ли это хорошей структурой для этого? Если нет, что должно быть исправлено?

2 ответа

Однако, что, если в какой-то момент в будущем я решаю делать операции с x в основном потоке? Или просто чтение из x. Что делать, если и другой поток, и основной поток хотят читать x одновременно?

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

Однако, если поток выполняет более сложные операции, а не просто устанавливает или получает x, вам может понадобиться synchronized этот метод или иным образом обеспечить блокировку мьютекса, чтобы убедиться, что 2 потока не перекрываются ненадлежащим образом.

Например, если вам нужно увеличить значение x, volatile не поможет, так как инкремент фактически представляет собой 3 операции: get, increment и set. Вы можете использовать synchronized блокировку для защиты ++ или вы должны использовать AtomicInteger который обрабатывает методы incrementAndGet() поточно-безопасным способом.

Ответ @Segey дает отличные отзывы о остальной части вашего кода. Я добавлю один комментарий об этом коде:

while (true) {
 aThread.queue.put("up");
 }

Вы почти никогда не хотите так вращаться. Если вы хотите сделать что-то подобное, я добавлю Thread.sleep(10) или что-то, чтобы замедлить добавление в очередь или сделать очередь ограниченной по размеру. Вероятно, у вас закончится нехватка памяти и создание таких элементов очереди.


В вашем коде есть несколько проблем.

Рассмотрим эту строку:

aThread = new AThread(this);

Это всегда плохая идея передать this где-то в конструкторе. И это не имеет ничего общего с потоками... пока. Причина в том, что "где-то" может вызывать метод на this, и метод может быть переопределен в подклассе, конструктор которого еще не был вызван, и может закончиться катастрофой, поскольку это переопределение может использовать некоторые из полей подкласса, которые еще не инициализированы.

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

while (true) {
 aThread.queue.put("up");

 }

Таким образом, вы создаете объект, работающий параллельно с запуском потока. Java не гарантирует, что поток увидит инициализированный класс в таком случае (даже если не было цикла).

Это также одна из причин, почему начало потоков в конструкторах считается плохой идеей. Некоторые IDE даже дают предупреждение в таких случаях. Обратите внимание, что запуск бесконечных циклов в конструкторах, вероятно, тоже плохая идея.

Если вы переместите свой код в метод run() и выполните new Test().run() в main(), то код будет выглядеть нормально, но вы правы, чтобы беспокоиться о

Однако, что, если в какой-то момент в будущем я решаю делать операции с x в основном потоке?

Лучшая идея заключается в том, чтобы основной поток забыл об объекте сразу после его передачи в поток:

public static void main(String[] args) throws InterruptedException {
 AThread aThread = new AThread(new Test());
 aThread.start();
 while (true) {
 aThread.queue.put("up");
 }
}

licensed under cc by-sa 3.0 with attribution.