单例模式(singleton)
描述:
一个类只有一个实例,并且为它提供一个全局访问点
场景:
在操作系统中,打印池是一个用于管理打印任务的应用程序,通过打印池用户可以删除、中止或者改变任务的优先级,在一个系统中只允许运行一个打印池对象,如果重复创建打印池则抛出异常。现使用单例模式来模拟实现打印池的设计,请根据类图编程实现该系统,并写出相应Java代码
java代码
package org.crudboy;
/** lazy **/
class PrintSpoolerSingleton
{
private static PrintSpoolerSingleton instance = null;
private PrintSpoolerSingleton() {} // 此处不可省略
public static PrintSpoolerSingleton getInstance() throws PrintSpoolerException {
if (instance == null) {
System.out.println("创建打印池!");
instance = new PrintSpoolerSingleton();
} else {
throw new PrintSpoolerException("打印池正在工作中!");
}
return instance;
}
public void manageJobs() {
System.out.println("管理打印任务!");
}
}
class PrintSpoolerException extends Exception {
public PrintSpoolerException(String message) {
super(message);
}
}
public class Singleton {
public static void main(String[] args) {
PrintSpoolerSingleton ps1, ps2;
try {
ps1 = PrintSpoolerSingleton.getInstance();
ps1.manageJobs();
} catch (Exception e) {
System.out.println(e.getMessage());
}
System.out.println("-----------------------------");
try {
ps2 = PrintSpoolerSingleton.getInstance();
ps2.manageJobs();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
为保证对象的全局唯一,我们只需要把该对象属性设置为static即可。这样该对象会在第一次访问时被创建(lazy模式)
需要强调的是,一定要显示把类的构造访问声明为private,否则类会自动生成修饰符public的构造方法,这样外部调用者就可以通过new创建多个对象。
单例模式在java中几种的写法
饿汉模式
饿汉模式在类被加载时就对该类进行实例化
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return singleton;
}
}
懒汉模式(lazy)
我们需要重点关注的懒汉模式,懒汉模式只有在单例对象第一被使用时,才会进行实例化。
1. 线程不安全写法
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton(); // 可能多次执行
}
return singleton;
}
}
在多线程并发执行下,可能出现在通过判断条件后线程切换,导致多次执行创建语句。
2. synchronized加锁
public class Singleton {
private static Singleton singleton = null;
private Singleton() {}
public synchronized static Singleton getInstance() {
if (singleton == null) {
singleton = new Singleton();
}
return singleton;
}
}
通过给类方法加上类锁,这样保证了同一时刻只有一个线程访问该方法。
3. 双检锁(double checking lock) + volatile
虽然在第2点中保证了线程安全,但也存在一个问题:锁的力度太大了。
我们可以发现不但创建对象的过程被锁住,获取对象的过程也被锁住了。于是可以使用dcl进行优化
public class Singleton {
private volatile static Singleton singleton = null;
private Singleton() {}
public static Singleton getInstance() {
if (singleton == null) {
synchronized(this.class) { // 只有在判断对象未被创建时,才上锁
if (singleton == null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
为什么单例对象要用volatile修饰呢?
通过volatile,禁止指令重排,防止获取到未初始化的对象。