Java:如果访问是只读的,两个线程是否可以安全地访问同一个方法

问题描述:

前言

我一天中只编写了几个多线程程序.当我必须这样做时,通常每两年一次.我正在尝试获得更多关于该主题的教育,我正在阅读实践中的 Java 并发".我有一个基本的了解.

I have only written a couple of multithreaded programs in my day. It's usually once every two years when I have to do this. I'm trying to get more educated on the subject and I'm reading "Java Concurrency in Practice." I have a basic understanding.

概述:

通常,我从不跨线程共享对象,因为它更容易,而且在大多数情况下,我只是想避免基本的阻塞情况.但是,我有一个用例,其中跨线程共享对象是有意义的.

Typically, I never share objects across threads because it's easier and in most cases I'm just trying to avoid basic blocking scenarios. However, I have a use case where it makes sense to share an object across threads.

我的 JSONBmBindMappingRow 在我的主线程中实例化(此处不包括不同的类).私有对象 BMBindMappingRow 在 JSONBmBindMappingRow 中设置.您可以将 JSONBmBindMappingRow 类视为不可变的;虽然,它绝对不是.但是,我将在我的程序中以这种方式对待它.

My JSONBmBindMappingRow is instantiated in my main thread (different class not included here). A private object BMBindMappingRow is set in JSONBmBindMappingRow . You can think of the JSONBmBindMappingRow class as immutable; although, it's definitely not. However, I will be treating it that way in my program.

JSONBmBindMappingRow 实例化后,它可以分配给多个线程,这些线程将调用 getJsonRow().

After the JSONBmBindMappingRow is instantiated it can be assigned to multiple threads which will call getJsonRow().

问题:

我的问题的范围如下:如果两个或多个线程同时访问 getJsonRow() 是否该线程安全,因为它们在自己的内存缓存中都有 JSONBmBindMappingRow 的副本?我认为它是安全的,不需要同步,但我会把它留给专家.

The scope of my question is as follows: If two or more threads access the getJsonRow() at the sametime is this thread-safe since both will have a copy of the JSONBmBindMappingRow in there own memory cache? I think it's safe and synchronization is not needed, but I will leave it to the experts.

如果两个线程同时访问,这段代码线程安全吗?

  public JSONRow getJsonRow()
  {
    JSONRow jrow = new JSONRow();
    for (Integer index: bbmr.getColumnMappingAll().keySet()) {
       BMFieldMapping bm = bbmr.getColumnMapping(index);
       if (bm.ws_field_name != null && !bm.ws_field_name.equalsIgnoreCase("")) {
          jrow.add(new JSONField(bm.ws_field_name, bm.getJavaDataType(), 1));
       }
    }
    return jrow;
  }

JSONBmBindMappingRow 类:

  package xxfi.oracle.apps.ws.json.row;

  import java.sql.Connection;
  import java.sql.SQLException;

  import oracle.jdbc.OracleCallableStatement;
  import oracle.jdbc.OracleResultSet;
  import oracle.jdbc.OracleTypes;

  import xxfi.oracle.apps.ws.bm.BMBindMappingRow;
  import xxfi.oracle.apps.ws.bm.BMFieldMapping;
  import xxfi.oracle.apps.ws.utility.JDBC;


  public class JSONBmBindMappingRow implements JSONRowBuildImpl
  {
     private BMBindMappingRow bbmr = new BMBindMappingRow();
     private Connection conn = null;
     private String tableName = null;
     private String className = this.getClass().getCanonicalName() ;

     public JSONBmBindMappingRow(Connection conn, String tableName)
     {
        this.tableName = tableName;
        this.conn = conn;
        init();
     }

     public void init()
     {
        setColumnBindMappings();
     }


     public void setColumnBindMappings()
     {
        StringBuffer plSql = new StringBuffer();

        plSql.append("DECLARE ");
        plSql.append("BEGIN ");
        plSql.append("   :1 := xxfi_bm_custom_table_api.get_column_binds ( ");
        plSql.append("  :2       /*tablename*/");
        plSql.append(");");
        plSql.append("END;");

        OracleCallableStatement oracleCallableStatement = null;
        OracleResultSet oracleResultSet = null;
        try {
           oracleCallableStatement = (OracleCallableStatement) this.conn.prepareCall(plSql.toString());
           oracleCallableStatement.registerOutParameter(1, OracleTypes.CURSOR);
           JDBC.nullSafe(oracleCallableStatement, 2, tableName, OracleTypes.VARCHAR);

           oracleCallableStatement.execute();

           // get cursor and cast it to ResultSet
           oracleResultSet = (OracleResultSet) oracleCallableStatement.getCursor(1);

           // loop it like normal
           while (oracleResultSet.next()) {
              bbmr.add(new BMFieldMapping(oracleResultSet.getString("ws_field_name"), 
                       oracleResultSet.getString("column_name"), oracleResultSet.getString("data_type"), 
                       oracleResultSet.getInt("bind_number")));

           }
         } catch (Exception e) {
           try {
              this.conn.rollback();
           } catch (SQLException f) {
              // TODO
           }
           System.out.println("Error in "+className+".setColumnBindMappings(): " + e);
           e.printStackTrace();

        } finally {
           JDBC.close(oracleCallableStatement, oracleResultSet);
        }
     }

     public String getArrayName()
     {
        return "";
     }

     public JSONRow getJsonRow()
     {
        JSONRow jrow = new JSONRow();
        for (Integer index: bbmr.getColumnMappingAll().keySet()) {
           BMFieldMapping bm = bbmr.getColumnMapping(index);
           if (bm.ws_field_name != null && !bm.ws_field_name.equalsIgnoreCase("")) {
              jrow.add(new JSONField(bm.ws_field_name, bm.getJavaDataType(), 1));
           }
        }
        return jrow;
     }

     public BMBindMappingRow getBbmr()
     {
        return bbmr;
     }
  }

不,您的代码不是线程安全的.主要问题是您仍然需要提供同步,否则 Java 没有义务使您的写入可见.

No your code is not thread safe. The major problem is that you still need to provide synchronization or Java is under no obligation to make your writes visible.

在您的 ctor 期间,您构建了一个私有对象 BMBindMappingRow.Java 没有义务使这些写入对其他线程的后续读取可见.如果 ctor 在一个线程(一个 CPU/核心)上被调用,然后被另一个线程上的不同 CPU 读取,那么你可能会遇到大问题.

During your ctor, you build a private object BMBindMappingRow. Java is under no obligation to make those writes visible to later reads by a different thread. If the ctor is called on one thread (one CPU/core) and then read by a different CPU on a different thread, you could have a big problem.

基本思想是以某种方式进行同步,以便 Java 知道它需要使这些写入可见.给你的代码最简单的方法是制作BMBindMappingRow final.Java 保证在 ctor 的末尾会有一个针对 final 字段的同步事件,并且它的所有写入都将可见.

The basic idea is to synchronize somehow so that Java know it needs to make those writes visible. The easiest way give your code is to make the BMBindMappingRow final. Java guarantees at the end of the ctor there will be a synchronization event for the final field and all of its writes will be made visible.

同样的想法适用于您的所有字段(实例变量).String 是不可变的,因此是线程安全的.我相信 Connection 也是线程安全的.但是,您在类中分配的字段不是线程安全的,因此您需要对此做一些事情.您写入 ctor 中的这些字段,因此您必须再次提供某种形式的同步.同样,我认为最简单的方法就是将它们全部定型.

The same idea applies to all of your fields (the instance variables). String is immutable and therefore thread safe. I believe Connection is thread safe too. However, the fields you assign in your class are not thread safe so you need to do something about that. You write to those fields in the ctor so again you must provide some form of synchronization. Again I think the easiest is just to make them all final.

final private BMBindMappingRow bbmr = new BMBindMappingRow();
final private Connection conn;
final private String tableName;
final private String className = this.getClass().getCanonicalName() ;

我鼓励您阅读 final 字段的语义. Java 语言规范的第 17 章处理多线程并且非常易读.有些地方有点厚,但仍然是你应该能够犁过的东西.

I encourage you to read up on the semantics of final fields. Chapter 17 of the Java language spec deals with multi-threading and is very readable. It's a bit thick in places but still something you should be able to plow through.

请注意,使用 final 这种方式对 NEVER MODIFY final 字段或它指向的对象非常重要,否则您会破坏 final 提供.在这种情况下,您需要返回 synchronizedjava.util.concurrent 中的某些类,以提供对更改的可见性.

Note that it's super important using final this way to NEVER MODIFY the final field or the object it points to, or you break the synchronization that final provides. In that case you'll need to go back to synchronized or some classes from java.util.concurrent to provide visibility to your changes.

既然您在下面的评论中提到了类变量,我将大刀阔斧地向您指出 Java 并发实践 作者:Brian Goetz.这是一本关于 Java 并发的书,也是我找到的唯一一本没有错误的书.我在这里花了一些通读和问题来完全理解它,但仔细阅读整本书是完全值得的.阅读规范也很好,但这本书解释的更详细,也有有用的例子.成为 Java 专家!

Since you mentioned class variables below in the comments, I'll get out the big guns and point you towards Java Concurrency in Practice by Brian Goetz. It is the book on Java concurrency and the only one I've found with no mistakes. It took me a couple of read throughs and questions here to fully grok it, but it's totally worth it to read the entire book carefully. Reading the spec is good too but this book explains things in much more detail and has useful examples too. Be a Java expert!