Arc*_*aye 1 java concurrency singleton multithreading
package singleton;
public class SingletonClass {
private static SingletonClass singleton = null;
private SingletonClass() {
}
static boolean stopThread = true;
//approach 1 which fails in multithereaded env
/*public static SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
return singleton;
}*/
//approach 2 which works
//method is synchronized
/* public static synchronized SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
return singleton;
}*/
***//approach 3 which is failing but I don't understand why
//big block of code is synchronized
public static SingletonClass getInstance(){
if(null == singleton){
synchronized (SingletonClass.class){
try {
if(stopThread){
stopThread = false;
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
singleton = new SingletonClass();
}
}
return singleton;
}***
//small block of code is synchronized, checked null again because even object instantiation is synchronised
//if we don't check null, it will create new object once again
//approach 4 which works
/* public static SingletonClass getInstance(){
if(null == singleton){
try {
if(stopThread){
System.out.println("in thread...");
stopThread = false;
//even if we interchange above 2 lines it makes whole lot of difference
//till the time it takes to print "in thread"
//2nd thread reaches there n enters if(stopThread) block because
//stopThread is still true because 1st thread spent time in writing that sentence and
//did not set stopThread = false by the time 2nd thread reached there
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (SingletonClass.class){
System.out.println("in this block");
if(null == singleton){
singleton = new SingletonClass();
}
}
}
return singleton;
}*/
}
---------------------------------------------------------
package singleton;
public class ThreadUsage implements Runnable {
@Override
public void run() {
SingletonClass singletonOne = SingletonClass.getInstance();
System.out.println(singletonOne.hashCode());
}
}
----------------------------------------------------------------
package singleton;
class ThreadUsageTest {
public static void main(String[] args) {
Runnable runnableOne = new ThreadUsage();
Runnable runnableTwo = new ThreadUsage();
new Thread(runnableOne).start();
new Thread(runnableTwo).start();
}
}
---------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)
在方法3中,它没有为2个对象提供相同的hashCode,我将Thread.sleep以及对象实例化都保留在同步块下,所以我的想法是,第二个线程甚至不应该进入这个块,直到第一个完成为止,但是它仍在执行并创建第二个对象,导致 diff hashCode。我在这里发什么消息?有人可以在这里纠正我的理解吗?如果我检查 null b4 对象创建,那么它会按预期工作,但为什么我需要在这里再次检查 null,因为我的整个代码都在同步块下?
if(null == singleton)
singleton = new SingletonClass();
Run Code Online (Sandbox Code Playgroud)
以下是代码(方法 3)最终为单例创建并返回两个(或更多)单独对象的一种方法:
null查看singletonnull查看singletonsingletonsingletonnull例如,检查和输入其后的同步块之间存在间隙。
要解决这个问题,只需创建getInstance一个synchronized方法并删除synchronized其中的块即可:
public static synchronized SingletonClass getInstance() {
if (instance == null) {
singleton = new SingletonClass();
}
return singleton;
}
Run Code Online (Sandbox Code Playgroud)
或者,如果您确实想避免在后续调用中同步,请在 Java 5 或更高版本(希望您正在使用!)上声明singleton volatile并在synchronized块中再次检查:
private static volatile SingletonClass singleton;
// ...
public static SingletonClass getInstance() { // Only works reliably on Java 5 (aka 1.5) and later!
SingletonClass instance = singleton;
if (instance == null) {
synchronized (SingletonClass.class) {
instance = singleton;
if (instance == null) {
singleton = instance = new SingletonClass();
}
}
}
return instance;
}
Run Code Online (Sandbox Code Playgroud)
这就是双重检查锁定习惯用法。在 Java 4(又名 1.4)及更早版本中,这不一定可靠,但现在是可靠的(前提是您volatile在成员上使用)。
user2683814 在评论中提出了一个很好的问题:
您能否解释一下第二个代码片段中空检查之前对局部变量的赋值?直接检查类变量行不通?
是的,它会起作用,但效率较低。
singleton在非的情况下null,使用本地意味着该方法仅访问singleton 一次。如果代码不使用本地变量,它将singleton至少访问两次(一次检查它,一次返回它)。由于访问volatile变量的成本稍高,因此最好使用本地变量(在上面的代码中可以将其优化为寄存器)。
这可能看起来像是过早的微优化,但如果您不是在性能关键的代码中执行此操作,您只需创建该方法synchronized并完全避免双重检查锁定的复杂性。:-)
| 归档时间: |
|
| 查看次数: |
82 次 |
| 最近记录: |