0

Preface

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.

Overview:

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.

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.

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

Question:

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.

Is this code thread safe if two threads access it at the same time?

  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 Class:

  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;
     }
  }
  • The Answer: yes. – f1sh Aug 10 '17 at 15:28
  • In general, accessing read only data is **NOT THREAD SAFE.** You need to do something special to tell Java when the data is thread safe. `volatile`, `final` and `synchronized` will help you out. Or use classes from `java.util.concurrent` which is by far the smarter/easier route. – markspace Aug 10 '17 at 15:46
  • The threads will be calling getJsonRow(). This is the required method for the JSONRowBuildImpl – icemanwiggins Aug 10 '17 at 15:51
  • I seen now that `jrow` is a local variable. That's OK, local variables are thread safe. The rest of the code is not however. – markspace Aug 10 '17 at 15:53
  • When you say, "The rest of the code is not however." I'm assuming your talking about the other methods and not getJsonRow(). So in short, the getJsonRow() method is thread-safe and each thread can interact with the returned JSONRow object safely. – icemanwiggins Aug 10 '17 at 15:59
  • See my answer below. I'm talking about all methods in the class, including `getJsonRow()` and the ctor. – markspace Aug 10 '17 at 16:02

3 Answers3

0

If your access is read-only and no data is changed during access by the thread, by definition your threads will will work on the same data and no race condition can arise.

drssdinblck
  • 260
  • 2
  • 11
0

Assuming that getJsonRow() returns a JSONRow that is completely initialized with all data and which is never modified after that, then it is safe for all threads to retrieve data from this method concurrently.

Jeeppp
  • 1,553
  • 3
  • 17
  • 39
0

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.

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.

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.

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() ;

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.

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.

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!

markspace
  • 10,621
  • 3
  • 25
  • 39
  • a sql connection is definitely NOT THREAD SAFE (it most surely does socket IO, for one thing). That's a show stopper for me on this use case, as a reviewer, I'd stop here and say : refactor it out (although maybe this connection object is used only in the constructor, and it may all word out OK in the end, I don't wan't to create the risk in the future of this connection being used at a point where it will fail). – GPI Aug 10 '17 at 16:16
  • Are you sure? Does the docs say that? Just because it's using socket IO doesn't mean it can't provide synchronization internally to make itself thread safe. I thought it was, but I'll go review the docs to be sure. – markspace Aug 10 '17 at 16:17
  • Connection is technically thread safe (**my bad** ! https://stackoverflow.com/questions/1531073/is-java-sql-connection-thread-safe) on compilant implementations, but only one thread can perform work at any given point in time. So you may create race conditions by sharing it. – GPI Aug 10 '17 at 16:20
  • 1
    @icemanwiggins I already answered that: no it ain't. You're writing memory during your ctor and trying to read back on a different thread (which calls `getJsonRow()` in this case). You can't do that unless you provide some additional information to Java telling it to make access by multiple threads safe. – markspace Aug 10 '17 at 16:23
  • 1
    @icemanwiggins : I get your question, but wheter the specific method you're asking works or not as is, i advise against putting your design in production. If you access a class from multiple threads, then it and its instance variables should also be statless (or thread safe, or reentrant, it depends, ...), and the connection object is not. Therefore, at some point in the future, you open up the possibility of real trouble. E.g. if 2 years from now somebody calls the `connection` variable inside `getJsonRow`, you're in deep trouble. – GPI Aug 10 '17 at 16:23
  • markspace: I saw the comments about final. I will review those. It's important to note the scope of my question. In short once the JSONBmBindMappingRow is instantiated, I will not be modifying it at all. I realize that it can be modified. So assuming JSONBmBindMappingRow class variable(s) won't modified, is the getJsonRow() method threadsafe? – icemanwiggins Aug 10 '17 at 16:27
  • Yeah being "technically thread safe" and working correctly in practice are two different things. If `Connection` has a history of being buggy in multi-threading environments I would definitely avoid using it that way in production environments. – markspace Aug 10 '17 at 16:28
  • @icemanwiggins No, it's not safe as written. You still need to "do something" so Java knows to make accesses thread safe. If you never modify the `JSONBmBindMappingRow` after the ctor, then `final` will do what you need. – markspace Aug 10 '17 at 16:29
  • @icemanwiggins I'd go with markspace's advice. Even with your assurance that the objects are never modified, it still depends on other things. For one, if the threads that access your shared objects were started before your objects were created and no other indicators are created (e.g. final, volatile, ...), then there is no visibility for those threads. And it's not a theoritcal risk, it does happen. – GPI Aug 10 '17 at 16:30
  • @markspace - thanks for the comments. Very helpful. I'll make it mostly thread-safe. I'll make init() and setColumnBindMappings() private. I'll also change the class variables to final. This makes changing the object difficult after it's create so the only real option is to create a new instance of the JSONBmBindMappingRow() if a developer needs to do something else. It's not a true singleton but it's pretty close. I prefer not to add a synchronization block in getJsonRow(), for what I'm doing it doesn't seem worth it. – icemanwiggins Aug 10 '17 at 17:05
  • @icemanwiggins Instance variables! "Class variables" have the `static` keyword modifier. And they work differently in multi-threaded applications, so make sure to study up later. – markspace Aug 10 '17 at 17:08
  • sorry wrong terminology, I do know the difference lol. I rarely use static variables, only for lazy initializations for config or loggers. – icemanwiggins Aug 10 '17 at 19:55