单例模式
1.基本介绍
(1) 定义
采取一定的方法,保证整个软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态)。
(2) 8种方式
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查-推荐使用
- 静态内部类-推荐使用
- 枚举-推荐使用
2.饿汉式(静态常量)
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部创建该类的对象实例。
- 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。
(2) 代码实现
/**
* 饿汉式(静态常量)实现单例模式
**/
public class Singleton{
// 1. 构造器私有化
private Singleton(){
}
// 2. 在类的内部创建该类的对象实例
private final static Singleton instance = new Singleton();
// 3. 向外暴露一个静态的公共方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
/** 使用 **/
public class Do{
psvm(){
// 获取对象实例,test1和test2是一样的
Singleton test1 = Singleton.getInstance();
Singleton test2 = Singleton.getInstance();
}
}
(3) 优缺点
- 优点:写法简单,在类装载的时候就完成实例化。避免了线程同步问题。
- 缺点:在类装载的时候就完成实例化,没有达到懒加载(Lazy Loading)的效果。如果从始至终没有用过这个实例,则会造成内存浪费。
- 如果是调用getInstance()方法导致类装载是没有问题的,但是如果还有其他方式导致类装载,这时候初始化的instance对象实例就没有使用,也就是没有达到lazy loading效果。
(4) 结论
这种单例模式可用,可能造成内存浪费。
3.饿汉式(静态代码块)
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部声明该类的实例对象。
- 在静态代码块中,创建该类的实例对象。
- 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。
(2) 代码实现
/**
* 饿汉式(静态代码块)实现单例模式
**/
public class Singleton{
// 1. 构造器私有化
private Singleton(){
}
// 2. 在类的内部声明该类的实例对象
private static Singleton instance;
// 3. 在静态代码块中,创建该类的实例对象
static {
instance = new Singleton();
}
// 4. 向外暴露一个静态的公共方法,返回实例对象
public static Singleton getInstance(){
return instance;
}
}
/** 使用 **/
public class Do{
psvm(){
// 获取对象实例,test1和test2是一样的
Singleton test1 = Singleton.getInstance();
Singleton test2 = Singleton.getInstance();
}
}
(3) 优缺点
- 优缺点和饿汉式(静态常量)相同。
(4) 结论
这种单例模式可用,可能造成内存浪费。
4.懒汉式(线程不安全)
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部声明该类的实例对象。
- 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。
(2) 代码实现
/**
* 懒汉式(线程不安全)实现单例模式
**/
public class Singleton{
// 1. 构造器私有化
private Singleton(){}
// 2. 在类的内部声明该类的实例对象
private static Singleton instance;
// 3. 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。懒汉:即用到的时候,才去创建
public static Singleton getInstance(){
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
/** 使用 **/
public class Do{
psvm(){
// 获取对象实例,test1和test2是一样的
Singleton test1 = Singleton.getInstance();
Singleton test2 = Singleton.getInstance();
}
}
(3) 优缺点
- 优点:用到的时候才去创建,起到了Lazy Loading效果
- 缺点:线程不安全,在多线程的情况下,一个线程进入了if(null == instance),还没来的及往下执行,另一个线程也通过这个判断语句,这时会产生多个实例。
(4) 结论
多线程情况下,产生了多个实例,即破坏了单例模式的设计初衷。在实际开发过程中,不要使用这种方式。
5.懒汉式(线程安全,同步方法)
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部声明该类的实例对象。
- 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法是同步方法,只能一个线程执行。
(2) 代码实现
/**
* 懒汉式(线程安全)实现单例模式
**/
public class Singleton{
private Singleton(){}
private static Singleton instance;
// 3. 将该方法声明为同步方法,当一个线程在执行这个方法是,其他线程等待。
public static synchronized Singleton getInstance(){
if(null == instance){
instance = new Singleton();
}
return instance;
}
}
(3) 优缺点
- 优点: 解决了线程安全问题。
- 缺点: 效率低,每个线程在想获取该类的实例的时候,执行getInstance()方法,都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获取该类的实例,直接return就行了。
(4) 结论
在实际开发中,不推荐使用这种方法。
6.懒汉式(线程安全,同步代码块)
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部声明该类的实例对象。
- 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。
(2) 代码实现
/**
* 懒汉式(线程安全)实现单例模式
**/
public class Singleton{
private Singleton(){}
private static Singleton instance;
public static synchronized Singleton getInstance(){
if(null == instance){
// 同步代码块,如果一个线程在执行,其他线程等待
synchronized(Singleton.class){
instance = new Singleton();
}
}
return instance;
}
}
(3) 优缺点
- 缺点:本意是对同步方法效率低问题的改进,但是,这种写法并没有实现线程安全,因为多线程一旦进入if(null == instance)就会往下执行,即使等待上一个线程执行完new,也会创建多个实例。
(4) 结论
这种写法是错误的。
7.双重检查-推荐使用
(1) 步骤
- 构造器私有化,防止其他地方使用new进行创建实例。
- 在类的内部声明该类的实例对象,该变量声明为共享变量。
- 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。新增双重检查
(2) 代码实现
/**
* 双重检查实现单例模式
**/
public class Singleton{
private Singleton(){}
// volatile修饰后,该变量变为共享变量,一个线程更改了它的值后,会立刻刷新到主存(内存)中,其他线程再读取会读取到最新的值
private static volatile Singleton instance;
public static synchronized Singleton getInstance(){
if(null == instance){ // 第一重检查
// 只有一个线程进入执行,其他线程等待
synchronized(Singleton.class){
if(null == instance){ // 第二重检查
// 该线程一旦创建该类的实例对象,其他线程过不了第二重检查
instance = new Singleton();
}
}
}
return instance;
}
}
(3) 优缺点
- 优点: 双重检查(Double-Check)概念是多线程开发中经常使用到的。保证了线程安全,避免了反复进行方法同步效率高,实现了Lazy Loading。
(4) 结论
在实际开发中,推荐使用这种方法实现单例模式
8.静态内部类-推荐使用
(1) 步骤
- 构造器私有化
- 创建一个静态内部类,该内部类中有一个静态属性Singleton
- 提供静态公有方法,直接返回该静态内部类的静态属性
(2) 代码实现
/**
* 静态内部类实现单例模式
**/
public class Singleton{
// 1. 构造器私有化
private Singleton(){}
// 2. 创建一个静态内部类,该内部类中有一个静态属性Singleton
private static class SingletonInstance{
private static final Singleton INSTANCE = new Singleton();
}
// 3. 提供静态公有方法,直接返回该静态内部类的静态属性
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
(3) 分析
- 在Singleton类装载的时候,内部类SingletonInstance是不装载的,在调用getInstance()的时候才装载内部类,因此实现了懒加载,用到的时候才创建实例对象。
- 类的静态属性只会在第一次加载类的时候初始化,在类的初始化时,是线程安全的。JVM帮助我们实现了线程安全。
- JVM在类装载的时候,是线程安全的,因此使用了JVM底层的机制实现线程安全。
(4) 优缺点
- 优点:线程安全、懒加载、效率高。
(5) 结论
推荐使用
9.枚举-推荐使用
(1) 步骤
(2) 代码实现
/**
* 枚举实现单例模式
**/
enum Singleton{
INSTANCE; // 属性
}
/** 使用 **/
public class Do{
psvm(){
// 获取对象实例,test1和test2是一样的
Singleton test1 = Singleton.INSTANCE;
Singleton test2 = Singleton.INSTANCE;
}
}
(4) 优缺点
- 优点:借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。
(5) 结论
推荐使用
10.JDK源码实例
Runtime runtime = Runtime.getRuntime();
采用的是饿汉式静态常量的方式实现的单例模式。