gpt4 book ai didi

java - 为什么并发修改共享对象的所有方法不需要同步修饰符?

转载 作者:行者123 更新时间:2023-12-01 19:38:49 25 4
gpt4 key购买 nike

我想了解何时将 synchronized 修饰符添加到修改共享对象的方法中,何时不添加。

我编写了弹跳球游戏的修改版本。每个球(我称之为“石头”)都是一个线程。为了管理重绘过程,我保留了一个石头HashSet,并且有几个方法处理这个集合:

  • 当我添加新石头时(如果用户按下 GUI 上的 Fire 按钮);

  • 当我杀死一颗或多颗石头时(如果用户点击面板上的石头);

  • 当其中一颗 killer 石(一种特殊的“坏”石)接触任何正常的“好”石并杀死它们时;

  • 最后,当调用 paintComponent() 方法时。

嗯,我认为处理这些事情的所有方法都必须声明为synchronized。但我做了一些尝试,发现:

  • 在某些情况下,需要 synchronized 修饰符(如果删除它,则会出现异常,好吧,这就是我所期望的);

  • 在其他一些情况下,我删除了synchronized修饰符,并且我从未遇到过任何异常,甚至越来越多地运行程序,创造并杀死了大量 killer 石头和/或好石头。

我在 google 上搜索了很多,但我了解到,当方法访问共享对象时,synchronized 修饰符总是需要,而仅当该对象是不可变。但我不明白为什么我可以从其中一些方法中删除 synchronized 修饰符而不会出现异常

现在我附加了 StoneSet 类,它是定义所有这些方法的地方。这个类是一个单例:只有它的一个实例被应用程序中的几乎所有其他对象创建和共享。我清除了类中所有不必要的代码,并编写了许多注释来帮助读者理解类并(我希望)告诉我发生了什么。对于很长的附件(超过 100 行),我深表歉意。

package rollingstones;

import java.awt.Graphics;
import java.util.HashSet;
import java.util.Iterator;

class StoneSet {

private final HashSet<Stone> set = new HashSet<>(64); // this is the set
private AreaGrafica areaGrafica; // this is the JPanel

void setAreaGrafica(AreaGrafica areaGrafica) { // invoked at the beginning
this.areaGrafica = areaGrafica;
}

/**
* This method is called by the paintComponent() of the panel.
* HERE THE SYNCHRONIZED MODIFIER IS NEEDED: IF I REMOVE IT, I GET java.util.ConcurrentModificationException
*/
synchronized void redrawAll(Graphics g) {
final Iterator<Stone> iter = set.iterator();
Stone stone;
while (iter.hasNext()) {
stone = iter.next();
g.setColor(stone.getColor());
g.fillOval(stone.getX(), stone.getY(), stone.getSize(), stone.getSize());
}
}

/**
* This method is called when the user clicks the GUI's Fire button (actionPerformed awt event).
*/
void addGoodStone() {
Stone stone = new GoodStone(); // GoodStone is a Stone
addStone(stone);
}

/**
* This method is called when the user clicks the GUI's Killer button (actionPerformed awt event).
*/
void addKillerStone() {
Stone stone = new KillerStone(); // KillerStone is a Stone
addStone(stone);
}

/**
* This method adds a stone into the set, so it modifies the set, but...
* ...HERE I REMOVED THE SYNCHRONIZED MODIFIER AND I NEVER GOT ANY EXCEPTION.
*/
private void addStone(Stone stone) {
stone.start(); // start the thread (each stone is a thread)
set.add(stone); // put the stone into the set
System.out.print(set.size() + " ");
}

/**
* This method is called when the user clicks a point on the panel (mouseClicked awt event).
* This method removes more than one of the stones from the set, but...
* ...HERE I REMOVED THE SYNCHRONIZED MODIFIER AND I NEVER GOT ANY EXCEPTION.
*/
void killStone(int xClicked, int yClicked) {
final Iterator<Stone> iter = set.iterator();
Stone stone;

while (iter.hasNext()) {
stone = iter.next();

if (SOME CONDITIONS, READING THE STONE STATUS) {
stone.interrupt(); // stop the thread
iter.remove(); // remove the stone from the set
System.out.print(set.size() + " ");
}
}

if (set.isEmpty()) {
areaGrafica.repaint(); // remove the image of the final stone from the panel
}
}


/**
* This method is called by the run() method of the killer stones (see later).
* HERE THE SYNCHRONIZED MODIFIER IS NEEDED: IF I REMOVE IT, I GET java.util.ConcurrentModificationException
*/
synchronized void killNeighbouringGoodStones(int x, int y, int radius) {
final Iterator<Stone> iter = set.iterator();
Stone stone;

while (iter.hasNext()) {
stone = iter.next();

if (SOME OTHER CONDITIONS, USING THE STONE STATUS) {
stone.interrupt(); // stone is a thread
iter.remove(); // remove the stone from the set
System.out.print(set.size() + " ");
}
}
}
}

}
<小时/>
/**
* This is the run() method of the Stone class.
*/
@Override
public void run() {
try { // while into the try
while (true) {
animate(); // this simple method changes the stone state (*)
Stone.areaGrafica.repaint();
Thread.sleep(SLEEP); // SLEEP is 50 ms
}
} catch (InterruptedException ex) {
System.err.println(ex.getMessage());
}
}

(*) if the stone is a killer stone, the animate() method is overridden:
@Override
void animate() {
super.animate();
set.killNeighbouringGoodStones(getCenterX(), getCenterY(), getSize() / 2); // here set is the singleton StoneSet
}
<小时/>
/**
* This is the paintComponent() method of the panel.
*/
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
set.redrawAll(g);
}

我认为 synchronized 修饰符对于访问共享对象的所有方法都是强制性的,但显然事实并非如此。

编辑

我有一个想法:也许......

  • ...redrawAll() 和killNeighbouringGoodStones() 需要synchronized 修饰符,因为这些方法由我创建的其他线程调用,而...

  • ...addStone() 和killStone() 可能不会同步,因为这些方法是由启用GUI 的API 监听器调用的。

这可能是真的吗?

最佳答案

  1. “我没有遇到异常”、“代码是正确的”和“当前部署的代码可以正常工作”之间存在着天壤之别。

  2. 如果可变对象受同步块(synchronized block)保护,则对该对象的每次访问都必须受到同一监视器上同步块(synchronized block)的保护。否则,代码不是线程安全的,并且可能会发生不好的事情(这对于读取和写入都是如此)。

  3. 如果代码不是线程安全的,它可能会工作得很好,特别是如果您正在编写游戏而不是银行系统。真正严重的错误只有在特定情况下才会出现:在某些特定版本的 JVM 上执行十亿分之一,当某些附加条件成立时等等。

  4. 您收到的异常可能不是典型的线程错误:它们来自以下事实:HashSet 是一个快速失败集合,并且您不允许同时使用不同的迭代器从中删除元素。换句话说:即使您同步了 HashSet 本身,您仍然会收到此错误,即使从纯粹的线程安全角度来看,代码是正确的。

  5. 正如 Solomon Slow 已经评论的那样,您的设计并不真正适合使用线程。所以我假设您这样做是为了好玩/学习 Java 线程。那么正确地做事就显得尤为重要:-)

关于java - 为什么并发修改共享对象的所有方法不需要同步修饰符?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56209755/

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