单例模式的目的是只生产一个对象实例,所有依赖它的对象访问到的都是同一个实例。通过将构造函数私有化,并提供一个public静态方法提供此实例。
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if( instance == null){
instance = new Singleton();
}
return singleton;
}
...
}
对于外部代码,不能用new来实例化,因此把构造函数声明为私有。
实例化只能由类本身来构造,并对外提供一个getInstance()方法。每次调用会判断是否实例化过,然后提供实例化过的对象。
上述代码在高并发环境下不安全,可能会产生多个实例。多线程下可以使用以下方法。
public class Singleton{
private static final Singleton instance = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return instance;
}
}
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if( instance == null){
instance = new Singleton();
}
return instance;
}
...
}
或者
public class Singleton{
private static Singleton instance = null;
private Singleton(){
}
public static Singleton getInstance(){
if( instance == null){
synchronized(Singleton.class){
if( instance == null)
instance = new Singleton();
}
}
return instance;
}
}
注:对象复制式不调用类的构造函数的,因此单例模式不要实现Cloneable接口。
通过new关键字、复制、反射。
单例模式只生成一个对象实例,并且不用反复创建销毁,减少了系统的性能开销。由于常驻内存,JAVA中使用要注意JVM垃圾回收。
在整个程序中,要提供一个共享访问点或共享数据,会用到单例模式。以下举例说明:
大数据计算框架Spark启动后,会初始化SparkContext(sc)。SparkContext通过getOrCreate函数,保证一个JVM只启动一个SparkContext。
class SparkContext(config: SparkConf) extends Logging {
private val allowMultipleContexts: Boolean = config.getBoolean("spark.driver.allowMultipleContexts", false)
...
}
object SparkContext extends Logging {
/**
* Lock that guards access to global variables that track SparkContext construction.
*/
private val SPARK_CONTEXT_CONSTRUCTOR_LOCK = new Object()
/**
* The active, fully-constructed SparkContext. If no SparkContext is active, then this is `null`.
*
* Access to this field is guarded by SPARK_CONTEXT_CONSTRUCTOR_LOCK.
*/
private val activeContext: AtomicReference[SparkContext] =
new AtomicReference[SparkContext](null)
/**
* This function may be used to get or instantiate a SparkContext and register it as a singleton object. Because we can only have one active SparkContext per JVM, this is useful when applications may wish to share a SparkContext.
* @note This function cannot be used to create multiple SparkContext instances even if multiple contexts are allowed.
* @param config `SparkConfig` that will be used for initialisation of the `SparkContext`
* @return current `SparkContext` (or a new one if it wasn't created before the function call)
*/
def getOrCreate(config: SparkConf): SparkContext = {
// Synchronize to ensure that multiple create requests don't trigger an exception from assertNoOtherContextIsRunning within setActiveContext
SPARK_CONTEXT_CONSTRUCTOR_LOCK.synchronized {
if (activeContext.get() == null) {
setActiveContext(new SparkContext(config), allowMultipleContexts = false)
} else {
if (config.getAll.nonEmpty) {
logWarning("Using an existing SparkContext; some configuration may not take effect.")
}
}
activeContext.get()
}
}
}
通过SPARK_CONTEXT_CONSTRUCTOR_LOCK.synchronized锁机制实现了多线程的懒汉式单例模式。
在Spark Streaming中,如果启用了checkpoint,并且使用了累加器或者广播变量,累加器或者广播变量将不能从checkpoint恢复。如果要使用,需要使用单例模式,才能在driver重启后恢复。
累加器Scala代码如下:
object DroppedWordsCounter {
@volatile private var instance: Accumulator[Long] = null
def getInstance(sc: SparkContext): Accumulator[Long] = {
if (instance == null) {
synchronized {
if (instance == null) {
instance = sc.accumulator(0L, "WordsInBlacklistCounter")
}
}
}
instance
}
}
广播变量Scala代码如下:
object WordBlacklist {
@volatile private var instance: Broadcast[Seq[String]] = null
def getInstance(sc: SparkContext): Broadcast[Seq[String]] = {
if (instance == null) {
synchronized {
if (instance == null) {
val wordBlacklist = Seq("a", "b", "c")
instance = sc.broadcast(wordBlacklist)
}
}
}
instance
}
}
参考资料:
《设计模式之禅(第2版)》(秦小波 著)
《大话设计模式》(程杰 著)
https://spark.apache.org/docs/1.6.1/streaming-programming-guide.html#accumulators-and-broadcast-variables