2017-08-09 61 views
3

我的一个java 静态方法是由多个线程访问。我是否需要通过同步关键字明确地同步该方法?java - 静态方法隐式线程安全?

有时回我在一本书其中指出

静态方法隐含线程安全的方法是不反对 具体

读取。

众所周知的例子是Singleton实现。 其中getInstance()是静态的,我们是否需要将它标记为synchronized?

public **synchronized** static Logger getInstance() { 
     if(instance == null){ 
      instance = new Logger(); 
     } 

     return instance; 
    } 

感谢

+1

不!静态不是同步的同义词! –

+1

如果该静态数据是可变的,则意味着每个对象都会看到更改。那是你要的吗?也许不是。 – duffymo

+0

但是,该方法将只有一个副本,因为它是类级别变量。对? – JavaUser

回答

0

不,事实并非如此。

您引用的句子意味着静态方法不是对象特定的,并且由于局部变量保存在线程环境中,因此只能从线程本身的本地执行中进行访问。 但是在静态方法中,您可以访问静态字段或多线程共享的对象,在这种情况下,您不是处理局部变量,而是处理多个对象之间共享的内容。

换句话说,由于执行是线程安全的,因此对对象字段的访问被共享给所有有权访问该对象的线程。在这种情况下,您需要使用​​关键字来处理并发问题。使用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. 

} 

}

+0

“物体的静态场”是矛盾的。你的意思是“类的静态字段”或“指向对象的静态字段”,但是,原始类型或数组的静态字段也需要线程安全结构。请注意,'static'方法也可能操作作为参数传递的对象,这些参数可能会在不同线程之间共享。 – Holger

2

static修改和线程安全是两个不同的问题。

只有当你没有任何竞争条件时,你才会成为事实上的安全事件。
具有方法无竞争状态是指:

  • 无论对方法的访问是由单线程

  • 通过并发线程,但只有读访问完成。

这两件事情都与事实的方法静态或不为零的关系。

例如在此代码:

public static Logger getInstance() { 
    if(instance == null){ 
     instance = new Logger(); 
    } 

    return instance; 
} 

它是线程安全的事实上的,如果应用程序是单螺纹。
但是,如果由线程同时访问它并不是这种情况,因为方法不能只读取访问
在这种情况下,您需要同步对方法的访问。


作为边注,草案普格成语单免去了使用一个同步方法来实现一个单。
你的确可以通过利用由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; 
    } 
} 
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时创建的记录器。线程A正在使用不同的记录器!

+0

这个答案需要更多的上下文。 OP更精确的解释更加精确。 –

1

有一些答案,解释为什么它不是线程安全的,但要回答你的报价

静态方法隐含线程安全的,因为该方法不反对特定

的JVM指令invokestatic为静态方法文件

如果方法是同步的,则与已解析的Class对象关联的监视器将被输入或重新输入就像在当前线程中执行monitorenter指令(§monitorenter)一样输入。

这意味着,静态方法不是本质上就是线程安全的并发环境中,因为有​​和non-synchronized静态方法之间的差异。

0

如果来自多个线程的同时调用不会创建一种情况,其中任何内存空间由一个线程写入,并且同时由另一个线程同时写入/读取,则可以将该方法视为线程安全。

这个例子显示的功能,如果所谓的多个线程可能会导致问题:

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); 
} 

不同步,不锁定,但很多从多快乐写作线程到相同的数据结构。这仍然是线程安全的,因为在任何时候,两个线程都不会同时写入/读取同一个内存位置。