5 ms·
That's true, but in the case of Strings in particular they are generally considered to be thread-safe by virtue of being immutable (and the Javadocs themselves
by cempaka 3y ago
That's true, but in the case of Strings in particular they are generally considered to be thread-safe by virtue of being immutable (and the Javadocs themselves say this in many places). Concurrently modifying the input character array may seem like willful abuse in this case, but I suppose there might be some carelessly written code out there which does it and the post shows how it would create some weird and very hard-to-debug behavior. And I can see the argument going both ways as far as the Javadoc: on the one hand, it only promises to return a String representing what is currently in the input character array, which seems to warn against concurrent/subsequent modification at least obliquely. But on the other hand, it does seem to commit to an up-front defensive copy of the input character array which doesn't quite take place, and without which things go a bit haywire if the encoding of the string changes.
I don't think the correct answer is to sacrifice any performance to guard against this weird edge case, though.
- gizmo686 3y agoAs the article points out, the only thing the Javadoc guarantees is the subsequent modifications of the character array have no effect. It says nothing about concurrent modifications. The type whose thread safety is in question here is not actually String, but char[]. I'm not going to say it is always wrong to share char[] between threads (as a primitive array of something other than double and long, the Java Spec does make some guarentees about char[]), however it is almost always wrong to be sharing char[] between threads. If you want a language that protects you from this type of mistake, you simply need something more advanced than Java. This isn't even the biggest hole in Java's safety. Java has a type system, which is at least a nominal claim to type safety. Yet, you can do things like: Integer[] foo = new Integer[1]; Object[] bar = foo; bar[0] = new Object() And the compiler will let you (although the runtime won't). Its even worse with generics: List<Integer> foo = new ArrayList<>(); List bar = foo; bar.add(new Object()); System.out.println(foo.get(0)); Integer x = foo.get(0); Will compile, and won't even through an exception until the very last line, past the point where you have retrieved and used a non Integer from a List<Integer> In fairness to java on the last one, it is at least a warning, and a deliberate compromise to support backwards compatability when they introduced generics.
- kaba0 3y agoWhile you are technically right in all your points, I think you exaggerate the problems regarding Java’s type system. Arrays are indeed a pain point from different times (the mistake of their covariance even “infected” C#), but generics are not problematic at all. In fact, code that compiles without warning is guaranteed to never get a ClassCastException. Every language’s type system gives you escape hatches, so that last example is similar to how one might implement an optimization library in Rust where you have uninitiated elements. Hell, there you don’t even get runtime errors if you get it wrong, it will just segfault. So I really disagree with this notion of “it even worse with generics”.
- tialaramex 3y ago> how one might implement an optimization library in Rust where you have uninitiated elements The correct way to do this is use the MaybeUninit<T> type. Then you're responsible for correctly initializing a T before you call MaybeUninit<T>::assume_init() to get a T.
- kaba0 3y agoAnd that MaybeUninit type uses an unsafe call under the hood as an escape hatch from the "draconian" compiler's rules. Every practical language has these. (nonetheless, thanks for mentioning the MaybeUninit, that was what I was thinking of, but didn't remember the name -- I haven't programmed in Rust for a long time)
- tialaramex 3y agoMaybeUninit<T>::assume_init() is the unsafe call. The promise that this is actually a T and not just a T-sized blob of uninitialized memory is made by that unsafe call, and since it's unsafe it's your responsibility as the programmer to obey the constraint, that this is, in fact, an initialized T. Depending on how long "a long time" is, you might well not actually have been thinking of MaybeUninit. The prior solution (until mid-2019) was as you describe, and it was in fact unsound.
- pitkali 3y agoImmutable types are only considered thread-safe after their construction, though.
- elygre 3y agoI’d argue that while a string is thread safe, this is not actually a string yet.
- mmis1000 3y agoProbably guard intern() instead would be enough? It would be enough to stop the broken string from pollute the whole process permanently.