首页 > 解决方案 > 为什么这些线程没有有序地开始和结束?如何解决?

问题描述

我是Java乘法线程的新手,我坚持研究我从教程中找到的练习:

想象一下这个场景:我有一个100hp的gareen,它被攻击了10次,我的代码是:

public class Hero{
    public String name;
    public float hp;
    public int damage;

    public Hero(){
        
    }
    public Hero(String name,float hp,int damage){
        this.name = name;
        this.hp = hp;
        this.damage = damage;
    }

    public synchronized void hurt(){
            this.hp -= 1;
    }

    public void recover(){
            this.hp += 1;
    }

    public void attackHero(Hero h) {
        try {

            Thread.sleep(1000);
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        h.hp-=damage;
        System.out.format("%s attacked %s, %s'hp become %.0f%n",name,h.name,h.name,h.hp);

        if(h.isDead())
            System.out.println(h.name +"dead!");
    }

    public boolean isDead() {
        return 0>=hp?true:false;
    }

}
public class TestHero {
    public static void main(String[] args) {
        Hero gareen = new Hero("Gareen", 100, 10);
        System.out.printf("Initial HP of Gareen: %f\n",gareen.hp);
        int N = 10;
        Thread[] hurtThreads = new Thread[N];
        Thread[] coverThreads = new Thread[N];

        for (int i = 0; i < N; i++) {
            Thread t1 = new Thread(() -> {
                gareen.hurt();
                System.out.println(Thread.currentThread().getName() + ": Gareen lost 1 HP,now his HP: " + gareen.hp);
                try{
                    Thread.sleep(3000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " ended");
            });

            t1.start();
            System.out.println(t1.getName() + " started");
            hurtThreads[i] = t1;
        }

// In this time, all threads have been spawned and unstart
        for(Thread t : hurtThreads){
            try {
                t.join();
                System.out.println(t.getName() + " joined ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Now, Gareen's HP: " + gareen.hp);
    
    }
}



在我的想象中,它应该是这样的(为了便于阅读,我将 result() 隔开):

Initial HP of Gareen: 100.000000
// started and joined will be printed first because they all are sychronized code
Thread-0 started
Thread-1 started
Thread-2 started
Thread-3 started
Thread-4 started
Thread-5 started
Thread-6 started
Thread-7 started
Thread-8 started
Thread-9 started
//
Thread-0 joined 
Thread-1 joined 
Thread-2 joined 
Thread-3 joined 
Thread-4 joined 
Thread-5 joined 
Thread-6 joined 
Thread-7 joined 
Thread-8 joined 
Thread-9 joined 
//
// the thread will be started and ended orderly
Thread-0: Gareen lost 1 HP,now his HP: 99.0
Thread-1: Gareen lost 1 HP,now his HP: 98.0
Thread-2: Gareen lost 1 HP,now his HP: 97.0
Thread-3: Gareen lost 1 HP,now his HP: 96.0
Thread-5: Gareen lost 1 HP,now his HP: 95.0
Thread-6: Gareen lost 1 HP,now his HP: 94.0
Thread-7: Gareen lost 1 HP,now his HP: 93.0
Thread-8: Gareen lost 1 HP,now his HP: 92.0
Thread-9: Gareen lost 1 HP,now his HP: 91.0
Thread-10: Gareen lost 1 HP,now his HP: 90.0
//
Thread-0 ended
Thread-1 ended
Thread-2 ended
Thread-3 ended
Thread-4 ended
Thread-5 ended
Thread-6 ended
Thread-7 ended
Thread-8 ended
Thread-9 ended
//
Now, Gareen's HP: 90.0

但现实是(相同,为了可读性而隔开):

Initial HP of Gareen: 100.000000
Thread-0 started
Thread-1 started
Thread-2 started
Thread-3 started
Thread-4 started
Thread-5 started
Thread-6 started
Thread-7 started
Thread-8 started
Thread-9 started
    
Thread-3: Gareen lost 1 HP,now his HP: 96.0
Thread-7: Gareen lost 1 HP,now his HP: 92.0
Thread-2: Gareen lost 1 HP,now his HP: 97.0
Thread-4: Gareen lost 1 HP,now his HP: 95.0
Thread-1: Gareen lost 1 HP,now his HP: 98.0
Thread-6: Gareen lost 1 HP,now his HP: 93.0
Thread-0: Gareen lost 1 HP,now his HP: 99.0
Thread-9: Gareen lost 1 HP,now his HP: 90.0
Thread-8: Gareen lost 1 HP,now his HP: 91.0
Thread-5: Gareen lost 1 HP,now his HP: 94.0
    
Thread-3 ended
Thread-6 ended
Thread-2 ended
Thread-4 ended
Thread-1 ended
Thread-7 ended
Thread-9 ended
Thread-8 ended
Thread-0 ended
Thread-5 ended
    
Thread-0 joined 
Thread-1 joined 
Thread-2 joined 
Thread-3 joined 
Thread-4 joined 
Thread-5 joined 
Thread-6 joined 
Thread-7 joined 
Thread-8 joined 
Thread-9 joined 
Now, Gareen's HP: 90.0

我不明白为什么 join() 总是在调用线程结束之后结束,但我知道它阻塞了最后一条语句System.out.println("Now, Gareen's HP: " + gareen.hp);

以确保将在最后打印。

根据打印结果,似乎每个线程几乎在同一时间开始,但它们的做事和结束都是不同的。 另一个奇怪的部分是我发现更改后的 HP 可以正确处理诸如t0 - 99, t2 - 98, ...之类的线程标签t9 - 90,但是它们没有按降序打印,我花了很长时间才意识到可能的原因: 系统打印什么消息与线程实际所做的不匹配,

我什至发现,当我删除Thread.sleep() 时,定义的 Thread 中的两个打印语句没有按顺序运行,似乎Threads 中的代码实际上没有按顺序运行。我一直认为 Thread 只是一个不能与外部同步代码有序运行的函数。

 for (int i = 0; i < N; i++) {
            Thread t1 = new Thread(() -> {
                gareen.hurt();
                System.out.println(Thread.currentThread().getName() + ": Gareen lost 1 HP,now his HP: " + gareen.hp);
                System.out.println(Thread.currentThread().getName() + " ended");
            });
          

            t1.start();
            System.out.println(t1.getName() + " started");
            hurtThreads[i] = t1;
        }
Initial HP of Gareen: 100.000000
Thread-0 started
Thread-1 started
Thread-2 started
Thread-3 started
Thread-4 started
Thread-5 started
Thread-6 started
Thread-7 started
Thread-8 started
Thread-9 started
    
Thread-5: Gareen lost 1 HP,now his HP: 94.0
Thread-5 ended
Thread-3: Gareen lost 1 HP,now his HP: 96.0
Thread-8: Gareen lost 1 HP,now his HP: 91.0
Thread-9: Gareen lost 1 HP,now his HP: 90.0
Thread-9 ended
Thread-7: Gareen lost 1 HP,now his HP: 92.0
Thread-7 ended
Thread-0: Gareen lost 1 HP,now his HP: 99.0
Thread-0 ended
Thread-3 ended
Thread-8 ended
Thread-2: Gareen lost 1 HP,now his HP: 97.0
Thread-6: Gareen lost 1 HP,now his HP: 93.0
Thread-4: Gareen lost 1 HP,now his HP: 95.0
Thread-6 ended
Thread-1: Gareen lost 1 HP,now his HP: 98.0
Thread-0 joined 
Thread-2 ended
Thread-1 ended
Thread-4 ended
    
Thread-1 joined 
Thread-2 joined 
Thread-3 joined 
Thread-4 joined 
Thread-5 joined 
Thread-6 joined 
Thread-7 joined 
Thread-8 joined 
Thread-9 joined 
Now, Gareen's HP: 90.0

Process finished with exit code 0


现在我完全糊涂了,是什么导致了这些现象,我有可能实现我想象的场景吗?

标签: javamultithreading

解决方案


不能保证线程按设定的顺序执行。您只需为此使用标准同步代码。

如果您只想在主线程之外以线性方式降低健康代码,则使用 1 线程并执行其中的所有代码。例子:

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < N; i++) {
                gareen.hurt();
                System.out.println(Thread.currentThread().getName() + ": Gareen lost 1 HP,now his HP: " + gareen.hp);
                try{
                    Thread.sleep(3000);
                } catch (InterruptedException e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " ended");
            }
        });
        t1.start();
        System.out.println(t1.getName() + " started");

这将达到减少后台线程运行状况的结果,以便您可以同时更新 UI、声音等


另一种解决方案是对 Hero 对象使用 Singleton - https://refactoring.guru/design-patterns/singleton和对象上的同步锁。然而,这不会产生与第一个解决方案显着不同的结果。


不明白为什么 join() 总是在被调用线程结束之后结束

Thread.join()在您的场景中用于阻塞主线程,直到您调用的线程.join()完成。


推荐阅读