gpt4 book ai didi

java - Java:Stack实现中的线程同步

转载 作者:行者123 更新时间:2023-12-03 13:18:12 25 4
gpt4 key购买 nike

我正在Threads中学习Java,尽管我创建了一个简单的堆栈来测试Thread synchronization。这是堆栈类:

public class Stack {

private int counter;
private String[] storage;

public Stack(final int number) {
storage = new String[number];
counter = 0;
}

public synchronized void push(final String msg) {
if(counter == storage.length) {
System.out.println("Counter full");
} else {
storage[counter++] = msg;
}
}

public synchronized String pop() {
if(isEmpty()) {
System.out.println("There is nothing to pop");
return null;
} else {
String lastElement = storage[--counter];
storage[counter] = null;
return lastElement;
}
}

public boolean isEmpty() {
return counter == 0 ? true : false;
}

public synchronized void printElements() {
for(int i=0; i < storage.length; i++) {
System.out.println(storage[i]);
}
System.out.println("Number of elements "+counter);
}
}
这是主要方法:
public static void main(String[] args) {
Stack s = new Stack(10);

final Thread t1 = new Thread(() -> {
for(int i=0; i < 2; i++) {
s.push("Hello "+i);
}
for(int i=0; i < 2; i++) {
s.push("How are you? "+i);
}
});

final Thread t2 = new Thread(() -> {
for(int i=0; i < 2; i++) {
s.push("Nice to meet you "+i);
}
s.pop();
});

t1.start();
t2.start();

try {
t2.join();
t1.join();
} catch(InterruptedException e) {
e.printStackTrace();
}

s.printElements();
}
使用 synchronized字是使一种方法一次只能由一个线程执行的基本方法。大多数时候,我会收到序列
Hello 0
Hello 1
How are you? 0
How are you? 1
Nice to meet you 0
null
null
null
null
null
Number of elements 5
这是理想的结果。但是,在某些情况下,会返回不同的顺序,这就是我的问题出现的地方。使用 同步的将确保一次只有一个线程执行一种方法,即保护 critical section。但这并不意味着在lambda表达式中指定的用于创建 Runnable的操作序列将始终以相同的流程执行,或者?
(很抱歉,如果这是一个假问题)

最佳答案

是的,为什么呢?
线程同时运行,并且它们引入了许多看似随机性的东西(尽管这当然不是您应该依赖的那种随机性!如果您以此方式生成加密货币作为随机性的来源,那么妥协就很容易了。例如,无论您用它生成的是什么!)。
同步门给您带来的是两件事:

  • JMM精简:它建立先发生/后发生(HBHA)关系。
  • 门控:您保证在任何情况下都不会遇到任何问题。 2个push op同时发生,因此它们都写入例如storage[5],一个任意覆盖另一个。有时,计数器会增加2,或仅增加1。

  • 每个线程可以随时在任何对象中为任何字段创建本地克隆副本,并且可以自由地仅写入其本地克隆副本。但这不是必须的-因此您不能依靠它。如果您编写的代码根据线程是否决定进行复制而有所不同,那么您已经编写了一个错误。阻止这种情况发生的方法是建立HBHA关系。您可以在网上搜索完整列表,但同步是建立它们的一种简便方法。
    因此,您的代码有一个错误。您的 isEmpty()方法未同步,但是正在访问共享状态( counter字段已由多个线程访问和更改-因此,实际上有任意数量的 counter副本在 float ,并且如果没有HBHA,您将不知道您可能会使用哪个旧副本在那儿阅读)。
    此外,一旦谈到“建立顺序”,就并行化而言,您几乎完全迷失了:重点是事情要同时发生,并且每次必须有2个线程进行协调,那么多线程的大多数优势消失。
    请注意,在一个线程中,代码是按顺序运行的(至少,您无法观察到任何效果只能由以其他某种顺序或同时运行的代码来解释,尽管热点编译器和CPU可以一起工作并对代码重新排序)并在顺序语句完全不影响彼此的情况下并行运行它们(尽管如此),您不能编写代码来证明这一点,但是,这很关键-如果您可以观察到,优化器将意识到这一点,并且不会应用)。
    启动线程会在 x.start()调用和线程的第一行之间创建HBHA,但这就是结束的地方。因此,当您编写时:
    x = "hello";
    t1.start();
    t2.start();

    这保证了两个线程都将“看到”您对 x字段(通过启动线程建立的HBHA)的写入,但是您无法保证t2开始运行后t2将开始运行。也许t2提早开始,JVM是免费的。是否-取决于虚拟机;如果您的代码在t1比t2 *“更早”开始时行为有所不同,则您编写了一个错误。
    *)考虑这确实是错误的。它们同时启动。但这是关于您观察到的事情:根据JVM规范,对于JVM是合法的,您观察到t2正在做的任何事情,然后再观察到t1可以在相同的时间内完成。 t2是否实际运行“更早”更为模糊。也许不是,但是例如t2并没有制作本地缓存的副本,而t1则做了。

    关于java - Java:Stack实现中的线程同步,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66769941/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com