首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >为什么不是所有并发修改共享对象的方法都需要synchronized修饰符?

为什么不是所有并发修改共享对象的方法都需要synchronized修饰符?
EN

Stack Overflow用户
提问于 2019-05-20 00:23:26
回答 2查看 71关注 0票数 0

我想知道什么时候向修改共享对象的方法添加synchronized修饰符,什么时候不添加。

我写了一个弹跳球游戏的修改版本。每个球(我称之为“石头”)都是一个thread。为了管理repaint进程,我保存了一个石头的HashSet,有几个方法处理这个集合:

当我添加一个新的石头(如果用户按下图形用户界面上的按钮)时触发

  • ;当我杀死一个或多个石头(如果用户单击panel);
  • when上的石头时)时触发
  • ;当用户单击GUI上的石头时,其中一个杀手石(一种特殊的“坏”石头)会触及任何正常的“好”石头并将其杀死;
  • 以及最后,当调用paintComponent()方法时。

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

  • 在某些情况下需要synchronized修饰符(如果我删除它,我会得到一个异常,OK,这就是我要做的事情)另外一些情况下,我删除了synchronized修饰符expected);
  • in 我从未得到任何异常,甚至越来越多地运行程序,创建并杀死大量的杀手石和/或好石。

我谷歌了很多,但我读到当方法访问共享对象时总是需要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()和while...
  • ...addStone() ()需要synchronized修饰符,因为这些方法由我创建的其他线程调用,而killNeighbouringGoodStones和killStone()可能不同步,因为这些方法由支持图形用户界面的killNeighbouringGoodStones侦听器调用。

这是真的吗?

EN

回答 2

Stack Overflow用户

发布于 2019-05-20 05:43:54

  1. 在“我没有得到异常”、“代码是正确的”和“当前部署的代码工作正常”之间存在天壤之别。
  2. 如果可变对象受同步块保护,则对该对象的每次访问都必须受同一监视器上同步块的保护。否则,代码就不是线程安全的,可能会发生不好的事情(这对读取和写入都适用)。
  3. 如果代码不是线程安全的,它可能工作得足够好,特别是,如果你正在编写游戏,而不是银行系统。真正棘手的错误可能只有在特定情况下才会出现:在某些特定版本的JVM上,当某些附加条件为真时,每十亿次执行中就会出现一个错误。etc.
  4. The异常可能不是典型的线程错误:它们来自于HashSet是一个快速失败的集合,并且不允许您同时使用不同的迭代器从其中删除元素。换句话说:即使你同步了HashSet本身,你仍然会得到这个错误,即使--从纯线程安全的角度--代码是正确的。
  5. 已经评论过,你的设计并不真正适合使用线程。因此,我假设您这样做是为了好玩/学习Java线程。因此,正确地做事情尤其重要:-)
票数 3
EN

Stack Overflow用户

发布于 2019-05-20 01:31:30

如果多个线程试图同时更改同一对象,则使用同步。正如你注意到的,如果从你的一些方法中移除synchronized,你会得到一个异常,因为所有这些方法都试图同时修改HashSet。

您必须确保以下情况不会同时发生(由两个不同的线程触发):

1. Iteration over the values in the the HashSet
2. Modification of the Data inside the HashSet

从代码风格的角度来看,同步从来都不是“强制性的”。这取决于开发人员找出在哪里使用它,以便您的代码正确工作。

您还需要查看“volatile”关键字,以确保所有线程都能看到相同的内容。

票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/56209755

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档