Использование потоков для изменения объекта - PullRequest
0 голосов
/ 16 октября 2018

Я новичок в темах.Я хотел получить два потока, чтобы увеличить целое число до определенного значения.Поскольку тип int является неизменным, я переключился на атомное целое число.Я также пытался обернуть int в класс, и это тоже не сработало.Я также попробовал статический / volatile int, и это не сработало.Я также пытался использовать политику справедливости.Основная проблема заключается в том, что counterObj не увеличивается должным образом и все еще имеет значение 0, даже если он вводится в оба потока.

Мое ожидаемое поведение при беге:

thread       value
thread 0     0
thread 1     1
thread 0     2
...

То, что я написал до сих пор:

import java.util.concurrent.atomic.AtomicInteger;

public class Application {
    public static void main(String[] args) {
        Application app = new Application();
        try {
            app.launch();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void launch() throws InterruptedException {
        int increments = 100;
        AtomicInteger counterObj = new AtomicInteger(0);
        CounterThread th1 = new CounterThread("1", counterObj, increments);
        CounterThread th2 = new CounterThread("2", counterObj, increments);
        th1.start();
        th2.start();


        System.out.println(counterObj.get());

    }


}

и

import java.util.concurrent.atomic.AtomicInteger;

public class CounterThread implements Runnable {
    private final String threadID;
    private AtomicInteger counterObj;
    private int bound;

    public CounterThread(String threadID, AtomicInteger counter, int bound) {
        this.threadID = threadID;
        this.counterObj = counter;
        this.bound = bound;
    }

    @Override
    public synchronized void run() {
        while (counterObj.get() < bound) {
            synchronized (this) {
                counterObj.incrementAndGet();
            }
        }
        System.out.println("Thread " + threadID + " finished");
    }


    public void start() throws InterruptedException {
        Thread thread = new Thread(this, threadID);
        thread.join();
        thread.start();
    }
}

Приветствия!

Ответы [ 5 ]

0 голосов
/ 16 октября 2018

Есть несколько проблем с вашим кодом:

Thread.join метод работает, только если поток запущен, иначе он ничего не делает.Поэтому вы должны изменить порядок своего кода, но если вы просто переместите метод join после start , при запуске первого потока вызовом CounterThread.start , основного потокабудет ожидать окончания запущенного потока, заблокированного в методе Thread.join , и только после этого продолжит запуск второго.Решением является создание дополнительного метода в классе CounterThread , который будет вызываться после запуска обоих потоков:

public void waitFinish() throws InterruptedException {
    thread.join();
}

synchronized (this) isсинхронизация на экземпляре CounterThread , который был создан при вызове new CounterThread (...) , но у вас есть два экземпляра, поэтому каждый будет синхронизироваться с другим объектом.Для синхронизации для работы вам необходимо использовать общий экземпляр объекта, в этом случае вы можете использовать общий counterObj .

. Гарантированы только методы AtomicInteger .быть потокобезопасным, поэтому после проверки, достигнута ли граница за пределами синхронизированного блока, при входе в синхронизированный блок значение уже может быть изменено другим потоком.Поэтому необходимо выполнить повторную проверку внутри синхронизированного блока ИЛИ, чтобы сначала выполнить синхронизацию с общей блокировкой ( counterObj ) перед проверкой и увеличением.

        while (true) {
            synchronized (counterObj) {
                if (counterObj.get() < bound)
                    counterObj.incrementAndGet();
                else break;
            }
        }

Обратите внимание, что AtomicInteger синхронизированные методы класса теперь не помогают, но поскольку это изменчивый объект, он помогает использовать его в качестве общей блокировки.Если вместо этого вы использовали Integer , будучи неизменным, при увеличении его будет создан новый экземпляр.Так что теперь единственная функция - это обертка, содержащая целочисленный результат.

Собираем все вместе:

public class Application {
    public static void main(String[] args) {
        Application app = new Application();
        try {
            app.launch();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    private void launch() throws InterruptedException {
        int increments = 100;
        AtomicInteger counterObj = new AtomicInteger(0);
        CounterThread th1 = new CounterThread("1", counterObj, increments);
        CounterThread th2 = new CounterThread("2", counterObj, increments);
        th1.start();
        th2.start();
        th1.waitFinish();
        th2.waitFinish();

        System.out.println(counterObj.get());
    }
}

public class CounterThread implements Runnable {
    private final String threadID;
    private AtomicInteger counterObj;
    private int bound;
    private Thread thread;

    public CounterThread(String threadID, AtomicInteger counter, int bound) {
        this.threadID = threadID;
        this.counterObj = counter;
        this.bound = bound;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (counterObj) {
                if (counterObj.get() < bound)
                    counterObj.incrementAndGet();
                else break;
            }
        }
        System.out.println("Thread " + threadID + " finished");
    }


    public void start() throws InterruptedException {
        thread = new Thread(this, threadID);
        thread.start();
    }

    public void waitFinish() throws InterruptedException {
        thread.join();
    }
}
0 голосов
/ 16 октября 2018

AtomicInteger сам заботится об атомарности, поэтому вам не нужно использовать synchronized - но только если вы играете по правилам и выполняете атомарные операции за один вызов.

ВыВы не можете это сделать, потому что вы звоните counterObj.get(), в зависимости от результата counterObj.incrementAndGet().Вам следует избегать этого, потому что вы хотите, чтобы проверка и обновление были частью одного и того же атомарного блока работы.

Вы можете приблизиться с помощью:

while(counterObj.incrementAndGet() < bound) {} ;

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

Чуть более сложное:

IntUnaryOperator incrementWithLimit = x -> 
   ( x < bound ? x + 1 : x );

while(counterObj.updateAndGet(incrementWithLimit) < bound) {};

То есть мы создали функцию, которая увеличивает число только в том случае, если оно меньше * 1017.* и мы говорим AtomicInteger, чтобы применить это.

0 голосов
/ 16 октября 2018

Что вы действительно хотите, так это чтобы только один поток увеличивал int за раз.Переменная int - это требуемый ресурс в синхронизированном блоке, поэтому разные потоки могут увеличивать его по одному.Это можно сделать, используя только syncrhonize.Отказ от ответственности: я не запускал код, чтобы в нем могли быть опечатки или исключения, которые необходимо удалить из класса приложения.

public class Application {

  private int theVar = 0;
  private int increments = 100;

  public static void main(String[] args) {
    Application app = new Application();
    try {
        app.launch();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
  }

  public synchronized addOne(){
    this.theVar++;
  }

  private void launch() throws InterruptedException {
    Runnable counter1 = new Counter(this, increments), counter2 = new Counter(this, increments);
    Thread t1 = new Thread(counter1);
    Thread t2 = new Thread(counter2);
    t1.start();
    t2.start();
  }
}

Класс счетчика

public class Counter implements Runnable{
  private Application app;
  int rounds = -1;

  public Counter(Application app, rounds){
    this.app = app;
    this.rounds = rounds;
  }
  public void run(){
    while(int i=0; i<rounds; i++){
        this.app.addOne();
    }
  }
}
0 голосов
/ 16 октября 2018

Я включил двойную проверку AtomicInteger, похоже, это то, что вы пытались выполнить.

import java.util.concurrent.atomic.AtomicInteger;

public class DualCounters{

    public static void main(String[] args) throws Exception{
        AtomicInteger i = new AtomicInteger(0);
        int bounds = 3;

        Thread a = new Thread(()->{
            int last = 0;
            while(i.get()<bounds){
                synchronized(i){
                    if(i.get()<bounds){
                        last = i.getAndIncrement();
                    }
                }
            }
            System.out.println("a last " + last);
        });

        Thread b = new Thread(()->{
            int last = 0;
            while(i.get()<bounds){
                synchronized(i){
                    if(i.get()<bounds){
                        last = i.getAndIncrement();
                    }
                }
            }
            System.out.println("b last " + last);
        });

        a.start();
        b.start();

        a.join();
        b.join();

        System.out.println(i.get() + " afterwards");

    }
}

Двойная проверка - это неправильная концепция в Java, AtomicInteger предлагает инструментыдля выполнения этого без какой-либо синхронизации.

int a;
while((a = i.getAndIncrement())<bounds){
    ...
}

Теперь a никогда не будет больше границ внутри цикла while .Когда цикл завершен i и a может иметь значение больше границы.

Если это было проблемой, всегда есть другой метод getAndUpdate

while((a = i.getAndUpdate(i->i<bounds?i+1:i)<bounds){
    ...
} 
0 голосов
/ 16 октября 2018

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

Thread thread1 = new Thread(new MyCounterRunnable("1", counterObj, increments));
Thread thread2 = new Thread(new MyCounterRunnable("2", counterObj, increments));

Затем, в вашем main, вам нужно вызвать join после запуска потоков ... следующим образом:

thread1.start(); // starts first thread.
thread2.start(); // starts second thread.

thread1.join(); // don't let main exit until thread 1 is done.
thread2.join(); // don't let main exit until thread 2 is done.
...