The docs say that you shouldn't lock on an instance of a value-based Java class such as Optional because code
may produce unpredictable results if it attempts to distinguish two references to equal values of a value-based class ... indirectly via an appeal to synchronization...
Why should Java's value-based classes not be serialized? asserts
Because future JVM implementations might not use object headers and reference pointers for value-based classes, some of the limitations are clear. (E.g. not locking on an identity which the JVM must not uphold. A reference on which is locked could be removed and replaced by another later, which makes releasing the lock pointless and will cause deadlocks).
I.E. that the prohibition is future-proofing. But there's no reference for that assertion.
If future-proofing is the basis, I'd like a reference for it. If not, I'd like to understand what the basis is since value-based objects are Objects.
EDIT
BTW, I understand the reasons not to lock on Integers and other primitive-wrapper classes; they may be cached. But I can find no documentation saying the same is true of value-based classes, and while Integer, &etc. are based on values, they are not value-based classes. I.E. The JavaDocs of Optional &etc. explicitly say
This is a value-based class
The same is not true for Integer, &etc.
Here's what a Blog post by Nicolai Parlog says about value-based classes:
In Java 8 value types are preceded by value-based classes. Their precise relation in the future is unclear but it could be similar to that of boxed and unboxed primitives (e.g. Integer and int). Additionally, the compiler will likely be free to silently switch between the two to improve performance. Exactly that switching back and forth, i.e. removing and later recreating a reference, also forbids identity-based mechanisms to be applied to value-based classes.
So what Nicolai is saying is this:
In the future, compilers may do things that transparently translate between values and value-based classes in ways that do not preserve object identity.
Certain things ("identity-based mechanisms") depend on object identity. Examples include the semantics of == for references, identity hashcode, primitive locking, and object serialization.
For those things, there is the potential that the transparent translation won't be transparent.
In the case of primitive locking, the concern is that something like the following sequence may occur.
An instance of a value-based class is created.
The instance is converted to a value behind the scenes.
The value is then converted back, giving a different object.
If two threads then use "the instance" as a primitive lock, they could be unaware that in fact there are in fact two objects (now). If they then attempted to synchronize, they would (could) be locking different objects. That would mean there was no mutual exclusion on whatever the state was that the locking was intended to protect.
If you don't lock on a value-based class, you won't have to worry about that potential hazard ... in the future.
But note, that Nicolai's blog posting is one person's speculation on what might happen in Java 10 or later.
BTW, I understand the reasons not to lock on Integers and other primitive-wrapper classes; they may be cached.
Caching is not the problem per se, but a mechanism that gives rise to the problem. The real problem is that it is difficult to reason about the object identity of the lock object, and hence whether the locking regime is sound.
With the the primitive wrappers, it is the semantics of boxing and unboxing that gives rise uncertainty of object identity. Going forward, the mooted value type <-> object conversion would be another source of this uncertainty.
The above blog is based on "State of the Values" April 2014. John Rose, Brian Goetz, and Guy Steele which talks about adding value types to a future version of Java. This note is a position statement rather than a fully spec'd (and adopted) proposal. However the note does give us this hint:
"Many of the above restrictions correspond to the restrictions on so-called value-based classes. In fact, it seems likely that the boxed form of every value type will be a value-based class."
which could be read as implying that there will be a relationship between value types and existing value-based classes. (Especially if you read between the lines of the Java 8 description of value-based classes.)
UPDATE - 2019/05/18
Value types didn't make it into Java 12, and they are not (yet) on the list for Java 13.
However, it is already possible to demonstrate a problem that is related to the problem that the blog post talks about:
public class BrokenSync {
private final Integer lock = 1;
public void someMethod() {
synchronized (lock) {
// do something
}
}
}
The problem is that each instance of BrokenSync will create an Integer instance by auto-boxing 1. But the JLS says that Integer objects produced by auto-boxing are not necessarily distinct objects. So, you can end up with all instances of BrokenSync using the same Integer object as a lock.
A lock is associated with an object. If an object is shared, it's lock can be shared. Immutable value classes can be shared. In theory all references to a value object that has a particular semantic value could refer to one shared objects. It is common for creation code for value objects to reuse value objects. For example by caching previously created values. So in general when you have a reference to a value object your code should work correctly even if the value object is also used elsewhere. So don't use it for a lock.
All the needed information is right on the page you cite titled "Value-based Classes", although it's not written as clearly as it might be, and it doesn't use some magic phrases that would have clarified these issues.
The magic phrase I would use to describe this situation is that value-based classes can have implementation-defined behaviors. What the page says is that these classes "make no use of" reference equality ==. It doesn't say that the implementation may not define such an operator, but it does say, in essence, "we are remaining silent on this point". One of the phrases used is "make no commitment", which is a bit clearer.
For example, one kind of implementation might, say out of convenience, might make a value-based class V behave like most other objects and not bother suppressing the members that V makes no use of. Another might implement value-based classes differently, using the same internal mechanism as it uses for primitive-wrapper classes. (If you're building the VM, you don't have to implement every class in Java.) As long as the VM satisfies all the requirements (or if you like, contracts) as specified on this page, it's met its obligations to the user.
Now suppose you write code that locks such an object. The locking wasn't written to cope with this situation, so it will do something, but it need not be consistent from VM to VM.
To be specific as to your question, future-proofing is just a special case of implementation-defined behavior. How it's written today may not be how it's written tomorrow, even when both are legal.
Related
It is well-known that the type wrappers such as Integer, Double, Boolean are immutable. However, I was unable to find this documented in the official API documentation, e.g., https://docs.oracle.com/javase/8/docs/api/java/lang/Boolean.html. I also looked in the source code files, and did not find this stated in the comments. (The comments in the source code for String, on the other hand, does mention its immutability.)
Is this because:
- it's documented elsewhere (if so, where?),
- this fact is too "well-known", or
- the developer is expected to read the implementation of the wrappers and figure out whether the wrapper is immutable or not?
It is worth consideration that immutable can mean two things:
a) that if you pass this value somwhere it can't be mutated.
b) "a" and that it can be safely used in multithreaded environment
ad A) There are classes that are just immutable but not thread safe, they are good to be used with setters/getters and to be keys in HashMap - these ones with no mutators, with all fields private but without all fields final or volatile.
ad B) There are classes that are immutable and thread safe - these without mutators and with all fields private and final or volatile.
Classes that are thread safe are often described as so in the documentation or even by name, of course some classes can be immutable and/or thread safe but not strictly documented as so. For example the String class is documented to be "constant", but there is no information about thread safety - there is only one enigmatic statement "Because String objects are immutable they can be shared" but I think it means something different than ...shared with other threads. We just know the properties of popular classes, but I agree that these properties should be clearly documented. Unfortunatelly in real life they aren't. So the only way to know if class is immutable is to check the documentation and if there is not enough information, then check the implemetation and ask the author if He plans to make the class mutable in the future. This topic is considered in a book Java Concurrency in Practice, and the author suggest to use two annotations to denote that something is #ThreadSafe and/or #Immutable but unfortunatelly this isn't a common practice yet.
The boxed wrappers are "immutable" because they're virtually
interchangeable syntactically with the literal types they wrap. For example boolean is immutable:
boolean x = false;
x.flip(); // not implemented
Native types in most programming languages are immutable. Therefore, by the wrapper contract,
Boolean x = false;
x.mutate(/* ??? */);
is not defined either.
In Java, Optional is implemented as public final class Optional<T> { ... } and not as a sealed hierarchy of Some and None.
Why is this not the case here? Is this a workaround for the absence of sealed in Java? Is there any deeper reasoning behind it?
If you have a look at method implementations, you will see that by going this way cases it features ugly null checks:
public<U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent())
return empty();
else {
return Optional.ofNullable(mapper.apply(value));
}
}
They're not only ugly but if you have a longer method chain, isPresent will need to be evaluated during every call, even if the Optional is empty since the beginning.
If we could pass a fixed implementation down the chain, it could be avoided.
optional
.map(i -> i) // isPresent()
.map(Object::toString) // isPresent()
.map(String::length) // isPresent()
.map(...) // isPresent()
Question
Why weren't subtypes used to model empty and non-empty cases?
I'm not specifically asking why Optional is final, rather why it wasn't implemented with Some and None, as many other languages do, so Why is optional declared as a final class is not really helpful.
Summary
The final modifier on class Optional is there in preparation for a bigger feature: value types, a goal for Project Valhalla (features for Java 10+).
You can read all about value types for Java in the 2014 article linked below:
Value Types for Java
JEP 169 drafts the proposal for implementing value objects into Java.
Provide JVM infrastructure for working with immutable and reference-free objects, in support of efficient by-value computation with non-primitive types.
JEP 390 hints at the possibility of "migrating certain classes to become primitive classes":
The design and implementation of primitive classes is sufficiently mature that we can confidently anticipate migrating certain classes of the Java Platform to become primitive classes in a future release.
So what does this have to do with Optional?
In the 2014 paper, it's mentioned how value-based classes may act as the boxed version of value types:
In fact, it seems likely that the boxed form of every value type will be a value-based class.
Optional is a value-based class.
Why do we need value types in Java?
In Java, objects instantiated from reference types have an identity. This allows specific objects to be referenced by variables and compared by reference. All classes/enum/interfaces currently create reference types. Thus, all objects have an identity.
But, in theory, not all objects require an identity, as explicitly mentioned in the 2014 paper linked above:
Object identity serves only to support mutability, where an object’s state can be mutated but remains the same intrinsic object.
Identities aren't free, and immutable types don't require mutation, which inheritly means they don't require identities.
Identities for immutable types result in an excessive footprint:
Object identity has footprint and performance costs, which is a major reason Java, unlike other many object oriented languages, has primitives.
Implementing Value Types
James Gosling wrote an article back in 1999 about compiling immutable types to values:
It is almost possible, under the current language spec, for a sufficiently clever optimizing compiler to transform certain classes into lightweight objects that are not heap allocated and are passed by value rather than reference: declare the class and all its instance variables to be final.
This idea has been inherited by Oracle's experimental Project Valhalla, lead by Brian Goetz. In preparation, a specification for value-based classes has been created, which one of the requirements is for the class to be final.
The 2014 paper on value types in Java further exposes the decision to enforce the final requirement:
Can values participate in inheritance-based subtyping?
No.
Can a value class be abstract or non-final? No.
The decision to limit or prohibit subclassing and subtyping of value types is necessary to avoid pointer polymorphism.
We can therefore ensure that all methods are resolved unambiguously in the exact type of the method receiver. Invoking a value method is always like invokestatic or invokespecial and never like invokevirtual or invokeinterface.
Value types cannot participate in traditional subtyping (if at all, it would be limited).
In fact, this question is not new. and it is proposed by Natpryce in his book: Growing Object Oriented Software. the Maybe is more like as java.util.Optional but it represent 2 states by polymorphism. Indeed, it's faster than Optional since it applies State Pattern to transit the present state to absent state only once. which means the following state is always absent if the current state is absent.
But I want to say another scenario the Object-Oriented Principle: Law of Demeter
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
Each unit should only talk to its friends; don't talk to strangers.
Only talk to your immediate friends.
As you can see the LoD principle will avoid you to writing such a train wreck code which makes the code tight-coupling & breaking encapsulation and make it harder to change & maintain.
In a nutshell, if you according to the LoD principle you shouldn't write any map(one).map(another).map(...) chain calls in your program. from that point of view, there is no benefit to introduce an inheritance hierachy to represent its internal state. Since the State Pattern is harder to maintain if you introduce a new state and harder for debuging.
Then there are only 2 additional checking in the Optional than Maybe. one is the intermediate operation map & flatMap, another is is the terminal operation orElse, orElseGet and .etc. So there is no great advantage to applies State Pattern in Optional.
This Java tutorial
says that an immutable object cannot change its state after creation.
java.lang.String has a field
/** Cache the hash code for the string */
private int hash; // Default to 0
which is initialized on the first call of the hashCode() method, so it changes after creation:
String s = new String(new char[] {' '});
Field hash = s.getClass().getDeclaredField("hash");
hash.setAccessible(true);
System.out.println(hash.get(s));
s.hashCode();
System.out.println(hash.get(s));
output
0
32
Is it correct to call String immutable?
A better definition would be not that the object does not change, but that it cannot be observed to have been changed. It's behavior will never change: .substring(x,y) will always return the same thing for that string ditto for equals and all the other methods.
That variable is calculated the first time you call .hashcode() and is cached for further calls. This is basically what they call "memoization" in functional programming languages.
Reflection isn't really a tool for "programming" but rather for meta-programming (ie programming programs for generating programs) so it doesn't really count. It's the equivalent of changing a constant's value using a memory debugger.
The term "Immutable" is vague enough to not allow for a precise definition.
I suggest reading Kinds of Immutability from Eric Lippert's blog. Although it's technically a C# article, it's quite relevant to the question posed. In particular:
Observational immutability:
Suppose you’ve got an object which has the property that every time
you call a method on it, look at a field, etc, you get the same
result. From the point of view of the caller such an object would be
immutable. However you could imagine that behind the scenes the object
was doing lazy initialization, memoizing results of function calls in
a hash table, etc. The “guts” of the object might be entirely mutable.
What does it matter? Truly deeply immutable objects never change their
internal state at all, and are therefore inherently threadsafe. An
object which is mutable behind the scenes might still need to have
complicated threading code in order to protect its internal mutable
state from corruption should the object be called on two threads “at
the same time”.
Once created, all the methods on a String instance (called with the same parameters) will always provide the same result. You cannot change its behavoiur (with any public method), so it will always represent the same entity. Also it is final and cannot be subclassed, so it is guaranteed that all instances will behave like this.
Therefore from public view the object is considered immutable. The internal state does not really matter in this case.
Yes it is correct to call them immutable.
While it is true that you can reach in and modify private ... and final ... variables of a class, it is an unnecessary and incredibly unwise thing to do on a String object. It is generally assumed that nobody is going to be crazy enough do it.
From a security standpoint, the reflection calls needed to modify the state of a String all perform security checks. Unless you've miss-implement your sandbox, the calls will be blocked for non-trusted code. So you should have to worry about this as a way that untrusted code can break sandbox security.
It is also worth noting that the JLS states that using reflection to change final, may break things (e.g. in multi-threading) or may not have any effect.
From the viewpoint of a developer who is using reflection, it is not correct to call String immutable. There are actual Java developers using reflection to write real software every day. Dismissing reflection as a "hack" is preposterous. However, from the viewpoint of a developer who is not using reflection, it is correct to call String immutable. Whether or not it is valid to assume that String is immutable depends on context.
Immutability is an abstract concept and therefore cannot apply in an absolute sense to anything with a physical form (see the ship of Theseus). Programming language constructs like objects, variables, and methods exist physically as bits in a storage medium. Data degradation is a physical process which happens to all storage media, so no data can ever be said to be truly immutable. In addition, it is almost always possible in practice to subvert the programming language features intended to prevent the mutation of a particular datum. In contrast, the number 3 is 3, has always been 3, and will always be 3.
As applied to program data, immutability should be considered a useful assumption rather than a fundamental property. For example, if one assumes that a String is immutable, one may cache its hash code for reuse and avoid the cost of ever recomputing its hash code again later. Virtually all non-trivial software relies on assumptions that certain data will not mutate for certain durations of time. Software developers generally assume that the code segment of a program will not change while it is executing, unless they are writing self-modifying code. Understanding what assumptions are valid in a particular context is an important aspect of software development.
It can not be modified from outside and it is a final class, so it can not be subclassed and made mutable. Theese are two requirments for immutability. Reflection is considered as a hack, its not a normal way of development.
A class can be immutable while still having mutable fields, as long as it doesn't provide access to its mutable fields.
It's immutable by design. If you use Reflection (getting the declared Field and resetting its accessibility), you are circumventing its design.
Reflection will allow you to change the contents of any private field. Is it therefore correct to call any object in Java immutable?
Immutability refers to changes that are either initiated by or perceivable by the application.
In the case of string, the fact that a particular implementation chooses to lazily calculate the hashcode is not perceptible to the application. I would go a step further, and say that an internal variable that is incremented by the object -- but never exposed and never used in any other way -- would also be acceptable in an "immutable" object.
Yes it is correct. When you modified a String like you do in your example, a new String is created but the older one maintain its value.
I'm re-reading Java Concurrency In Practice, and I'm not sure I fully understand the chapter about immutability and safe publication.
What the book says is:
Immutable objects can be used safely by any thread without additional
synchronization, even when synchronization is not used to publish
them.
What I don't understand is, why would anyone (interested in making his code correct) publish some reference unsafely?
If the object is immutable, and it's published unsafely, I understand that any other thread obtaining a reference to the object would see its correct state, because of the guarantees offered by proper immutability (with final fields, etc.).
But if the publication is unsafe, another thread might still see null or the previous reference after the publication, instead of the reference to the immutable object, which seems to me like something no-one would like.
And if safe publication is used to make sure the new reference is seen by all the threads, then even if the object is just effectively immutable (no final fields, but no way to mute them), then everything is safe again. As the book says :
Safely published effectively immutable objects can be used safely by
any thread without additional synchronization.
So, why is immutability (vs. effective immutability) so important? In what case would an unsafe publication be wanted?
It is desirable to design objects that don't need synchronization for two reasons:
The users of your objects can forget to synchronize.
Even though the overhead is very little, synchronization is not free, especially if your objects are not used often and by many different threads.
Because the above reasons are very important, it is better to learn the sometimes difficult rules and as a writer, make safe objects that don't require synchronization rather than hoping all the users of your code will remember to use it correctly.
Also remember that the author is not saying the object is unsafely published, it is safely published without synchronization.
As for your second question, I just checked, and the book does not promise you that another thread will always see the reference to the updated object, just that if it does, it will see a complete object. But I can imagine that if it is published through the constructor of another (Runnable?) object, it will be sweet. That does help with explaining all cases though.
EDIT:
effectively immutable and immutable
The difference between effectively immutable and immutable is that in the first case you still need to publish the objects in a safe way. For the truly immutable objects this isn't needed. So truly immutable objects are preferred because they are easier to publish for the reasons I stated above.
So, why is immutability (vs. effective immutability) so important?
I think the main point is that truly immutable objects are harder to break later on. If you've declared a field final, then it's final, period. You would have to remove the final in order to change that field, and that should ring an alarm. But if you've initially left the final out, someone could carelessly just add some code that changes the field, and boom - you're screwed - with only some added code (possibly in a subclass), no modification to existing code.
I would also assume that explicit immutability enables the (JIT) compiler to do some optimizations that would otherwise be hard or impossible to justify. For example, when using volatile fields, the runtime must guarantee a happens-before relation with writing and reading threads. In practice this may require memory barriers, disabling out-of-order execution optimizations, etc. - that is, a performance hit. But if the object is (deeply) immutable (contains only final references to other immutable objects), the requirement can be relaxed without breaking anything: the happens-before relation needs to be guaranteed only with writing and reading the one single reference, not the whole object graph.
So, explicit immutability makes the program simpler so that it's both easier for humans to reason and maintain and easier for the computer to execute optimally. These benefits grow exponentially as the object graph grows, i.e. objects contain objects that contain objects - it's all simple if everything is immutable. When mutability is needed, localizing it to strictly defined places and keeping everything else immutable still gives lots of these benefits.
I had the exact same question as the original poster when finishing reading chapters 1-3 . I think the authors could have done a better job elaborating on this a bit more.
I think the difference lies therein that the internal state of effectively immutable objects can be observed to be in an inconsistent state when they are not safely published whereas the internal state of immutable objects can never be observed to be in an inconsistent state.
However I do think the reference to an immutable object can be observed to be out of date / stale if the reference is not safely published.
"Unsafe publication" is often appropriate in cases where having other threads see the latest value written to a field would be desirable, but having threads see an earlier value would be relatively harmless. A prime example is the cached hash value for String. The first time hashCode() is called on a String, it will compute a value and cache it. If another thread which calls hashCode() on the same string can see the value computed by the first thread, it won't have to recompute the hash value (thus saving time), but nothing bad will happen if the second thread doesn't see the hash value. It will simply end up performing a redundant-but-harmless computation which could have been avoided. Having hashCode() publish the hash value safely would have been possible, but the occasional redundant hash computations are much cheaper than the synchronization required for safe publication. Indeed, except on rather long strings, synchronization costs would probably negate any benefit from caching.
Unfortunately, I don't think the creators of Java imagined situations where code would write to a field and prefer that it should be visible to other threads, but not mind too much if it isn't, and where the reference stored to the field would in turn identify another object with a similar field. This leads to situations writing semantically-correct code is much more cumbersome and likely slower than code which would be likely to work but whose semantics would not be guaranteed. I don't know any really good remedy for that in some cases other than using some gratuitous final fields to ensure that things get properly "published".
I am wondering about the benefits of having the string-type immutable from the programmers point-of-view.
Technical benefits (on the compiler/language side) can be summarized mostly that it is easier to do optimisations if the type is immutable. Read here for a related question.
Also, in a mutable string type, either you have thread-safety already built-in (then again, optimisations are harder to do) or you have to do it yourself. You will have in any case the choice to use a mutable string type with built-in thread safety, so that is not really an advantage of immutable string-types. (Again, it will be easier to do the handling and optimisations to ensure thread-safety on the immutable type but that is not the point here.)
But what are the benefits of immutable string-types in the usage? What is the point of having some types immutable and others not? That seems very inconsistent to me.
In C++, if I want to have some string to be immutable, I am passing it as const reference to a function (const std::string&). If I want to have a changeable copy of the original string, I am passing it as std::string. Only if I want to have it mutable, I am passing it as reference (std::string&). So I just have the choice about what I want to do. I can just do this with every possible type.
In Python or in Java, some types are immutable (mostly all primitive types and strings), others are not.
In pure functional languages like Haskell, everything is immutable.
Is there a good reason why it make sense to have this inconsistency? Or is it just purely for technical lower level reasons?
What is the point of having some
types immutable and others not?
Without some mutable types, you'd have to go the whole hog to pure functional programming -- a completely different paradigm than the OOP and procedural approaches which are currently most popular, and, while extremely powerful, apparently very challenging to a lot of programmers (what happens when you do need side effects in a language where nothing is mutable, and in real-world programming of course you inevitably do, is part of the challenge -- Haskell's Monads are a very elegant approach, for example, but how many programmers do you know that fully and confidently understand them and can use them as well as typical OOP constructs?-).
If you don't understand the enormous value of having multiple paradigms available (both FP one and ones crucially relying on mutable data), I recommend studying Haridi's and Van Roy's masterpiece, Concepts, Techniques, and Models of Computer Programming -- "a SICP for the 21st Century", as I once described it;-).
Most programmers, whether familiar with Haridi and Van Roy or not, will readily admit that having at least some mutable data types is important to them. Despite the sentence I've quoted above from your Q, which takes a completely different viewpoint, I believe that may also be the root of your perplexity: not "why some of each", but rather "why some immutables at all".
The "thoroughly mutable" approach was once (accidentally) obtained in a Fortran implementation. If you had, say,
SUBROUTINE ZAP(I)
I = 0
RETURN
then a program snippet doing, e.g.,
PRINT 23
ZAP(23)
PRINT 23
would print 23, then 0 -- the number 23 had been mutated, so all references to 23 in the rest of the program would in fact refer to 0. Not a bug in the compiler, technically: Fortran had subtle rules about what your program is and is not allowed to do in passing constants vs variables to procedures that assign to their arguments, and this snippet violates those little-known, non-compiler-enforceable rules, so it's a but in the program, not in the compiler. In practice, of course, the number of bugs caused this way was unacceptably high, so typical compilers soon switched to less destructive behavior in such situations (putting constants in read-only segments to get a runtime error, if the OS supported that; or, passing a fresh copy of the constant rather than the constant itself, despite the overhead; and so forth) even though technically they were program bugs allowing the compiler to display undefined behavior quite "correctly";-).
The alternative enforced in some other languages is to add the complication of multiple ways of parameter passing -- most notably perhaps in C++, what with by-value, by-reference, by constant reference, by pointer, by constant pointer, ... and then of course you see programmers baffled by declarations such as const foo* const bar (where the rightmost const is basically irrelevant if bar is an argument to some function... but crucial instead if bar is a local variable...!-).
Actually Algol-68 probably went farther along this direction (if you can have a value and a reference, why not a reference to a reference? or reference to reference to reference? &c -- Algol 68 put no limitations on this, and the rules to define what was going on are perhaps the subtlest, hardest mix ever found in an "intended for real use" programming language). Early C (which only had by-value and by-explicit-pointer -- no const, no references, no complications) was no doubt in part a reaction to it, as was the original Pascal. But const soon crept in, and complications started mounting again.
Java and Python (among other languages) cut through this thicket with a powerful machete of simplicity: all argument passing, and all assignment, is "by object reference" (never reference to a variable or other reference, never semantically implicit copies, &c). Defining (at least) numbers as semantically immutable preserves programmers' sanity (as well as this precious aspect of language simplicity) by avoiding "oopses" such as that exhibited by the Fortran code above.
Treating strings as primitives just like numbers is quite consistent with the languages' intended high semantic level, because in real life we do need strings that are just as simple to use as numbers; alternatives such as defining strings as lists of characters (Haskell) or as arrays of characters (C) poses challenges to both the compiler (keeping efficient performance under such semantics) and the programmer (effectively ignoring this arbitrary structuring to enable use of strings as simple primitives, as real life programming often requires).
Python went a bit further by adding a simple immutable container (tuple) and tying hashing to "effective immutability" (which avoids certain surprises to the programmer that are found, e.g., in Perl, with its hashes allowing mutable strings as keys) -- and why not? Once you have immutability (a precious concept that saves the programmer from having to learn about N different semantics for assignment and argument passing, with N tending to increase with time;-), you might as well get full mileage out of it;-).
I am not sure if this qualifies as non-technical, nevertheless: if strings are mutable, then most(*) collections need to make private copies of their string keys.
Otherwise a "foo" key changed externally to "bar" would result in "bar" sitting in the internal structures of the collection where "foo" is expected. This way "foo" lookup would find "bar", which is less of a problem (return nothing, reindex the offending key) but "bar" lookup would find nothing, which is a bigger problem.
(*) A dumb collection that does a linear scan of all keys on each lookup would not have to do that, since it would naturally accomodate key changes.
There is no overarching, fundamental reason not to have strings mutable. The best explanation I have found for their immutability is that it promotes a more functional, less side-effectsy way of programming. This ends up being cleaner, more elegant, and more Pythonic.
Semantically, they should be immutable, no? The string "hello" should always represent "hello". You can't change it any more than you can change the number three!
Not sure if you would count this as a 'technical low level' benefit, but the fact that immutable string is implicitly threadsafe saves you a lot of effort of coding for thread safety.
Slightly toy example...
Thread A - Check user with login name FOO has permission to do something, return true
Thread B - Modify user string to login name BAR
Thread A - Perform some operation with login name BAR due to previous permission check passing against FOO.
The fact that the String can't change saves you the effort of guarding against this.
If you want full consistency you can only make everything immutable, because mutable Bools or Ints would simply make no sense at all. Some functional languages do that in fact.
Python's philosophy is "Simple is better than complex." In C you need to be aware that strings can change and think about how that can affect you. Python assumes that the default use case for strings is "put text together" - there is absolutely nothing you need to know about strings to do that. But if you want your strings to change, you just have to use a more appropriate type (ie lists, StringIO, templates, etc).
In a language with reference semantics for user-defined types, having mutable strings would be a desaster, because every time you assign a string variable, you would alias a mutable string object, and you would have to do defensive copies all over the place. That's why strings are immutable in Java and C# -- if the string object is immutable, it does not matter how many variables point to it.
Note that in C++, two string variables never share state (at least conceptionally -- technically, there might be copy-on-write going on, but that is getting out of fashion due to inefficiencies in multi-threading scenarios).
If strings are mutable, then many consumers of a string will have to to make copies of it. If strings are immutable, this is far less important (unless immutability is enforced by hardware interlocks, it might not be a bad idea for some security-conscious consumers of a string to make their own copies in case the strings they're given aren't as immutable as they should be).
The StringBuilder class is pretty good, though I think it would be nicer if it had a "Value" property (read would be equivalent to ToString, but it would show up in object inspectors; write would allow direct setting of the whole content) and a default widening conversion to a string. It would have been nice in theory to have MutableString type descended from a common ancestor with String, so a mutable string could be passed to a function which didn't care whether a string was mutable, though I suspect that optimizations which rely on the fact that Strings have a certain fixed implementation would have been less effective.
The main advantage for the programmer is that with mutable strings, you never need to worry about who might alter your string. Therefore, you never have to consciously decide "Should I copy this string here?".