繁体   English   中英

Java 多线程意外 output

[英]Java multithreading unexpected output

我正在尝试在 Java 中学习多线程。 这是我写的代码:

public class SharedResources {
    volatile private int sharedPar1;
    volatile private String sharedPar2;

    public SharedResources(Integer sharedPar1, String sharedPar2) {
        synchronized (sharedPar1) {
            this.sharedPar1 = sharedPar1;
        }
        synchronized (sharedPar2) {
            this.sharedPar2 = sharedPar2;
        }
    }

    public synchronized int getSharedPar1() {
        return sharedPar1;
    }

    public synchronized void setSharedPar1(int sharedPar1) {
        this.sharedPar1 = sharedPar1;
    }

    public synchronized String getSharedPar2() {
        return sharedPar2;
    }

    public synchronized void setSharedPar2(String sharedPar2) {
        this.sharedPar2 = sharedPar2;
    }
}

上面这只是一个代表模拟共享资源的class。 两个模拟线程类:

public class Task1 extends Thread {
    volatile private SharedResources sharedResources;

    public Task1(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1() + 1000);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2() + "c");
            System.out.println("Task1, it. " + i + ". - Shared resources: " + sharedResources.getSharedPar1() + " and " + sharedResources.getSharedPar2());
        }
    }
}

任务 2:

public class Task2 extends Thread {
    volatile private SharedResources sharedResources;

    public Task2(SharedResources sharedResource) {
        this.sharedResources = sharedResource;
    }

    public SharedResources getSharedResources() {
        return sharedResources;
    }

    public void setSharedResources(SharedResources sharedResources) {
        this.sharedResources = sharedResources;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            sharedResources.setSharedPar1(sharedResources.getSharedPar1() + 100);
            sharedResources.setSharedPar2(sharedResources.getSharedPar2() + "b");
            System.out.println("Task2, it. " + i + ". - Shared resources: " + sharedResources.getSharedPar1() + " and " + sharedResources.getSharedPar2());
        }
    }
}

现在,主要的function:

public static void main(String[] args) {
        SharedResources sharedResource = new SharedResources(0, "");

        Thread firstTask = new Task1(sharedResource);
        Thread secondTask = new Task2(sharedResource);

        firstTask.start();
        secondTask.start();

        for (int i = 0; i < 10; i++) {
            System.out.println("Main thread! - Shared resources: " + sharedResource.getSharedPar1() + " and " + sharedResource.getSharedPar2());
            sharedResource.setSharedPar1(sharedResource.getSharedPar1() + 1);
            sharedResource.setSharedPar2(sharedResource.getSharedPar2() + "a");
        }
        System.out.println();
    }

现在,想法是这样 - 有两个线程对变量sharedPar1sharedPar2进行不同的操作,在 output 我想获得变量的一致值,这意味着我希望能够将该程序的 output 逻辑连接到它的代码行。 但是,我不知道为什么我的 output 中有一些东西。 基本上,我需要有人向我解释这种output:

Main thread! - Shared resources: 1000 and 
Main thread! - Shared resources: 1101 and ba
Main thread! - Shared resources: 1102 and baa
Main thread! - Shared resources: 1103 and baaa
Main thread! - Shared resources: 1104 and baaaa
Main thread! - Shared resources: 1105 and baaaaa
Main thread! - Shared resources: 1106 and baaaaaa
Main thread! - Shared resources: 1107 and baaaaaaa
Main thread! - Shared resources: 1108 and baaaaaaaa
Main thread! - Shared resources: 1109 and baaaaaaaaa

Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b
Task2, it. 1. - Shared resources: 1210 and baaaaaaaaaab
Task1, it. 1. - Shared resources: 2210 and baaaaaaaaaabc
Task1, it. 2. - Shared resources: 3310 and baaaaaaaaaabcc
Task2, it. 2. - Shared resources: 3310 and baaaaaaaaaabcb
Task1, it. 3. - Shared resources: 4310 and baaaaaaaaaabcbc
Task2, it. 3. - Shared resources: 4410 and baaaaaaaaaabcbcb
Task1, it. 4. - Shared resources: 5410 and baaaaaaaaaabcbcbc
Task2, it. 4. - Shared resources: 5510 and baaaaaaaaaabcbcbcb
Task1, it. 5. - Shared resources: 6510 and baaaaaaaaaabcbcbcbc
Task2, it. 5. - Shared resources: 6610 and baaaaaaaaaabcbcbcbcb
Task1, it. 6. - Shared resources: 7610 and baaaaaaaaaabcbcbcbcbc
Task2, it. 6. - Shared resources: 7710 and baaaaaaaaaabcbcbcbcbcb
Task1, it. 7. - Shared resources: 8710 and baaaaaaaaaabcbcbcbcbcbc
Task1, it. 8. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcc
Task1, it. 9. - Shared resources: 10810 and baaaaaaaaaabcbcbcbcbcbccc
Task2, it. 7. - Shared resources: 9810 and baaaaaaaaaabcbcbcbcbcbcb
Task2, it. 8. - Shared resources: 10910 and baaaaaaaaaabcbcbcbcbcbcccb
Task2, it. 9. - Shared resources: 11010 and baaaaaaaaaabcbcbcbcbcbcccbb

我了解 output 中的所有内容,直到Task2 行。 0. - 共享资源:1100 和 b - 为什么在这一行中我们没有前一行的值(主循环中的最后一行)? Output 的主要部分,我们在字符串中添加了“aaa..”,而添加到 integer 变量中的那些在这一行中找不到,这就是我不明白的。

所以,基本上我希望能够在多个不同线程之间使用共享的 class 和变量,但即使我将变量设置为“易失性”并将方法设置为“同步”,输出到屏幕时再次无法正确更新值。 有人可以解释这是为什么吗? 如果你发现这是做这些事情的错误方式,我错在哪里?

另外,我想知道是否有一种方法可以确保在一个线程中,我们总是执行一个循环的完整迭代,然后调度程序将我们的线程踢出处理器?

有几个问题。 首先,您在获取/设置周期上有一个竞争条件,以便每个线程可以读取相同的初始值,因此它们替换另一个线程的更新值。 这是因为您没有在整个块上同步每个独立变量的原子更改,并且两个线程在每个运行sharedResource.setSharedParX(updatedValue) ) 之前执行getSharedParX() ) :

sharedResource.setSharedParX(sharedResource.getSharedParX() + x);

因此,您的读取/更新时间线会践踏其他线程的结果(请注意,在更新之前主要打印),并且这些线程的 output 的第一行使用sharedPar1sharedPar2的第一个/第二个值的混合:

// Task1 read sharedPar1=0 and saved sharedPar1=1000
Main thread! - Shared resources: 1000 and 

// Task2 reads sharedPar1=1000 and saved sharedPar1=1100 (trampling main values)
// Task2 read sharedPar2="" and saved sharedPar2="b" (trampling main/Task2 values)
Task2, it. 0. - Shared resources: 1100 and b
Task1, it. 0. - Shared resources: 1100 and b

其次注意System.out是一个PrintWriter所以println消息是同步的。 在 Task1/Task2 有机会之前,主线程能够打印更多结果,这就是为什么 Task1/2 的第一行出现在列表中的原因。 在线程内部打印到相同的 stream 通常不是一个好主意,因为 I/O 争用可能会减慢所有线程,因为它们相互竞争以写入 output。

您需要修复以使操作是原子的 - 查看使用AtomicIntegerAtomicReference<String>或实现您自己的处理程序以执行一步所需的更改(并删除 setX),例如:

synchronized int addAndGet(int delta) {
    sharedPar1 += delta;
    return sharedPar1;
}
synchronized String appendAndGet(String delta) {
    sharedPar2 += delta;
    return sharedPar2;
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM