Поток Java не работает правильно

Я хочу изменить механизм Thread, поэтому я создал класс Company следующим образом:

public class Company {
    static void p(String s){ System.out.println(s); }

    interface IWork{ void work(); }
    interface OnReportListener{ int onEnd(Worker w); }
    static class Job{ int effertCount, budget=100; }

    static class Worker implements IWork{
        String name; Job job=new Job(); OnReportListener listener; boolean isOver;
        public Worker(String n, OnReportListener l) {
            name = n; listener = l;
        }

        public void work() {
            new Thread(){
                public void run() {
                    while (!isOver) {
                        int spent = (int) Math.round(Math.random()*7-2) ;
                        if (spent<0) p(name+": I earned $"+(-spent));
                        isOver = (job.budget-=spent) <=0;
                        job.effertCount++;
                    }
                    p(name+": OMG, I got the salary $"+ listener.onEnd(Worker.this));
                }
            }.start();
        }
    }

    static class Boss implements IWork, OnReportListener{
        Set<Worker> members; int endCount;
        public Boss(Set<Worker> s){ members = s;}
        public int onEnd(Worker w) {
            p("Boss: "+w.name+", thanks for your effort, you deserve it!");
            endCount++;
            return w.job.effertCount*10;
        }

        public void work() {
            new Thread(){
                public void run() {
                    while (endCount<members.size()) { /*fool around*/ }
                    p("Boss: It's time to go home!");
                }
            }.start();
        }
    }

    public static void main(String[] args) {
        Set<Worker> workers = new HashSet<Worker>();
        Boss boss = new Boss(workers);
        Worker tom = new Worker("Tom", boss); 
        workers.add(tom); // hire Tom
        Worker mary = new Worker("Mary", boss); 
        workers.add(mary); // hire Mary

        p("Company.main: Start to work!");
        boss.work();
        tom.work();
        mary.work();
        p("Company.main: End of the assigning");
    }
}

Когда я запустил приложение, я получил неожиданные результаты:

Company.main: Start to work!
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $2
Tom: I earned $2
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $2
Tom: I earned $2
Tom: I earned $1
Tom: I earned $1
Tom: I earned $1
Tom: I earned $2
Tom: I earned $1
Boss: Tom, thanks for your effort, you deserve it!
Tom: OMG, I got the salary $770
Mary: I earned $1
Mary: I earned $2
Mary: I earned $2
Mary: I earned $1
Mary: I earned $1
Mary: I earned $1
Mary: I earned $2
Mary: I earned $1
Mary: I earned $2
Mary: I earned $1
Mary: I earned $1
Mary: I earned $2
Boss: Mary, thanks for your effort, you deserve it!
Mary: OMG, I got the salary $510
Company.main: End of the assigning

Но в другой практике класс ThreadTest:

public class ThreadTest extends Thread{
    static void p(String s){ System.out.println(s); }
    public ThreadTest(String s){ super(s); }
    public void run() {
        for (int i = 0; i < 25; i++) p(getName()+": "+i);
    }

    public static void main(String[] args) {
        p("Main: Start!");
        new ThreadTest("t1").start();
        new ThreadTest("t2").start();
        p("Main: Finish!");
    }
}

Я запустил и получил:

Main: Start!
t1: 0
t1: 1
t1: 2
Main: Finish!
t2: 0
t2: 1
t2: 2
t2: 3
t2: 4
t2: 5
t1: 3
t1: 4
t1: 5
t1: 6
t2: 6
t2: 7
t2: 8
t2: 9
t2: 10
t2: 11
t2: 12
t2: 13
t2: 14
t2: 15
t2: 16
t2: 17
t2: 18
t2: 19
t2: 20
t2: 21
t2: 22
t2: 23
t2: 24
t1: 7
t1: 8
t1: 9
t1: 10
t1: 11
t1: 12
t1: 13
t1: 14
t1: 15
t1: 16
t1: 17
t1: 18
t1: 19
t1: 20
t1: 21
t1: 22
t1: 23
t1: 24

Это меня смущает:

  1. Я ожидаю, что основной поток класса Company должен заканчиваться после того, как каждый объект IWork начнет работать, но похоже, что это не так.
  2. Я полагаю, что Том и Мэри должны работать вместе, но в результате Мэри работает после того, как Том закончил работу.
  3. Кажется, начальник не перестает работать ...
  4. [Обновите / добавьте этот вопрос:] Мне не нужно добавлять Thread.yield () или Thread.sleep () для ThreadTest, и t1 / t2 / основной поток может выполняться отдельно.

Как я мог изменить свой код Company, чтобы он соответствовал моим ожиданиям (вопросы 1 ~ 3), и почему?

Большое спасибо.

Попробуйте добавить Thread.sleep(0) в цикл каждого из методов run. Это должно дать планировщику потоков возможность переключать контексты.

OldCurmudgeon 27.03.2018 17:35

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

Kayaman 27.03.2018 17:49
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
2
141
2

Ответы 2

Единственная проблема в том, что на endCount нет синхронизации, поэтому, когда Tom и Mary вызывают метод onEnd для Boss и увеличивают endCount, рабочий Boss может этого не заметить.

Вы можете использовать AtomicInteger,

AtomicInteger endCount

а также

  • используйте endCount.incrementAndGet() вместо endCount++
  • используйте endCount.get() < members.size() вместо endCount<members.size()

так что JMM может гарантировать, что Boss получит новое значение в своем цикле.


И, как предлагается в комментарии, вы можете добавить это в цикл Worker, так будет проще моделировать среду multi-thread:

try {
    Thread.sleep(10);
} catch (Exception e) {

}

Обновлять

Когда вы запускаете несколько потоков, вы не можете контролировать порядок выполнения каждой из них без синхронизации. Порядок их выполнения определяется ЦП. Вы можете проверить это.

Даже во втором тесте, помимо того, что Main: Start! гарантированно отображается в первой строке, порядок остальных строк по-прежнему неуверенный.

И Thread.sleep или Thread.yield просто упростят имитацию выполнения параллелизма, это все еще не гарантировано Tom, и Mary будет выводить что-то на консоль построчно.


Вот результат теста на моем компьютере:

Company.main: Start to work!
Company.main: End of the assigning
Tom: I earned $2
Mary: I earned $1
Mary: I earned $1
Mary: I earned $1
Tom: I earned $1
Tom: I earned $2
Mary: I earned $1
Tom: I earned $1
Tom: I earned $2
Mary: I earned $1
Mary: I earned $2
Tom: I earned $1
Mary: I earned $2
Mary: I earned $1
Mary: I earned $1
Mary: I earned $2
Mary: I earned $2
Mary: I earned $2
Tom: I earned $1
Tom: I earned $1
Mary: I earned $1
Tom: I earned $1
Mary: I earned $1
Tom: I earned $1
Mary: I earned $2
Tom: I earned $1
Mary: I earned $1
Tom: I earned $1
Mary: I earned $2
Mary: I earned $1
Tom: I earned $1
Boss: Tom, thanks for your effort, you deserve it!
Tom: OMG, I got the salary $590
Mary: I earned $1
Boss: Mary, thanks for your effort, you deserve it!
Mary: OMG, I got the salary $770
Boss: It's time to go home!

Этого не достаточно. Создание volatile не делает endCount++; атомарным, что может привести к потере обновлений значения.

Kayaman 27.03.2018 17:50

Вы должны использовать AtomicInteger

Jose Da Silva 27.03.2018 17:50

I expect Tom and Mary should work together but the result is Mary works after the end of Tom's working

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

Вы можете форсировать этот вопрос, добавив Thread.yield() (или спящий, или выполнив некоторый ввод-вывод).

В этом случае sleep и yield означают разные вещи, хотя эффект примерно одинаковый.

  • sleep говорит: «Уважаемый планировщик, этот поток не хочет ничего делать в течение следующих x мс»
  • yield говорит: «Дорогой планировщик, сейчас отличное время, чтобы позволить другим потокам делать что-то».

Выберите тот, который ближе всего к тому, что вы хотите сказать.

I expect the main thread should end after each IWork objects starts to work but it seems doesn't

Та же проблема, что и выше. Все ваши потоки - это занятые циклы, загружающие ЦП до завершения.

Finally it seems the boss never stops his working...

Согласно комментарию ниже, endCount не является потокобезопасным. Вы можете обернуть его в AtomInteger или добавить несколько синхронизированных блоков.

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

[Update/Append this question:] I don't have to add Thread.yield() or Thread.sleep() for ThreadTest and t1/t2/main thread could run separately.

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

endCount++; может вызвать состояние гонки
Jose Da Silva 27.03.2018 17:52

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