单例模式:
一个类只有一个实例,并提供一个访问它的全局访问点。
应用场景:创建一个唯一的变量(对象)。
1.场景问题:(读取配置文件到内存)
文件名:AppConfig.properties
内容:paramA=a
paramB=b
package computer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
AppConfig config=new AppConfig();
String paramA=config.getParameterA();
String paramB=config.getParameterB();
System.out.println("paramA="+paramA+"paramB="+paramB);
}
}
class AppConfig{
private String parameterA;
private String parameterB;
public AppConfig(){
readConfig();
}
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
private void readConfig() {
Properties p=new Properties();
InputStream in=null;
try {
in=AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA=p.getProperty("paramA");
this.parameterB=p.getProperty("paramB");
}catch(IOException e) {
System.out.println("装载配置文件出错!");
e.printStackTrace();
}finally{
try {
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
}
问题:系统运行时,多处要用到配置文件,即系统会创建多个AppConfig的实例对象,资源浪费。
2.解决方案:(单例模式)
构造函数私有,用户无法通过new直接实例;静态方法负责检验并实例化自己,确保只有一个实例被创建。
注意:类中的私有成员变量也是静态的!
package computer;
public class Test {
public static void main(String[] args) {
Singleton s1=Singleton.GetInstance();
Singleton s2=Singleton.GetInstance();
if(s1==s2) {
System.out.println("两个对象是相同的实例!");
}else {
System.out.println("两个对象是不同的实例!");
}
}
}
class Singleton{
private static Singleton instance;
//构造方法为私有,外界无法用new创建此类的对象
private Singleton() {}
public static Singleton GetInstance() {
//实例不存在就new一个新实例,存在就返回已经有的实例
if(instance==null) {
instance=new Singleton();
}
return instance;
}
}
缺点:多线程时可能同时有两个对象被创建。
3.多线程时的单例模式:
用到synchronized():被synchronized()修饰的区域每次只有一个线程可访问,其他线程若试图进入锁定的代码,将被阻止,直到对象被释放。
package computer;
public class Test {
public static void main(String[] args) {
Singleton s1=Singleton.GetInstance();
Singleton s2=Singleton.GetInstance();
if(s1==s2) {
System.out.println("两个对象是相同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}else {
System.out.println("两个对象是不同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}
}
}
class Singleton{
private static Singleton instance;
private static final Object syncRoot=new Object();
private Singleton() {}
public static Singleton GetInstance() {
//在同一时刻加了锁的部分程序只有一个线程可以进入(即第一个线程进入以后,锁住。其他线程再也进不来,除非该对象被释放。)
synchronized(syncRoot) {
if(instance==null) {
instance=new Singleton();
}
}
System.out.println("singleton创建。");
return instance;
}
}
解释:不直接synchronized(instance),而是再创建一个syncRoot来synchronized的原因是:当instance还没有被创建过时,因为不存在instance,就没法锁instance。
缺点:每次调用getInstance()方法时都需要同步,降低了性能。
解决方法:双重锁定。
4.双重锁定的单例模式:
不用让线程每次都加锁,而只是在实例未被创建的时候加锁。
package computer;
public class Test {
public static void main(String[] args) {
SingletonDCL s1=SingletonDCL.getInstance();
SingletonDCL s2=SingletonDCL.getInstance();
if(s1==s2) {
System.out.println("两个对象是相同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}else {
System.out.println("两个对象是不同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}
}
}
class SingletonDCL{
//Volatile:当一个线程修改了这个变量的值,volatile 保证了新值能立即同步到主内存,以及每次使用前立即从主内存刷新。
private volatile static SingletonDCL instance;
private SingletonDCL() {}
//此方法是获得本类实例的唯一全局访问点:
public static SingletonDCL getInstance() {
if(instance==null) {
//给类加锁,类的所有对象同用一把锁:
synchronized(SingletonDCL.class){
if(instance==null) {
instance=new SingletonDCL();
}
}
}
System.out.println("singleton创建!");
return instance;
}
}
解释:为什么要在判断了实例是否存在后再次判断实例是否存在?因为在instance为null的情况下可能会有同时两个线程同时执行getInstance()方法,则他们都可以通过instance==null的判断,然后,由于synchronized(SingletonDCL.class),这两个线程只有一个可以进入,另一个排队等第一个出来以后再进入。这是如果没有第二重instence是否为null的判断,则第一个线程创建实例后,第二个线程还可以创建实例。
5.静态内部类:(没讲)
定义静态内部类SingletonHolder,Singleton被装载时,instance不被初始化,因为SingletonHolder类没有被主动使用,只有显式调用getInstance()方法时,才会显式装载SingletonHolder类,从而实例化instance。
package computer;
public class Test {
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
if(s1==s2) {
System.out.println("两个对象是相同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}else {
System.out.println("两个对象是不同的实例!");
System.out.println("singleton1:"+s1.toString());
System.out.println("singleton2:"+s2.toString());
}
}
}
class Singleton{
private static class SingletonHolder{
private static final Singleton INSTANCE=new Singleton();
}
private Singleton() {}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
6.单例模式分类:
1)懒汉单例模式(时间换空间):在第一次被引用时才会将自己实例化。
缺点:面临多线程访问的
安全问题。
class Singleton{
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if(instance==null) {
instance=new Singleton();
}
return instance;
}
}
2)饿汉单例模式(空间换时间):在静态初始化期间或者在类构造函数中分配变量。在自己被加载时就将自己实例化。
缺点:提前占用系统资源。
class Singleton{
//在类加载的时候被创建,是static所以后期不会再改变,所以是线程安全的:
private static Singleton instance=new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}
7.改写开头例子:
1)懒汉单例模式:
package computer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
AppConfig config=AppConfig.getAppConfig();
String paramA=config.getParameterA();
String paramB=config.getParameterB();
System.out.println("paramA="+paramA+"paramB="+paramB);
}
}
class AppConfig{
private String parameterA;
private String parameterB;
//增加该类静态成员:
private static AppConfig appconfig;
//构造方法改为私有:
private AppConfig(){
readConfig();
}
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
private void readConfig() {
Properties p=new Properties();
InputStream in=null;
try {
in=AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA=p.getProperty("paramA");
this.parameterB=p.getProperty("paramB");
}catch(IOException e) {
System.out.println("装载配置文件出错!");
e.printStackTrace();
}finally{
try {
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
//添加静态getAppConfig()方法
public static AppConfig getAppConfig() {
if(appconfig==null) {
appconfig=new AppConfig();
}
return appconfig;
}
}
2)多线程的懒汉单例模式:
package computer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
AppConfig config=AppConfig.getAppConfig();
String paramA=config.getParameterA();
String paramB=config.getParameterB();
System.out.println("paramA="+paramA+"paramB="+paramB);
}
}
class AppConfig{
private String parameterA;
private String parameterB;
//增加该类静态成员:
private static AppConfig appconfig;
//增加
private static final Object syncRoot=new Object();
//构造方法改为私有:
private AppConfig(){
readConfig();
}
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
private void readConfig() {
Properties p=new Properties();
InputStream in=null;
try {
in=AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA=p.getProperty("paramA");
this.parameterB=p.getProperty("paramB");
}catch(IOException e) {
System.out.println("装载配置文件出错!");
e.printStackTrace();
}finally{
try {
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
//添加静态getAppConfig()方法
public static AppConfig getAppConfig() {
synchronized(syncRoot) {
if(appconfig==null) {
appconfig=new AppConfig();
}
}
return appconfig;
}
}
3)双重锁定的懒汉模式:
package computer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
AppConfig config=AppConfig.getAppConfig();
String paramA=config.getParameterA();
String paramB=config.getParameterB();
System.out.println("paramA="+paramA+"paramB="+paramB);
}
}
class AppConfig{
private String parameterA;
private String parameterB;
//增加该类静态成员:
private static AppConfig appconfig;
//构造方法改为私有:
private AppConfig(){
readConfig();
}
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
private void readConfig() {
Properties p=new Properties();
InputStream in=null;
try {
in=AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA=p.getProperty("paramA");
this.parameterB=p.getProperty("paramB");
}catch(IOException e) {
System.out.println("装载配置文件出错!");
e.printStackTrace();
}finally{
try {
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
//添加静态getAppConfig()方法
public static AppConfig getAppConfig() {
if(appconfig==null) {
synchronized(AppConfig.class) {
if(appconfig==null) {
appconfig=new AppConfig();
}
}
}
return appconfig;
}
}
4)饿汉单例模式:
package computer;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class Test {
public static void main(String[] args) {
AppConfig config=AppConfig.getAppConfig();
String paramA=config.getParameterA();
String paramB=config.getParameterB();
System.out.println("paramA="+paramA+"paramB="+paramB);
}
}
class AppConfig{
private String parameterA;
private String parameterB;
//添加该类的静态私有成员:
private static AppConfig appconfig=new AppConfig();
//修改构造函数为私有:
private AppConfig(){
readConfig();
}
public String getParameterA() {
return parameterA;
}
public String getParameterB() {
return parameterB;
}
private void readConfig() {
Properties p=new Properties();
InputStream in=null;
try {
in=AppConfig.class.getResourceAsStream("AppConfig.properties");
p.load(in);
this.parameterA=p.getProperty("paramA");
this.parameterB=p.getProperty("paramB");
}catch(IOException e) {
System.out.println("装载配置文件出错!");
e.printStackTrace();
}finally{
try {
in.close();
}catch(IOException e) {
e.printStackTrace();
}
}
}
//添加静态getAppConfig()方法
public static AppConfig getAppConfig() {
return appconfig;
}
}