java设计模式之单例模式
程序员文章站
2022-07-14 10:04:19
...
1.概念
在它的核心结构中包含一个被称为单例的特殊类
2.特点
单例类自能有一个实例,构造方法限定为private避免了类在外部被实例化。
单例类必须自己创建自己的唯一实例。
单例类必须给所有其他对象提供这一实例。
3.实现单例模式
1.饿汉式单例(立即加载方式)
解释:在类加载时就完成了初始化
package com.lpw.single;
// 饿汉式单例(一)
public class Singleton {
// 私有无参构造函数(单例必须)
private Singleton() {}
private static Singleton singleton = new Singleton();
public static Singleton getSingleton() {
return singleton;
}
}
package com.lpw.single;
// 饿汉式单例(二)所有静态的部分,在类加载的时候就会被实例化
public class Singleton2 {
private Singleton2() {}
private static Singleton2 singleton = null;
// 静态代码块
static {
singleton = new Singleton2();
}
public static Singleton2 getSingleton() {
return singleton;
}
}
饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变,所以本身就是线程安全的。
2.懒汉式单例(延迟加载方式)
解释:在类加载时不会被出示化
package com.lpw.single;
// 懒汉式单例
public class Singleton3 {
// 不论何种单例,无参都是私有的避免被外部实例化
private Singleton3() {}
// 提供一个实例
private static Singleton3 singleton = null;
// 提供一个供外部调用的方法
public static Singleton3 getSingleton() {
if(singleton == null) {
try {
// 让线程休息10毫秒
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
singleton = new Singleton3();
}
return singleton;
}
}
package com.lpw.single;
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
Singleton3 singleton3 = Singleton3.getSingleton();
// ----------------- 属于单线程------------------------
// System.err.println(singleton3);
// System.err.println(singleton3);
// System.err.println(singleton3);
// System.err.println(singleton3);
// System.err.println(singleton3);
// System.err.println(singleton3);
// System.err.println(singleton3);
// 单线程的时候,用的还是同一个对象
// ----------------- 属于单线程结束------------------------
// 多线程开始
// 亲测,当让线程sleep10毫秒,在4g运存,i5,32位操作系统会出现不适同一个对象,在自己16g运存,i7,64位操作系统一直显示是同一个对象,估测由于运行太快导致
// 第一个线程
new Thread() {
@Override
public void run() {
System.out.println(Singleton3.getSingleton());
};
}.start();
// 第二个线程
new Thread() {
@Override
public void run() {
System.out.println(Singleton3.getSingleton());
};
}.start();
// 第三个线程
new Thread() {
@Override
public void run() {
System.out.println(Singleton3.getSingleton());
};
}.start();
// 多线程结束
}
}
问题:该示例虽然用延迟加载方式实现了懒汉式单例,但在多线程环境下会产生多个single对象?
解决方法:在方法上加synchronized 同步锁此种方式虽然解决了多个实例对象问题,但是该方式运行效率低下,下一个进程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。
package com.lpw.single;
// 饿汉式单例(二)所有静态的部分,在类加载的时候就会被实例化
public class Singleton4 {
private Singleton4() {}
private static Singleton4 singleton = null;
// 提供一个供外部调用的方法,使用同步锁解决多线程出现多个对象问题(加 static同步的是这个类,不加static 同步的是当前对象)
// 使用同步锁会影响运行效率,同等条件下,同步锁,锁的内容越少,效率越高
// 效率相对较低的方法
synchronized public static Singleton4 getSingleton() {
if(singleton==null) {
singleton =new Singleton4();
}
return singleton;
}
}
使用同步锁会影响运行效率,同等条件下,同步锁,锁的内容越少,效率越高。所以,尝试缩小同步范围。
package com.lpw.single;
// 饿汉式单例(二)所有静态的部分,在类加载的时候就会被实例化
public class Singleton5 {
private Singleton5() {}
private static Singleton5 singleton = null;
// 这时是可以的继续缩小
public static Singleton5 getSingleton() {
synchronized (Singleton5.class){
if(singleton==null) {
singleton =new Singleton5();
}
return singleton;
}
}
}
package com.lpw.single;
// 饿汉式单例(二)所有静态的部分,在类加载的时候就会被实例化
public class Singleton5 {
private Singleton5() {}
private static Singleton5 singleton = null;
public static Singleton5 getSingleton() {
if(singleton==null) {
// 当 1,2,3三个线程走到这里,排队,线程1,创建一对象,线程2创建一对象,线程3创建一对象,此时逻辑错误
synchronized (Singleton5.class){
singleton =new Singleton5();
}
}
return singleton;
}
}
package com.lpw.single;
// 饿汉式单例(二)所有静态的部分,在类加载的时候就会被实例化
public class Singleton6 {
private Singleton6() {}
private static Singleton6 singleton = null;
// 最终解决办法,使用双重检查进一步做了优化,可以避免整个方法被锁,支队需要锁的代码部分加锁,可以提高执行效率
public static Singleton6 getSingleton() {
if(singleton==null) {
synchronized (Singleton6.class){
if(singleton==null){
singleton =new Singleton6();
}
}
}
return singleton;
}
}
解决:使用双重检查进一步做了优化,可以避免整个方法被锁,支队需要锁的代码部分加锁,可以提高执行效率
3.静态内部类实现
package com.lpw.single2;
public class Singleton {
private Singleton() {}
// 静态内部类
// 外部类:Singleton,被加载,内部类InSideClass没有被加载,除非主动被调用的时候才会被加载
private static class InSideClass{
private static Singleton singleton = new Singleton();
}
public static Singleton getSingleton() {
return InSideClass.singleton;
}
}
3.分析
优点:
1.在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例。
2.避免对资源的多重占用(比如写文件操作)。
缺点:
扩展很困难。
4.应用场景
1.WEB中的计数器,不用每次刷新都在数据库加一次,用单例先缓存起来。
2.创建的一个对象需要消耗的资源过多,比如I/O于数据库的连接等。
下一篇: java设计模式之单例模式