我的一个java 静态方法由多个线程访问。我是否需要通过synchronized关键字显式同步该方法?
有时回来我读了一本书,其中说明了
静态方法是隐式线程安全的,因为该方法不是对象 具体
众所周知的例子是,Singleton实现。 其中getInstance()是静态的,我们是否需要将其标记为同步?
public **synchronized** static Logger getInstance() {
if( instance == null ){
instance = new Logger();
}
return instance;
}
由于
答案 0 :(得分:4)
static
修饰符和线程安全是两个截然不同的事项。
只有在没有任何竞争条件的情况下,你才会遇到事故安全案件。
没有方法的竞争条件意味着:
对方法的访问权限由单线程
或并发线程,但仅限于阅读权限。
这两个方面与静态的方法无关。
例如,在此代码中:
public static Logger getInstance() {
if( instance == null ){
instance = new Logger();
}
return instance;
}
如果应用程序是单线程的,那么它是线程安全的事实
但是如果线程同时访问它并不是这种情况,因为方法不仅仅是读取权限。
在这种情况下,您需要同步对方法的访问。
作为旁注,Bill Pugh成语单身人士不使用同步方法来实现单身人士。
您可以通过在加载类时利用JVM完成的静态初始化来实现它:
public class SingletonBillPughWithEagerCreation {
// executed as soon as the SingletonBillPughWithEagerCreation class is loaded by the classLoader
private static SingletonBillPughWithEagerCreation instance = new SingletonBillPughWithEagerCreation();
private SingletonBillPughWithEagerCreation() {
}
public static SingletonBillPughWithEagerCreation getInstance() {
return instance;
}
}
答案 1 :(得分:2)
不,不是。
您引用的句子意味着静态方法不是特定于对象的,并且由于局部变量保存在线程环境中,因此只能从线程本身的本地执行中访问它。 但是在静态方法中,您可以访问静态字段或由多个线程共享的对象,在这种情况下,您不处理局部变量,而是处理多个对象之间共享的内容。
换句话说,由于执行是线程安全的,因此对有权访问该对象的所有线程共享对对象字段的访问。在这种情况下,您有一个并发问题,必须使用synchronized
关键字来处理。使用synchronized关键字,您每次只能从一个线程访问一个语句。
class MyObject{
static MyType mySharedObject; //if you access this in static methos you are not safe until you sync the access
public static void myMethod(){
int localVar; //that is safely accessed
mySharedObject.setSomething(pippo); //that is not safe in multi thread environment.
}
}
答案 2 :(得分:2)
有一些答案解释了为什么它不是线程安全的,而是回答你的引用
静态方法是隐式线程安全的,因为该方法不是特定于对象的
静态方法的JVM指令invokestatic记录了
如果方法是同步的,则输入或重新输入与已解析的Class对象关联的监视器,就好像通过在当前线程中执行monitorenter指令(§monitorenter)一样。
这意味着静态方法在并发环境中本身并不具有线程安全性,因为synchronized
和non-synchronized
静态方法之间存在差异。
答案 3 :(得分:2)
如果来自多个线程的同时调用不会创建一个情况,其中任何内存空间由一个线程写入,同时由另一个线程同时写入/读取,则可以认为该方法是线程安全的。
此示例显示了一个函数,如果多个线程调用可能会导致问题:
void doThreadNonsense(int input) {
this.myValue = input;
if (this.myValue > 9000) System.out.println("It's over 9000!");
}
因为这里首先写入一个值,然后由多个线程读取,并且可能发生线程A写入5,然后线程B写入9000,然后线程A输出该句子,因为该值现在是> 9000,尽管输入了5。
然而,只需稍作修改,此功能就变为线程安全:
void doThreadNonsense(int input) {
this.myValue = input;
if (input > 9000) System.out.println("It's over 9000!");
}
现在该值只由多个线程写入,但由于在任何32位(或更高)的机器上原子地写入int,myValue
只能由一个或另一个线程写入,但两者都不会写入同一时间。此示例显示了您需要检查代码正在做什么以确定它是否是线程安全的。
如果采用静态方法,它可以 - 如上所述 - 也不是线程安全的:
static int myValue;
static void doThreadNonsense(int input) {
myValue = input;
if (myValue > 9000) System.out.println("It's over 9000!");
}
但是在大多数情况下,静态函数不会写入静态变量(这不被OOP设计模式视为干净的代码),如果您不访问方法范围之外的任何内容,代码将自动进行线程处理 - 安全,因为所有使用的内存位置只对当前线程是本地的。
static void doThreadNonsense(int input) {
if (input> 9000) System.out.println("It's over 9000!");
}
所以你的书应该说:“遵循干净代码范例的静态方法往往是线程安全的。”
然而,这并没有任何帮助,完全忽略了为什么某些东西是线程安全的而其他东西不是。采取以下示例,令人惊讶的是(对某些人)线程安全,尽管它违反了许多“干净的线程”规则:
static final int[][] matrix = new int[N][M];
static void fillMatrixColumn(final int n) {
for (int m = 0; m < M; ++m) {
matrix[n][m] = calculateValue(n, m);
}
}
public static void main(String[] args)() {
IntStream.range(0, N)
.parallel()
.forEach(this::fillMatrixColumn);
printMatrix(matrix);
}
没有同步,没有锁定,但很多从多个线程写到相同的数据结构。这仍然是线程安全的,因为在任何时候两个线程都不会同时写入/读取相同的内存位置。
答案 4 :(得分:0)
考虑以下示例。 getInstance
被线程A和B调用两次。
line1:public **synchronized** static Logger getInstance() {
line2: if( instance == null ){
line3: instance = new Logger();
line4: }
line5:
line6: return instance;
line7:}
可能的执行顺序可能是A2, B2, A3, B3
,意味着第2行由线程A执行,然后由线程B执行,之后第3行由线程A和B执行。这将导致instance == B.getInstance()
,意思是线程B调用getInstance时创建的Logger。但是,线程A使用的是不同的记录器!