java

位置:IT落伍者 >> java >> 浏览文章

Java中的模式--单态


发布日期:2022年08月13日
 
Java中的模式--单态

单态定义:

Singleton模式主要作用是保证在Java应用程序中一个类Class只有一个实例存在

Singleton模式就为我们提供了这样实现的可能使用Singleton的好处还在于可以节省内存因为它限制了实例的个数有利于Java垃圾回收(garbage collection)

使用Singleton注意事项

有时在某些情况下使用Singleton并不能达到Singleton的目的如有多个Singleton对象同时被不同的类装入器装载在EJB这样的分布式系统中使用也要注意这种情况因为EJB是跨服务器跨JVM的

单态模式的演化

单态模式是个简单的模式但是这个简单的模式也有很多复杂的东西

(注意在这里补充一下现在单态模式其实有一个写法是不错的见这里但还是建议看完这篇文章因为解释的事情是不一样的这里说的是为什么doublechecked不能使用

首先最简单的单态模式单态模式

import javautil*;

class Singleton

{

private static Singleton instance;

private Vector v;

private boolean inUse;

private Singleton()

{

v = new Vector();

vaddElement(new Object());

inUse = true;

}

public static Singleton getInstance()

{

if (instance == null) //

instance = new Singleton(); //

return instance; //

}

}

这个单态模式是不安全的为什么说呢 ?因为没考虑多线程如下情况

Thread 调用getInstance() 方法并且判断instance是null然后进入if模块

在实例化instance之前

Thread 抢占了Thread 的cpu

Thread 调用getInstance() 方法并且判断instance是null然后进入if模块

Thread 实例化instance 完成返回

Thread 再次实例化instance

这个单态已经不在是单态

为了解决刚才的问题单态模式

public static synchronized Singleton getInstance()

{

if (instance == null) //

instance = new Singleton(); //

return instance; //

}

采用同步来解决这种方式解决了问题但是仔细分析正常的情况下只有第一次时候进入对象的实例化须要同步其它时候都是直接返回已经实例化好的instance不须要同步大家都知到在一个多线程的程序中如果同步的消耗是很大的很容易造成瓶颈

为了解决上边的问题单态模式加入同步

public static Singleton getInstance()

{

if (instance == null)

{

synchronized(Singletonclass) {

instance = new Singleton();

}

}

return instance;

}

同步改成块同步而不使用函数同步但是仔细分析

又回到了模式一的状态再多线程的时候根本没有解决问题

为了对应上边的问题单态模式也就是很多人采用的Doublechecked locking

public static Singleton getInstance()

{

if (instance == null)

{

synchronized(Singletonclass) { //

if (instance == null) //

instance = new Singleton(); //

}

}

return instance;

}

这样模式一中提到的问题解决了不会出现多次实例化的现象

当第一次进入的时候保正实例化时候的单态在实例化后多线程访问的时候直接返回不须要进入同步模块既实现了单态又没有损失性能表面上看我们的问题解决了但是再仔细分析

我们来假象这中情况

Thread :进入到//位置执行new Singleton()但是在构造函数刚刚开始的时候被Thread抢占cpu

Thread :进入getInstance()判断instance不等于null返回instance

(instance已经被new已经分配了内存空间但是没有初始化数据)

Thread :利用返回的instance做某些操做失败或者异常

Thread :取得cpu初始化完成

过程中可能有多个线程取到了没有完成的实例并用这个实例作出某些操做

-----------------------------------------

出现以上的问题是因为

mem = allocate(); //分配内存

instance = mem; //标记instance非空

//未执行构造函数thread 从这里进入

ctorSingleton(instance); //执行构造函数

//返回instance

------------------------------------------

证明上边的假想是可能发生的字节码是用来分析问题的最好的工具可以利用它来分析下边一段程序(为了分析方便所以渐少了内容)

字节码的使用方法见这里利用字节码分析问题

class Singleton

{

private static Singleton instance;

private boolean inUse;

private int val;

private Singleton()

{

inUse = true;

val = ;

}

public static Singleton getInstance()

{

if (instance == null)

instance = new Singleton();

return instance;

}

}

得到的字节码

;asm code generated for getInstance

DB mov eax[C] ;load instance ref

DB test eaxeax ;test for null

DB jne DD

DB mov eaxCh

DBE call EFF ;allocate memory

DC mov [C]eax ;store pointer in

;instance ref instance

;nonnull and ctor

;has not run

DC mov ecxdword ptr [eax]

DCA mov dword ptr [ecx] ;inline ctor inUse=true;

DD mov dword ptr [ecx+] ;inline ctor val=;

DD mov ebxdword ptr ds:[Ch]

DDD jmp DB

上边的字节码证明猜想是有可能实现的

好了上边证明Doublechecked locking可能出现取出错误数据的情况那么我们还是可以解决的

public static Singleton getInstance()

{

if (instance == null)

{

synchronized(Singletonclass) { //

Singleton inst = instance; //

if (inst == null)

{

synchronized(Singletonclass) { //

inst = new Singleton(); //

}

instance = inst; //

}

}

}

return instance;

}

利用Doublechecked locking 两次同步中间变量解决上边的问题

(下边这段话我只能简单的理解翻译过来不好所以保留原文list 是上边的代码list 是下边的

The code in Listing doesnt work because of the current definition of the memory model

The Java Language Specification (JLS) demands that code within a synchronized block

not be moved out of a synchronized block However it does not say that

code not in a synchronized block cannot be moved into a synchronized block

A JIT compiler would see an optimization opportunity here

This optimization would remove the code at

// and the code at // combine it and generate the code shown in Listing :)

list

public static Singleton getInstance()

{

if (instance == null)

{

synchronized(Singletonclass) { //

Singleton inst = instance; //

if (inst == null)

{

synchronized(Singletonclass) { //

//inst = new Singleton(); //

instance = new Singleton();

}

//instance = inst; //

}

}

}

return instance;

}

If this optimization takes place you have the same outoforder write problem we discussed earlier

如果这个优化发生将再次发生上边提到的问题取得没有实例化完成的数据

以下部分为了避免我翻译错误误导打家保留原文

Another idea is to use the keyword volatile for the variables inst and instance

According to the JLS (see Resources) variables declared volatile are supposed to

be sequentially consistent and therefore not reordered

But two problems occur with trying to use volatile to fix the problem with

doublechecked locking:

The problem here is not with sequential consistency

Code is being moved not reordered

Many JVMs do not implement volatile correctly regarding sequential consistency anyway

The second point is worth expanding upon Consider the code in Listing :

Listing Sequential consistency with volatile

class test

{

private volatile boolean stop = false;

private volatile int num = ;

public void foo()

{

num = ; //This can happen second

stop = true; //This can happen first

//

}

public void bar()

{

if (stop)

num += num; //num can == !

}

//

}

According to the JLS because stop and num are declared volatile

they should be sequentially consistent This means that if stop is ever true

num must have been set to

However because many JVMs do not implement the sequential consistency feature of volatile

you cannot count on this behavior

Therefore if thread called foo and thread called bar concurrently

thread might set stop to true before num is set to

This could lead thread to see stop as true but num still set to

There are additional problems with volatile and the atomicity of bit variables

but this is beyond the scope of this article

See Resources for more information on this topic

简单的理解上边这段话使用volatile有可能能解决问题volatile被定义用来保正一致性但是很多虚拟机并没有很好的实现volatile所以使用它也会存在问题

最终的解决方案

单态模式使用同步方法

放弃同步使用一个静态变量如下

class Singleton

{

private Vector v;

private boolean inUse;

private static Singleton instance = new Singleton();

private Singleton()

{

v = new Vector();

inUse = true;

//

}

public static Singleton getInstance()

{

return instance;

}

}

但使用静态变量也会存在问题问题见 这篇文章

而且如在文章开头提到的使用EJB跨服务器跨JVM的情况下单态更是问题

好了是不是感觉单态模式根本没法使用了其实上边都是特殊情况这中特殊情况的出现是有条件的只要根据你的具体应用回避一些就能解决问题所以单态还是可以使用的但是在使用前慎重自己考虑好自己的情况适合哪种情况

               

上一篇:JAVA多线程学习初步经典实例

下一篇:Java模