为什么在使用getter和setter时线程不安全的类不能工作?

时间:2015-05-15 14:02:35

标签: java multithreading thread-safety synchronized

我有一个课程如下:

public class BoolFlag
{
boolean flag;

public BoolFlag()
{
    flag=false;
}

public synchronized void setFlag(boolean flag)
{
    this.flag=flag;
}

public synchronized boolean getFlag()
{
    return flag;
}
}

如果我没有将getter和setter设置为同步,则会导致问题如下:

当我在一个buttonLlick listiner中设置一个名为imageready的BoolFlag对象时:

 if (status == JFileChooser.APPROVE_OPTION)
           { 
               try
               {
                    System.out.println("File chosen");
                    imgFile=chooser.getSelectedFile();  
                    image.setImg( ImageIO.read(imgFile) );
                    imageready.setFlag(true);

               }
               catch(Exception e)
               {
                   System.out.println("file not opened");
                }
            }

然后在另一个线程中获取它:

public void run()
{
    boolean running=true;
    while(running)
    {
        // System.out.println("Img sender running");
        if(imageready.getFlag())
        { System.out.println("trying to send img");
            try
            {
                  OutputStream outputStream = img_s.getOutputStream();
                  ImageIO.write(image.getImg(), "jpg", outputStream);

                  System.out.println("Send image"+System.currentTimeMillis());
                  outputStream.flush();
                  outputStream.close();
                  imageready.setFlag(false);

            }
            catch(Exception e)
            {
                System.out.println("image client send failed");
                imageready.setFlag(false);
            }
        }
     }
 }

我期望在选择文件后,将imageready设置为true,然后获取该标志的线程执行块内的语句:

if(imageready.getFlag()){...send image...}

但即使imageready设置为true,它也不会进入块中。

正如您可能注意到的那样,我在if块之前发表了一条声明:

// System.out.println("Img sender running");

如果我没有将其注释掉,那么if块中的语句将被执行。使用其他不相关的语句(如sleep(100))可能会产生相同的效果。如果我在if(imageready.getflag())上放置一个断点,然后逐步执行它,它也会进入块。

如果我将getter和setter设置为同步,则不会发生这些问题。 似乎它是关于Tread安全类的东西,但我无法弄清楚为什么它很重要,即使我只是使用getter和setter。

3 个答案:

答案 0 :(得分:1)

您的get访问器很可能被内联,因此它实际上只是一个字段读取,并且运行时可以优化出现在循环中的简单字段读取,以使该值有效地只读取一次。

您可以通过读取volatile值来避免这种情况,或者正如您所注意到的那样,通过在写入和读取标志时使用synchronized强制使用内存屏障来避免这种情况。

在您的情况下,我只需使用AtomicBoolean代替您的BoolFlag课程。

另一方面,我会避免忙着准备你的"图像准备好"第二个线程上的标志。考虑使用等待通知机制,例如CountDownLatch计数为1

答案 1 :(得分:1)

是的,这是由于线程沟通,你想要实现的是Product-Consumer problem

您的代码几乎没有问题:

1)写入图像文件的线程类在getFlag()方法中没有被阻塞,因此你的while循环可能会运行很多,使线程保持活动状态。而是阻止getFlag()中的线程,并使用wait()notifyAll()方法进行有效的线程通信。

Java example of producer-consumer problem。在你的案例中你需要做类似的事情来管理线程。

答案 2 :(得分:0)

同步不提供编写线程安全类的工具。在多线程环境中,如果一个对象在线程之间共享,则没有可能影响程序正确性的问题,如数据争用,脏读,过时值。

优化代码java保存内存缓冲区中的变量数据,以便更快地访问。由于其他线程对该变量所做的更改可能无法立即反映这就是为什么其他线程读取变量的先前值因此无法在不久的将来读取更新的值。

您可以将变量定义为volatile。易失性确保线程将获得Java Memory Model提交的变量的最近更新值。