3

I have seen examples where even though the object is immutable there are situations in which we need to update some fields and as I understand it, the object is passed to a constructor which makes a copy with updated values but the original is not changed. Fine, but what if two threads want to do this? Do we not run into synchronization problems if they both are changing the same field? And how if now there are new objects made (rather than a changed original) do multiple threads know that the values of fields have changed?

I have seen many examples of how to make an immutable class but it is not clear to me how updating attributes works in a multithreaded environment, how synchronization is not required and as I mentioned above, how to determine what object now represents the current state.

Here is an article in which changing an immutable object is discussed: https://jlordiales.me/2012/12/24/the-ins-and-outs-of-immutability/

Robert Harvey
  • 199,517
Jeff
  • 149
  • 3
    Immutable means it doesn't change, period. You don't update fields in an immutable objects because this capability isn't possible. If you create a copy, then each thread will create its own copy, so there is no synchronization issue. Do you have any example of a scenario you do not understand? – Vincent Savard Jun 15 '18 at 18:47
  • Is it plausible to have a system of objects that never change? Can an account object which contains a balance be immutable, so that when the balance changes, a new account object is created or would account be an example of a class that would not be designed to be immutable? – Jeff Jun 15 '18 at 18:54
  • 5
    For accounts, instead of having some value that is changed, you'd instead model it as a series of immutable transactions, chained one right after another. And to get the current value (at any given point in time), you'd just add them all up. – Telastyn Jun 15 '18 at 19:20
  • Accounts have balances that could be derived by adding transactions but they also have other attributes like addresses and permissions where adding would not work, right? Where finding the latest timestamp I guess? Would Hibernate or other ORMs understand the merging process? How in general would Hibernate deal with immutable objects that get copied when attributes change? – Jeff Jun 15 '18 at 19:34
  • Your Account example is not cutting it - the Sum() would apply to the amounts of the Transactions – Henk Holterman Jun 15 '18 at 19:57
  • 2
    @Jeff: Google "temporal database". It's a thing. Records are immutable; they're never updated or deleted. You want to change an address, you create a new record with the updated address, the change time/date, and your user ID/key/etc. No record changes or disappears, the database becomes basically a bunch of snapshots of its last known state, and you can view the state of the account at any point in its entire history. Or you can track down when a change was made -- and if you stored it, also who made the change. – cHao Jun 15 '18 at 20:28
  • 1
    @Jeff: You can always transform any "mutable" value into an immutable value by adding a version field. I recommend reading some of Rich Hickey's articles and watching some of his presentations about Clojure; he explains very well the dangers of conflating identity, value, and state, and how Clojure fixes this. – Jörg W Mittag Jun 16 '18 at 06:49
  • @JörgWMittag: Thanks. My point is partially again that the discussions in for example the Java tutorials Oracle provides about immutables just tell you about thread safety after designing a class, not a word about practical matters. I hope people will find this question and answers useful therefore. – Jeff Jun 16 '18 at 07:24
  • 1
    "Can an account object which contains a balance be immutable, so that when the balance changes" – That is not how banking systems are designed. And in fact, it is not how banking systems in the real world work. Both in software and in the real world, you create an immutable transaction slip (in the real world, this used to be literally an actual slip of paper that was shipped by horse and carriage to the central office) and the balance is then simply the sum of the transactions. So, the transaction is data and the balance is a function, which is exactly the dual of what gets usually taught – Jörg W Mittag Jun 16 '18 at 10:32
  • @JörgWMittag: Right. Nonetheless, a customer address or privileges can change and summation would not work in that kind of situation. – Jeff Jun 16 '18 at 11:18
  • @Jeff: the idea of immutable transaction logs actually still work even for customer address and privileges. You may want to research about MVCC (multi version concurrency control), which is hope many modern, general purpose relational databases and filesystems are designed internally. Though this is usually considered implementation details, and applications don't really need to deal with the database' internal write ahead log directly. – Lie Ryan Jun 17 '18 at 14:05
  • @Jeff: Again, you can always transform anything you consider to be a mutable object by adding an "ID" and a "version" (or "timestamp") field. The ID allows you to know that this new object is actually "the same" (in some sense) and the version allows you to know which one is current. This always works, so it proves that you can always get rid of mutability. However, there are often better ways, as in my bank account example. Compilers are another example that have been traditionally implemented using mutable shared data structures. In the FP world, there is the saying "A compiler is just a … – Jörg W Mittag Jun 17 '18 at 19:35
  • "left fold over an abstract syntax tree" and compilers for FP languages that are written in FP languages often use purely functional immutable data structures. However, this might be considered obscure and niche, BUT(!) the current C♯ compiler by Microsoft is also implemented with an immutable tree at its core: any operation always returns a new copy of the (sub)tree it is working on. – Jörg W Mittag Jun 17 '18 at 19:37
  • You can always get rid of mutability for data structures. You can't always get rid of it everywhere -- the system where nothing changes is one where nothing happens -- but you can wall off the mutable bits. In languages that don't allow mutation, the system itself is hiding and handling those bits. – cHao Jun 18 '18 at 15:11
  • see also https://www.youtube.com/watch?v=2yXtZ8x7TXw – ctrl-alt-delor Jun 18 '18 at 18:38
  • If a thread does 1 + 2 and another thread does 1 - 3 does it corrupt the 1? No it does not. – user253751 Dec 11 '18 at 03:58

2 Answers2

9

Fine, but what if two threads want to do this?

Then one thread makes one change (and gets one version of the object) and another thread makes another change (and gets a different, independent version of the object).

Do we not run into synchronization problems if they both are changing the same field?

Nope, because each thread gets their own copy of the object.

And how if now there are new objects made (rather than a changed original) do multiple threads know that the values of fields have changed?

They don't. That's the point.

how to determine what object now represents the current state.

And that's the gotcha. Immutability works great when things are values, or when things really are independent. But if you have an object that represents a single record - say from a database - then changes need to be merged back into that single record. That can be last in wins. That can be some manner of versioning/timestamping. There's lots of options with various tradeoffs.

But the key bit is that synchronization only needs to happen at that last step where the changes are merged and actually take effect on the single source of truth.

Telastyn
  • 109,398
  • This is the first time in a discussion of immutability that I have read of merging changes -- can you provide an article which goes into this in some detail? In my experience, you have things like a customer account which changes or it is not useful. It sounds like you could have two separate CS reps modifying the account and then there would be two copies plus the original that would have to be merged at some point, right? – Jeff Jun 15 '18 at 19:11
  • @Jeff - something like https://en.wikipedia.org/wiki/Optimistic_concurrency_control is an example of one way to take different changes an integrate them back into the source of truth. – Telastyn Jun 15 '18 at 19:14
  • @Jeff the data model in your application is not necessarily the source of truth for this data. You would typically still have a single authoritative database that enforces a logically consistent data model (ACID transactions). These approaches work very well if your application handles short-lived requests rather than long-lived sessions and tears down the data model after each request. This has become the standard architecture for web applications. – amon Jun 15 '18 at 19:16
  • @Telastyn: so it sounds like persistence/database representation is central to this. I am familiar with timestamps when updating a db record but discussions of immutability don't seem to (in the ones I've read) go into dbs or timestamps. Rather, they mention how to make a class immutable and how in fact when you change an object you are really making a copy but the final step I have yet to see mentioned until your very helpful answer. – Jeff Jun 15 '18 at 19:25
  • @Telastyn: loved you "single source of truth" :D Keeping it to reference in the future. – Ignacio Soler Garcia Jun 15 '18 at 20:43
  • @jeff - that’s because immutability works a lot better with things that work like values - strings, dates, times, ranges, configurations, snapshots of data, etc. There is no need for that messy final step when there is no record to reconcile. It also makes for far simpler discussions/articles. – Telastyn Jun 15 '18 at 20:47
3

The article you linked talks about using the Builder Pattern to create an object. There's no conceptual difference between doing that and using a constructor. You simply treat all of the steps in the Builder as a single operation.

During the build process, you're not mutating an immutable object, because the object is not considered completely built until you call Build() on the builder, at which point you then have a fully constructed, immutable object.

This is also true of constructors. Many constructors have several steps within the body of the constructor method. All of these steps must be completed before the object is considered fully constructed and a usable immutable object results.


If you want to make a new immutable object from an existing immutable object with some of the values changed, what you now need is a new constructor or builder that takes an existing object as a parameter, and then set all of the attributes in the constructor or builder that you want to change.

For example:

public BookBuilder(Book book) {
    this.isbn = book.getIsbn();
    this.publicationYear = book.getPublicationYear();
    this.reviews = book.getReviews();
}

And then

Book originalBook = getRandomBook();

Book modifiedBook = new 
BookBuilder(originalBook).isbn("123456").publicationYear(2011).build();

The builder creates a copy of the original book, calls the methods in your fluent interfaces to set the desired fields to new values, and then the build() method freezes the new object in some way so that it becomes immutable.

Note that the author makes two concessions:

  1. The builder is not thread safe, and
  2. You still have to make copies of objects.

When you use constructors, you can set and change final or readonly members until the constructor finishes executing, at which point these members become unchangeable.

Robert Harvey
  • 199,517
  • My understanding is that he creates a copy of the original object with different attributes. Are you disagreeing with the article or my interpretation? – Jeff Jun 15 '18 at 23:38
  • No, that's how immutable objects work. That's how they work whether you use Builder or a constructor. He is not making the assertion that you are relieved from the overhead of making a copy; you still have to do that, even if you use a Builder. – Robert Harvey Jun 16 '18 at 03:19
  • But isn't the omission of a discussion of merging, etc. confusing? Obviously I found it so. – Jeff Jun 16 '18 at 07:26
  • What "merging" would you be referring to? See the update to my answer. – Robert Harvey Jun 16 '18 at 16:32
  • The first answer mentions merging. – Jeff Jun 16 '18 at 16:58