一,实现单例模式必要步骤
A,静态私有变量
B,私有构造方法,防止外部new
二,创建方式
1,饿汉模式一:静态常量。类加载的时候即创建一个实例,很着急,饿得很,上来不管吃得了吃不了,先来一个。
1) 优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
2) 缺点:在类装载的时候就完成实例化,没有达到 Lazy Loading 的效果。如果从始至终从未使用过这个实例,则
会造成内存的浪费
3) 这种方式基于 classloder 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,在单例模式中大
多数都是调用 getInstance 方法,但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静
态方法)导致类装载,这时候初始化 instance 就没有达到 lazy loading 的效果
4) 结论:这种单例模式可用,可能造成内存浪费,但当你确定这个实例必定被用到时候,是可以使用的。
package designpattern.singleton.type1;
public class SingletonTest01 {
public static void main(String[] args) {
//单例的话,这两个实例应该是同一个,hashcode
SingleTon instance = SingleTon.getInstance();
SingleTon instance1 = SingleTon.getInstance();
System.out.println(instance == instance1);
//我们看下两个变量的hashcode
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
//饿汉式写法(静态变量)
class SingleTon{
private static final SingleTon singleTon = new SingleTon();
//首先将构造器私有构造器,外部不能new一个Singleton
private SingleTon(){
}
//对外提供一个静态方法
public static SingleTon getInstance(){
return singleTon;
}
}
2,饿汉模式二:静态代码块,使用静态代码块类加载的时候即创建一个实例。
1) 这种方式和上面的方式其实类似,只不过将类实例化的过程放在了静态代码块中,也是在类装载的时候,就执
行静态代码块中的代码,初始化类的实例。优缺点和上面是一样的。
2) 结论:这种单例模式可用,但是可能造成内存浪费
public class SingletonTest02 {
public static void main(String[] args) {
//单例的话,这两个实例应该是同一个,hashcode
SingleTon2 instance = SingleTon2.getInstance();
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance == instance1);
//我们看下两个变量的hashcode
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
//饿汉模式2(静态代码块)
//其实就是在静态代码块给private 的实例初始化
class SingleTon2{
private static final SingleTon2 singleTon;
static{
singleTon = new SingleTon2();
}
//首先将构造器私有构造器,外部不能new一个Singleton
private SingleTon2(){
}
//对外提供一个静态方法调用获取该实例
public static SingleTon2 getInstance(){
return singleTon;
}
}
3、懒汉模式(线程不安全):即我很懒,不着急。只有到用的时候,才去拿一个实例用
1) 起到了 Lazy Loading 的效果,但是只能在单线程下使用。
2) 如果在多线程下,一个线程进入了 if (singleton == null)判断语句块,还未来得及往下执行,另一个线程也通过
了这个判断语句,这时便会 产生多个实例。所以在多线程环境下不可使用这种方式
3) 结论:在实际开发中, 不要使用这种方式.
代码如下:
//懒汉模式(线程不安全)
public class SingletonTest1 {
public static void main(String[] args) {
SingleTon instance = SingleTon.getInstance();
SingleTon instance1 = SingleTon.getInstance();
System.out.println(instance == instance1);
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class SingleTon{
private static SingleTon singleTon;
private SingleTon() {
}
//提供一个静态方法,当需要使用这个对象,显式的调用才会创建
public static SingleTon getInstance(){
if(singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
4、懒汉模式(线程安全),加入synchronized关键字
1) 解决了 线程安全问题
2) 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行
一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。 方法进行同步效率太低
3) 结论:在实际开发中, 不推荐使用这种方式
public class SingleTonTest3 {
public static void main(String[] args) {
// TODO Auto-generated method stub
}
}
class SingleTon{
private static SingleTon singleTon;
private SingleTon() {
}
//提供一个静态方法,当需要使用这个对象,显示的调用才会创建
//这里加了synchronized关键字,实现了线程安全,即多个线程同时访问时,排队访问该方法
//这回导致排队耗时,降低性能
public static synchronized SingleTon getInstance(){
if(singleTon == null){
singleTon = new SingleTon();
}
return singleTon;
}
}
5、懒汉模式之同步代码块,即把实例的创建放到同步代码块中去
1) 解决了 线程安全问题
2) 效率太低了,每个线程在想获得类的实例时候,执行 getInstance()方法都要进行同步。而其实这个方法只执行
一次实例化代码就够了,后面的想获得该类实例,直接 return 就行了。 方法进行同步效率太低
3) 结论:在实际开发中, 不推荐使用这种方式
代码如下:
public static SingleTon getInstance(){
if(singleTon == null){
synchronized (SingleTon.class) {
singleTon = new SingleTon();
}
}
return singleTon;
}
6、懒汉模式之双重检查
1) Double-Check 概念是多线程开发中常使用到的,如代码中所示,我们进行了两次 if (singleton == null)检查,这
样就可以保证线程安全了。
2) 这样,实例化代码只用执行一次,后面再次访问时,判断 if (singleton == null),直接 return 实例化对象,也避
免的反复进行方法同步.
3) 线程安全; 延迟加载; 效率较高
4) 结论:在实际开发中,推荐使用这种单例设计模式
public class SingleTonTest4 {
public static void main(String[] args) {
// TODO Auto-generated method stub
//单例的话,这两个实例应该是同一个
SingleTon instance = SingleTon.getInstance();
SingleTon instance1 = SingleTon.getInstance();
System.out.println(instance == instance1);
//我们看下两个变量的hashcode
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
class SingleTon {
//volatile关键字,多线程访问时,首个访问到线程在赋值时候,其他线程可看到变化了的实例
//此时,singleTon属性不为空,就不会再走同步代码块了
private static volatile SingleTon singleTon;
private SingleTon() {
}
// 提供一个静态方法,当需要使用这个对象,显示的调用才会创建
// 这里加了synchronized关键字,实现了线程安全,即多个线程同时访问时,排队访问该方法
// 这回导致排队耗时,降低性能
public static SingleTon getInstance() {
if (singleTon == null) {
synchronized (SingleTon.class) {
if (singleTon == null) {
singleTon = new SingleTon();
}
}
}
return singleTon;
}
}
7、静态内部类,使用java的类加载机制,实现懒加载和线程安全
1) 这种方式采用了类装载的机制来保证初始化实例时只有一个线程。
2) 静态内部类方式在 Singleton 类被装载时并不会立即实例化,而是在需要实例化时,调用 getInstance 方法,才
会装载 SingletonInstance 类,从而完成 Singleton 的实例化。
3) 类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM 帮助我们保证了线程的安全性,在类进行
初始化时,别的线程是无法进入的。
4) 优点: 避免了线程不安全,利用 静态内部类特点实现延迟加载,效率高
5) 结论: 推荐使用.
class SingleTon{
//定义私有静态变量
private static SingleTon singleTon;
//构造器私有化是必须的
private SingleTon(){
}
//写一个静态内部类,类里面定义一个静态属性SingleTon
private static class SingleTonInstance{
private static final SingleTon INSTANCE = new SingleTon();
}
public static synchronized SingleTon getInstance(){
return SingleTonInstance.INSTANCE;
}
}
8、通过枚举实现单例
1) 这借助 JDK1.5 中添加的枚举来实现单例模式。不仅能避免多线程同步问题,而且还能防止反序列化重新创建
新的对象。
2) 这种方式是 Effective Java 作者 Josh Bloch 提倡的方式
3) 结论: 推荐使用
//通过枚举的方式
public class SingleTonTest6 {
public static void main(String[] args) {
//单例的话,这两个实例应该是同一个
SingleTon instance = SingleTon.INSTANCE;
SingleTon instance1 = SingleTon.INSTANCE;
System.out.println(instance == instance1);
//我们看下两个变量的hashcode
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
enum SingleTon{
//属性
INSTANCE;
public void sayOk(){
System.out.println("this is ok");
}
}