意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。
特点:
- 单例类只能有一个实例
- 构造方法限定为private避免了类在外部被实例化
- 单例类必须自己创建自己的唯一实例
- 单例类必须给所有其他对象提供这一实例
单例模式有两种:饿汉式单例(立即加载方式),懒汉式单例(延迟加载方式)。
package com.zlfan.singleton;
/*
* 饿汉式单例
*/
public class Singleton {
public static Singleton singleton = new Singleton();
/*还可以在代码块中创建*/
private Singleton(){}
public static Singleton getInstance(){
return singleton;
}
}
饿汉式单例在类加载初始化的时候就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变。所以饿汉式单例是线程安全的。
package com.zlfan.singleton;
public class Singleton2 {
private static Singleton2 singleton = null;
private Singleton2(){}
public static Singleton2 getInstance() {
if(singleton== null){
singleton= new Singleton2();
}
return singleton;
}
}
懒汉式单例,在多线程环境下会产生多个singleton对象,是线程不安全的。
例如:在创建实例的时候线程睡眠,就会产生多个对象
package com.zlfan.singleton;
public class Singleton2 {
private static Singleton2 singleton = null;
private Singleton2(){}
public static Singleton2 getInstance() {
if(singleton == null){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
singleton = new Singleton2();
}
return singleton;
}
}
package com.zlfan.singleton;
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Singleton2.getInstance());
}
}.start();
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Singleton2.getInstance());
}
}.start();
new Thread(){
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(Singleton2.getInstance());
}
}.start();
}
}
解决方案就是就是在getInstance()方法加上synchronized同步锁。
synchronized public static Singleton2 getInstance() {
if(singleton == null){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
singleton = new Singleton2();
}
return singleton;
}
但是该方式运行效率却很低下,下一个线程想要获取对象,就必须等待上一个线程释放锁之后,才可以继续运行。我们需要进行升级优化 ,用synchronized修饰方法,如果有static方法则锁住的是整个类,如果是非static方法则锁的是类对象。我们使用双重检查。
//双重检查
public static Singleton2 getInstance() {
if(singleton==null){
synchronized(Singleton2.class){
if(singleton==null){
singleton = new Singleton2();
}
}
}
return singleton;
}
使用双重检查,避免整个方法被锁,只需要对锁的代码部分加锁提高执行效率。
单例模式优缺点
优点:
-
在内存只有一个实例,减少内存的开销,尤其是频繁的创建和销毁实例
-
避免对资源的多重占用
缺点:
应用场景
WEB中的计数器,不用每次刷新都往数据库加一次,用单例先缓存起来。
创建的一个对象需要消耗的资源过多,比如I/O于数据库的连接等。
- 扩展困难
单例模式适用于:
- 当类只能有一个实例而且客户可以从一个众所周知的访问点访问它时
- 当这个唯一实例应该是通过子类化可扩展时的,并且客户无须更改代码就能使用一个扩展的实例时