Shrdi
Shrdi

Reputation: 465

Java Thread not go correctly

I want to varify the Thread mechanism, so I made a Company class as following:

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");
    }
}

When I ran the application, I got the unexpected results:

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

But in another practice, a ThreadTest class:

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!");
    }
}

I ran it and got:

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

These confuses me:

  1. I expect the main thread of Company class should end after each IWork objects starts to work but it seems doesn't.
  2. I expect Tom and Mary should work together but the result is Mary works after the end of Tom's working.
  3. It seems the boss never stops his working...
  4. [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.

How could I modify my code of Company to let it go with my expectation (question 1~3), and why?

Thanks a lot.

Upvotes: 1

Views: 155

Answers (2)

Michael Lloyd Lee mlk
Michael Lloyd Lee mlk

Reputation: 14661

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

Because you don't have any logical place for the thread to yield (for example IO), there is no guarantee that the context will ever switch. The scheduler does not force the matter here, so they both run until completion in serial.

You can force the matter by adding a Thread.yield() (or sleep, or by doing some IO).

With this sleep and yield mean different things, even though the effect is sortof the same.

  • sleep says "dear scheduler, this thread wants to do nothing for the next x ms"
  • yield says "dear scheduler now is an awesome time to let other threads do stuff".

Select the one that is closest to what you want to say.

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

Same issue as above. All your threads are busy loops, hogging the CPU until they finish.

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

As per the comment below, endCount is not thread safe. You could wrap it in an AtomInteger, or add some synchronized blocks.

As an unrelated aside, you should think about the Boss not being a Thread at all. She could be implemented just via callbacks.

[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.

The Scheduler does as the Scheduler wants. If you run the first multiple times you are not guaranteed to get the same results in either application.

Upvotes: 1

xingbin
xingbin

Reputation: 28289

The only problem is there is no synchronization on endCount, so when Tom and Mary call onEnd method of Boss and increment endCount, the working Boss may not notice it.

You can use AtomicInteger,

AtomicInteger endCount

and

  • use endCount.incrementAndGet() instead endCount++
  • use endCount.get() < members.size() instead endCount<members.size()

so the JMM can gurantee Boss get the fresh value in its loop.


And, as suggested in the comment, you can add this in the loop of Worker, it will be easier to simulate multi-thread enviroment:

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

}

Update

When you start multiple threads, you can not controll the execution orders of each line of them without synchronization. Their execution order is scheduled by CPU. You can check this.

Even in your second test, besides Main: Start! is guranteed to be displayed at first line, other lines' order is still uncertain.

And, Thread.sleep or Thread.yield will just make it easier to simulate concurrency execution, it is still not guranteed Tom and Mary will output something on the console line by line.


Here is the test result on my computer:

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!

Upvotes: 1

Related Questions