1.单例模式:确保一个类只有一个实例,并提供一个全局访问点来访问这个唯一的实例。
对于单例模式,在单例类的内部创建它的唯一实例,并通过静态方法getInstance()让客户端可以使用它的唯一实例;为了防止在外部对单例类实例化,将其构造函数的可见性设置为private,在单例类内部定义一个singleton类型的静态对象作为供外部共享访问的唯一实例。
/**这种单例模式示例有问题存在,下文中会做完善,欢迎思考*/
public class Singleton{
private static Singleton instance = null;//静态私有成员变量
//私有构造函数
private Singleton(){
}
//静态共有工厂方法,返回唯一实例
public static Singleton getInstance(){
if(instance == null)
instance = new Singleton();
return instance;
}
}
/**为了测试单例类创建对象的唯一性,可以编写以下的客户测试代码*/
public class Client{
public static void main(String args[]){
Singleton sl = Singleton.getInstance();
Signleton s2 = Singleton.getInstance();
//判断两个对象是否相同
if(s1 == s2){
System.out.pringln("两个对象是相同实例");
}
else{
System.out.pringln("两个对象是不同实例");
}
}
}
2.实例描述:一家四口人,只有一个碗,每个人想吃饭都只能用这一个碗来单独吃饭。这个碗就设计为单例类,这个碗中有个吃饭的单子,每次都会随机选一个人来吃饭。
/**实例代码,这种单例模式有存在问题,下文会做完善,欢迎同学用完善后的单例模式把此示例再做修改*/
/**创建单例类*/
import java.util.*;
public class Bowl{
//私有静态成员变量,存储唯一实例
private static Bowl instance = null;
//人的集合
private List personList = null;
//私有构造函数
private Bowl(){
personList = new ArrayList();
}
//公有静态成员方法,返回唯一实例
public static Bowl getBowl(){
if(instance == null){
instance = new Bowl();
}
return instance;
}
//增加吃饭的人
public void addPerson(String person){
personList.add(person);
}
//删除服务器
public void removePerson(String person){
personList.remove(person);
}
//使用Random类随机获取服务器
public String getPerson(){
Random random = new Random();
int i = random.nextInt(personList.size());
return(String)personList.get(i);
}
}
/**客户端测试类*/
public class Client{
public static void main(String args[]){
//创建四个Bowl碗对象
Bowl Bowl1,Bowl2,Bowl3,Bowl4;
Bowl1 = Bowl.getBowl();
Bowl2 = Bowl.getBowl();
Bowl3 = Bowl.getBowl();
Bowl4 = Bowl.getBowl();
//判断Bowl碗是否想同
if(Bowl1== Bowl2&Bowl2== Bowl3&Bowl3 == Bowl4){
System.out.println("碗具有唯一性");
}
//增加吃饭的人
Bowl1.addPerson("Person1");
Bowl2.addPerson("Person2");
Bowl3.addPerson("Person3");
Bowl4.addPerson("Person4");
//模拟客户端请求的分发,如果输出结果为同一个person,可以将i适当放大
//例如改为i<100
for(int i = 0;i<10;i++){
String person = Bowl1.getPerson();
System.out.println("分发请求到吃饭的人:"+person);
}
}
}
输出结果如下图:
3.饿汉单例模式
public class EagerSingleton{
private static final EagerSingleton instance = new EagerSingleton();
private EagerSingleton(){}
public static EagerSingleton getInstance(){
return instance;
}
}
此种方式虽然是线程安全的,但并不是懒加载,也就是程序中无论用不用的到这样的类都会在程序启动之初就进行创建。这样就会导致类似下载一个游戏,可能地图没有打开,程序已经把所有的地图实力化了,手机的体验是一开游戏内存就满了,手机卡的不成样子。
4.懒汉单例模式
/**为了确保线程安全,加了synchronized锁*/
public class LazySingleton{
private static LazySingleton instance = null;
private LazySingleton(){};
//使用synchronized关键字对方法加锁,确保任意时刻只有一个线程可执行该方法
public static synchronized LazySingleton getInstance(){
if(instance==null){
instance = new LazySingleton();
}
return instance;
}
}
/**此模式虽然安全,但是由于把锁加到方法上后,所有的访问都会因为需要锁占用导致资源的浪费,如果不是特殊情况不建议使用此种单例模式*/
改进
···
public static LazySingleton getInstance(){
if(instance == null){
synchronized(LazySingleton.class){
instance=new LazySingleton();
}
}
return instance;
}
···
再次改进:使用双重检查锁定来实现懒汉式单例模式。volatile修饰的成员变量可以确保多个线程能够正确处理,但是volatile关键字会屏蔽Java虚拟机所做的一切代码优化,所以可能会导致系统的运行效率降低。上面存在的问题:单例对象不唯一的情况。例如在某一瞬间,线程A和线程B都在调用getInstance()方法,此时instance对象为null值,均能通过instance==null的判断。由于实现了synchronized加锁机制,线程A执行完毕后进入synchronized锁定的代码中执行实例创建代码。但是当A执行完毕时线程B并不知道实例已经创建,将继续创建新的实例,导致产生多个实例,违背了单例模式的设计思想,因此需要进一步改进,在synchronized中再进行一次instance==null的判断,称为双重检查锁定。
public class LazySingleton{
private volatile static LazySingleton instance = null;
private LazySingleton(){}
public static LazySingleton getInstance(){
//第一重判断
if(instance == null){
//锁定代码块
synchronized (LazySingleton.class){
//第二重判断
if(instance == null){
instance = new LazySingleton();//创建单例实类
}
}
}
rerurn instance;
}
}
5.使用静态内部类实现单例模式
Java语言通过Initialization on Demand Holder(IoDH)技术来实现单例模式。需要在单例类中新加一个静态内部类,在该内部类中创建单例对象,再对该单例对象通过getInstance()方法返回给外部使用。
public class Singleton{
private Singleton(){}
//静态内部类
private static class HolderClass{
private final static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return HolderClass.instance;
}
public static void main(String args[]){
Singleton s1,s2;
s1 = Singleton.getInstance();
s2 = Singleton.getInstance();
System.out.println(s1 == s2);
}
}
优点解决了懒汉模式和恶汉模式的缺点。缺点是与编程语言本身的特性相关,很多面向对象的语言都不支持IoDH。
6.CAS(AtomicReference)(线程安全)此方法用的不多。
7.枚举单例模式(线程安全)此方法用的不多。