Как многопоточность двух счетчиков в Java? - PullRequest
1 голос
/ 28 апреля 2020

В данный момент я изучаю нить и пишу код для альтернативного подсчета чисел. Один поток должен считать от 0 до 100, если булево обратное значение равно false, и один поток должен считать от 100 до 0, если обратное значение истинно.

На данный момент я сделал следующее:

public class Count implements Runnable {

private int start = 0;
private int finish = 0;
private boolean reverse = false;
private Printer printer;
private String prefix;

public Counter(String prefix, int start, int finish, boolean reverse, Printer printer) throws Exception {
    if (finish < start && printer == null) {
        throw new Exception("finish must be equal or greater than start, no nulls allowed.");
    } else {
        this.start = start;
        this.finish = finish;
        this.reverse = reverse;
        this.printer = printer;
        this.prefix = prefix;
    }
}

@Override
public synchronized void run() {

    if (reverse) {


        for (int i = start; i <= finish; i++) {

            try {
                Thread.sleep(100);
                printer.setPrefix(prefix);
                printer.setCount(i);
                printer.print();
            } catch (Exception e) {
                System.err.println("Already interrupted.");
            }
        }
    } else {

        for (int i = finish; i >= start; i--) {
            try {
                Thread.sleep(100);
                printer.setPrefix(prefix);
                printer.setCount(i);
                printer.print();
            } catch (Exception e) {
                System.err.println("Already interrupted.");
            }
        }
    }
}
}




public class Printer {

    String prefix = null;
    int count = 0;

    public Printer() {

    }

    public void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public void setCount(int count) {
        this.count = count;
    }

    public void print() {
        System.out.println(prefix + ": " + count);

    }
}

public class Main {

    public static void main(String[] args) throws Exception {

        Printer printer = new Printer();
        Thread counter = new Thread(new Counter("Prefix1", 0, 100, false, printer));
        Thread counter2 = new Thread(new Counter("Prefix2", 0, 100, true, printer));
        counter.start();
        counter2.start();


    }
}

Когда я запускаю программу, вывод выглядит следующим образом:

Prefix2: 0
Prefix2: 0
Prefix1: 99
Prefix2: 1
Prefix2: 98
Prefix2: 98
Prefix1: 97
Prefix2: 3
Prefix1: 4
Prefix1: 4

Может кто-нибудь исправить или сказать мне, как исправить код, чтобы распечатать их альтернативным способом?

Ожидается Вывод:

Prefix1: 1
Prefix2: 100
Prefix1: 2
Prefix2: 99
Prefix1: 3
Prefix2: 98
and so on...

Заранее спасибо!

Ответы [ 4 ]

1 голос
/ 28 апреля 2020

Чего вам не хватает, так это того, что вам нужно то, что управляет тем, кто в этом очередь. Темы не будут go синхронно (конечно, дух). Что может произойти, это:

Thread 1 Runs
Thread 1 Runs
Thread 1 Runs
Thread 2 Runs

Даже если вы установите Prio то же самое. Я сделал вам пример, чтобы показать, как вы можете его настроить:

import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.atomic.AtomicBoolean;

class Scratch {
    public static void main(String[] args) {
        AtomicBoolean atomicBoolean = new AtomicBoolean(true);

        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(2);

        threadPoolExecutor.execute(() -> {
            int i = 10;
            while (i > 0) {
                if (atomicBoolean.get()) {
                    System.out.println("plus: " + i);
                    i--;
                    atomicBoolean.set(false);
                }
            }
        });
        threadPoolExecutor.execute(() -> {
            int i = 1;
            while (i <= 10) {
                if (!atomicBoolean.get()) {
                    System.out.println("minus: " + i);
                    ++i;
                    atomicBoolean.set(true);
                }
            }
        });
        threadPoolExecutor.shutdown();
    }
}

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

0 голосов
/ 28 апреля 2020

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

Java 5 представил много утилиты параллелизма , в том числе блокировка объектов . (Обратитесь к Параллельному уроку Основы trail, который является частью Oracle java учебников.) Еще одна ссылка, которая помогла мне понять утилиты java для параллелизма, была книга Размышления в Java (4-е издание) Брюса Эккеля.

Поэтому я использовал класс ReentrantLock вместо синхронизации. Я также синхронизировал все методы в вашем классе Printer, так как они вызываются из нескольких потоков. Вот моя версия этого класса. Я добавил идентификатор потока в метод print(), чтобы вы могли соотнести напечатанный вывод с вызывающим потоком.

public class Printer {
    String prefix;
    int count;

    public synchronized void setPrefix(String prefix) {
        this.prefix = prefix;
    }

    public synchronized void setCount(int count) {
        this.count = count;
    }

    public synchronized void print() {
        System.out.printf("[%d] %s: %3d%n", Thread.currentThread().getId(), prefix, count);
    }
}

Вот моя версия вашего Counter класса.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;

public class Counter implements Runnable {
    private static ReentrantLock theLock = new ReentrantLock();
    private int start = 0;
    private int finish = 0;
    private boolean reverse = false;
    private Printer printer;
    private String prefix;

    public Counter(String prefix, int start, int finish, boolean reverse, Printer printer)
            throws Exception {
        if (finish < start && printer == null) {
            throw new Exception("finish must be equal or greater than start, no nulls allowed.");
        }
        else {
            this.start = start;
            this.finish = finish;
            this.reverse = reverse;
            this.printer = printer;
            this.prefix = prefix;
        }
    }

    @Override
    public void run() {
        if (reverse) {
            for (int i = start; i <= finish; i++) {
                try {
                    Thread.sleep(100);
                    theLock.lock();
                    printer.setPrefix(prefix);
                    printer.setCount(i);
                    printer.print();
                }
                catch (Exception e) {
                    System.err.println("Already interrupted.");
                }
                finally {
                    if (theLock.isHeldByCurrentThread()) {
                        theLock.unlock();
                    }
                }
            }
        }
        else {
            for (int i = finish; i >= start; i--) {
                try {
                    Thread.sleep(100);
                    theLock.lock();
                    printer.setPrefix(prefix);
                    printer.setCount(i);
                    printer.print();
                }
                catch (Exception e) {
                    System.err.println("Already interrupted.");
                }
                finally {
                    if (theLock.isHeldByCurrentThread()) {
                        theLock.unlock();
                    }
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        Printer printer = new Printer();
        ExecutorService es = Executors.newFixedThreadPool(2);
        es.execute(new Counter("Prefix1", 0, 100, false, printer));
        es.execute(new Counter("Prefix2", 0, 100, true, printer));
        es.shutdown();
    }
}

Насколько я могу судить, вы не можете полностью контролировать планирование потоков в java, следовательно, если вы запустите приведенный выше код, который вы можете увидеть в выходных строках, например:

[14] Prefix2:   0
[13] Prefix1: 100
[13] Prefix1:  99
[14] Prefix2:   1
0 голосов
/ 28 апреля 2020

Основная проблема в том, что вы synchronizing на разных locks, поэтому вы вообще не синхронизируете. Оператор synchronized run() использует монитор intrinsic из каждого экземпляра count. Поэтому попробуйте следующее:

  • Удалите синхронный оператор из прогона
  • Establi sh общий lock для синхронизации. Поместите следующее в ваш count класс.
public final static Object lock = new Object();
  • Затем сделайте это с каждым из ваших try blocks
synchronized (lock) {
    try {
        Thread.sleep(100);
        printer.setPrefix(prefix);
        printer.setCount(i);
        printer.print();
    } catch (Exception e) {
        System.err.println("Already interrupted.");
    }
}
  • Теперь уменьшите ваш счет со 100 до 10, чтобы вы могли легче отслеживать события.

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

Вот мои изменения.

    public final static Object lock = new Object();

    @Override
    public  void run() {
        if (reverse) {

            for (int i = start; i <= finish; i++) {

             synchronized(lock) {
                    try {
                        Thread.sleep(100);
                        printer.setPrefix(prefix);
                        printer.setCount(i);
                        printer.print();
                    } catch (Exception e) {
                        System.err.println("Already interrupted.");
                    }
                }
            }

        } else {
            for (int i = finish; i >= start; i--) {
                synchronized(lock) {
                try {
                        Thread.sleep(100);
                        printer.setPrefix(prefix);
                        printer.setCount(i);
                        printer.print();
                    } catch (Exception e) {
                        System.err.println("Already interrupted.");
                    }
                }

            }
        }

    }

И вот мой последний пробег

Prefix1: 10
Prefix1: 9
Prefix2: 0
Prefix1: 8
Prefix2: 1
Prefix2: 2
Prefix2: 3
Prefix2: 4
Prefix2: 5
Prefix2: 6
Prefix2: 7
Prefix2: 8
Prefix2: 9
Prefix2: 10
Prefix1: 7
Prefix1: 6
Prefix1: 5
Prefix1: 4
Prefix1: 3
Prefix1: 2
Prefix1: 1
Prefix1: 0
0 голосов
/ 28 апреля 2020

Одна вещь, которую вы должны учитывать, это то, что, возможно, у Counter будет единая ответственность , такая как подсчет. Принтер должен нести единоличную ответственность за печать этого счетчика на консоли. Вы должны только создать один объект Counter и разделить его между двумя потоками. На данный момент у вас есть 2 темы с 2 объектами Counter. Проблема в том, что поток 1 и поток 2 не знают, должны ли они увеличиваться или уменьшаться.

Решением вашей проблемы было бы создание отношения случай-до с каждым потоком в одном объекте Counter. ThreadA может отвечать за увеличение, а ThreadB может отвечать за уменьшение. Счетчиком будет общий ресурс , который должен быть синхронизирован .

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

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