Java 中的功能等效
Functional Equivalence in Java
我正在阅读 Effective Java,遇到了 Joshua Bloch 推荐的情况
class MyComparator extends Comparator<String>{
private MyComparator(){}
private static final MyComparator INSTANCE = new MyComparator();
public int compare(String s1,String s2){
// Omitted
}
}
XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.
那么 总是 安全地创建一个静态最终 Object
任何 class 它指向 如果它没有字段?当从两个线程并行调用 compare
时,这不会导致多线程问题吗?或者我误解了一些基本的东西。如果没有共享字段,是否每个线程都具有执行自主权?
多线程问题是由不需要的状态更改引起的。如果没有更改的状态,则不会出现此类问题。这也是不可变对象在多线程环境中非常方便的原因。
在这种特殊情况下,该方法仅对输入参数 s1
和 s2
进行操作,并且不保留任何状态。
从两个线程并行调用 compare
方法是安全的(堆栈限制)。您传递给该方法的参数存储在该线程的堆栈中,任何其他线程都无法访问。
始终推荐不可变单例。避免创建可变单例,因为它们会在您的应用程序中引入全局状态,这很糟糕。
编辑:如果传递的参数是可变对象引用,那么您必须特别注意确保线程安全。
说明
So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?
视情况而定。只有当一个线程正在更改 something 而另一个线程正在同时使用它时,才会发生多线程问题。因为另一个线程可能不知道由于缓存和其他影响而发生的变化。或者它会导致一个纯逻辑错误,创建者没有考虑到线程可以在操作期间被中断。
所以当一个class是stateless的时候,在多线程环境下使用是绝对安全的。因为首先没有任何线程可以更改。
请注意,这也意味着 class 不允许使用其他地方的非线程安全内容。因此,例如在另一个线程正在使用它时更改其他 class 中的字段。
例子
这是一个漂亮的 classic 示例:
public class Value {
private int value;
public int getValue() {
return value;
}
public void increment() {
int current = value; // or just value++
value = current + 1;
}
}
现在,假设两个线程都调用 value.increment()
。一个线程在以下时间后中断:
int current = value; // is 0
然后另一个启动并完全执行increment
。所以
int current = value; // is 0
value = current + 1; // is 1
所以 value
现在是 1
。现在第一个线程继续,预期结果将是 2
,但我们得到:
value = current + 1; // is 1
由于它的 current
在第二个线程 运行 之前已经计算过了,所以它仍然是 0
.
我们还说操作(或本例中的方法)不是原子。所以它可以被调度程序中断。
这个问题当然会发生,因为 Value
有一个字段 value
,所以它有一个可变的状态。
XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.
从这个角度来看,"current day" 答案可能是:使 MyComparator 成为一个枚举。 JVM 保证 MyComparatorEnum.INSTANCE 将是一个真正的单例,您不必担心构建单例时必须考虑的细微细节 "yourself".
是的。如果 class 没有字段,则创建 static final
对象是安全的。在这里,Comparator
仅通过其 compare(String, String)
方法提供功能。
在多线程的情况下,compare
方法将只处理局部变量(b/c来自无状态class),局部变量不共享b/w 线程,即每个线程都有自己的 (String, String) 副本,因此不会相互干扰。
So is it always safe to create a static final Object
of whatever class it is pointing to if it has no fields?
"Always" 这个说法太强了。很容易构造一个人为的 class,其中实例尽管没有字段但不是线程安全的:
public class NotThreadSafe {
private static final class MapHolder {
private static final Map<NotThreadSafe, StringBuilder> map =
// use ConcurrentHashMap so that different instances don't
// interfere with each other:
new ConcurrentHashMap<>();
}
private StringBuilder getMyStringBuilder() {
return MapHolder.map.computeIfAbsent(this, k -> new StringBuilder());
}
public void append(final Object s) {
getMyStringBuilder().append(s);
}
public String get() {
return getMyStringBuilder().toString();
}
}
。 . .但该代码是不现实的。如果您的实例没有任何可变状态,那么它们自然是线程安全的;在 normal Java 代码中,可变状态意味着实例字段。
So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?
我敢说是。没有字段会使 class 无状态,因此是不可变的,这在多线程环境中总是可取的。
无状态对象始终是线程安全的。
不可变对象始终是线程安全的。
摘自 Java 并发实践:
Since the actions of a thread accessing a stateless object cannot affect the correctness of operations in other threads, stateless objects are thread-safe.
Stateless objects are always thread-safe.
The fact that most servlets can be implemented with no state greatly reduces the burden of making servlets threadͲ
safe. It is only when servlets want to remember things from one request to another that the thread-safety requirement becomes an issue.
...
An immutable object is one whose state cannot be changed after construction. Immutable objects are inherently
thread-safe; their invariants are established by the constructor, and if their state cannot be changed, these invariants
always hold.
Immutable objects are always thread-safe.
Immutable objects are simple. They can only be in one state, which is carefully controlled by the constructor. One of the
most difficult elements of program design is reasoning about the possible states of complex objects. Reasoning about
the state of immutable objects, on the other hand, is trivial.
Wouldn't this cause multithreading issue when compare is called from two threads parallelly?
没有。每个线程都有自己的堆栈,其中存储局部变量(包括方法参数)。线程的栈不是共享的,所以没有办法并行地搞砸它。
另一个很好的例子是无状态 servlet。摘自那本好书。
@ThreadSafe
public class StatelessFactorizer implements Servlet {
public void service(ServletRequest req, ServletResponse resp) {
BigInteger i = extractFromRequest(req);
BigInteger[] factors = factor(i);
encodeIntoResponse(resp, factors);
}
}
StatelessFactorizer
is, like most servlets, stateless: it has no fields and references no fields from other classes. The
transient state for a particular computation exists solely in local variables that are stored on the thread's stack and are
accessible only to the executing thread. One thread accessing a StatelessFactorizer
cannot influence the result of
another thread accessing the same StatelessFactorizer
; because the two threads do not share state, it is as if they
were accessing different instances.
Is it like every thread has autonomy of execution if no fields is shared?
每个线程都有自己的程序计数器、堆栈和局部变量。有一个术语"thread confinement",它的一种形式叫做"stack confinement"。
Stack confinement is a special case of thread confinement in which an object can only be reached through local variables. Just as encapsulation can make it easier to preserve invariants, local variables can make it easier to confine objects to a thread. Local variables are intrinsically confined to the executing thread; they exist on the executing thread's stack, which is not accessible to other threads.
阅读:
- Java 并发实践
- Thread Confinement
我正在阅读 Effective Java,遇到了 Joshua Bloch 推荐的情况
class MyComparator extends Comparator<String>{
private MyComparator(){}
private static final MyComparator INSTANCE = new MyComparator();
public int compare(String s1,String s2){
// Omitted
}
}
XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.
那么 总是 安全地创建一个静态最终 Object
任何 class 它指向 如果它没有字段?当从两个线程并行调用 compare
时,这不会导致多线程问题吗?或者我误解了一些基本的东西。如果没有共享字段,是否每个线程都具有执行自主权?
多线程问题是由不需要的状态更改引起的。如果没有更改的状态,则不会出现此类问题。这也是不可变对象在多线程环境中非常方便的原因。
在这种特殊情况下,该方法仅对输入参数 s1
和 s2
进行操作,并且不保留任何状态。
从两个线程并行调用 compare
方法是安全的(堆栈限制)。您传递给该方法的参数存储在该线程的堆栈中,任何其他线程都无法访问。
始终推荐不可变单例。避免创建可变单例,因为它们会在您的应用程序中引入全局状态,这很糟糕。
编辑:如果传递的参数是可变对象引用,那么您必须特别注意确保线程安全。
说明
So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?
视情况而定。只有当一个线程正在更改 something 而另一个线程正在同时使用它时,才会发生多线程问题。因为另一个线程可能不知道由于缓存和其他影响而发生的变化。或者它会导致一个纯逻辑错误,创建者没有考虑到线程可以在操作期间被中断。
所以当一个class是stateless的时候,在多线程环境下使用是绝对安全的。因为首先没有任何线程可以更改。
请注意,这也意味着 class 不允许使用其他地方的非线程安全内容。因此,例如在另一个线程正在使用它时更改其他 class 中的字段。
例子
这是一个漂亮的 classic 示例:
public class Value {
private int value;
public int getValue() {
return value;
}
public void increment() {
int current = value; // or just value++
value = current + 1;
}
}
现在,假设两个线程都调用 value.increment()
。一个线程在以下时间后中断:
int current = value; // is 0
然后另一个启动并完全执行increment
。所以
int current = value; // is 0
value = current + 1; // is 1
所以 value
现在是 1
。现在第一个线程继续,预期结果将是 2
,但我们得到:
value = current + 1; // is 1
由于它的 current
在第二个线程 运行 之前已经计算过了,所以它仍然是 0
.
我们还说操作(或本例中的方法)不是原子。所以它可以被调度程序中断。
这个问题当然会发生,因为 Value
有一个字段 value
,所以它有一个可变的状态。
XYZComparator is stateless, it has no fields. hence all instances of the class are functionally equivalent. Thus it should be a singleton to save on unnecessary object creation.
从这个角度来看,"current day" 答案可能是:使 MyComparator 成为一个枚举。 JVM 保证 MyComparatorEnum.INSTANCE 将是一个真正的单例,您不必担心构建单例时必须考虑的细微细节 "yourself".
是的。如果 class 没有字段,则创建 static final
对象是安全的。在这里,Comparator
仅通过其 compare(String, String)
方法提供功能。
在多线程的情况下,compare
方法将只处理局部变量(b/c来自无状态class),局部变量不共享b/w 线程,即每个线程都有自己的 (String, String) 副本,因此不会相互干扰。
So is it always safe to create a static final
Object
of whatever class it is pointing to if it has no fields?
"Always" 这个说法太强了。很容易构造一个人为的 class,其中实例尽管没有字段但不是线程安全的:
public class NotThreadSafe {
private static final class MapHolder {
private static final Map<NotThreadSafe, StringBuilder> map =
// use ConcurrentHashMap so that different instances don't
// interfere with each other:
new ConcurrentHashMap<>();
}
private StringBuilder getMyStringBuilder() {
return MapHolder.map.computeIfAbsent(this, k -> new StringBuilder());
}
public void append(final Object s) {
getMyStringBuilder().append(s);
}
public String get() {
return getMyStringBuilder().toString();
}
}
。 . .但该代码是不现实的。如果您的实例没有任何可变状态,那么它们自然是线程安全的;在 normal Java 代码中,可变状态意味着实例字段。
So is it always safe to create a static final Object of whatever class it is pointing to if it has no fields?
我敢说是。没有字段会使 class 无状态,因此是不可变的,这在多线程环境中总是可取的。
无状态对象始终是线程安全的。
不可变对象始终是线程安全的。
摘自 Java 并发实践:
Since the actions of a thread accessing a stateless object cannot affect the correctness of operations in other threads, stateless objects are thread-safe.
Stateless objects are always thread-safe.
The fact that most servlets can be implemented with no state greatly reduces the burden of making servlets threadͲ safe. It is only when servlets want to remember things from one request to another that the thread-safety requirement becomes an issue.
...
An immutable object is one whose state cannot be changed after construction. Immutable objects are inherently thread-safe; their invariants are established by the constructor, and if their state cannot be changed, these invariants always hold.
Immutable objects are always thread-safe.
Immutable objects are simple. They can only be in one state, which is carefully controlled by the constructor. One of the most difficult elements of program design is reasoning about the possible states of complex objects. Reasoning about the state of immutable objects, on the other hand, is trivial.
Wouldn't this cause multithreading issue when compare is called from two threads parallelly?
没有。每个线程都有自己的堆栈,其中存储局部变量(包括方法参数)。线程的栈不是共享的,所以没有办法并行地搞砸它。
另一个很好的例子是无状态 servlet。摘自那本好书。
@ThreadSafe public class StatelessFactorizer implements Servlet { public void service(ServletRequest req, ServletResponse resp) { BigInteger i = extractFromRequest(req); BigInteger[] factors = factor(i); encodeIntoResponse(resp, factors); } }
StatelessFactorizer
is, like most servlets, stateless: it has no fields and references no fields from other classes. The transient state for a particular computation exists solely in local variables that are stored on the thread's stack and are accessible only to the executing thread. One thread accessing aStatelessFactorizer
cannot influence the result of another thread accessing the sameStatelessFactorizer
; because the two threads do not share state, it is as if they were accessing different instances.
Is it like every thread has autonomy of execution if no fields is shared?
每个线程都有自己的程序计数器、堆栈和局部变量。有一个术语"thread confinement",它的一种形式叫做"stack confinement"。
Stack confinement is a special case of thread confinement in which an object can only be reached through local variables. Just as encapsulation can make it easier to preserve invariants, local variables can make it easier to confine objects to a thread. Local variables are intrinsically confined to the executing thread; they exist on the executing thread's stack, which is not accessible to other threads.
阅读:
- Java 并发实践
- Thread Confinement