需求 :保证了一个类在内存中只能有一个对象
思路:1、如果其他程序能够随意用new创建该类对象,那么就无法控制个数。因此,不让其他程序用new创建该类的对象。
2、既然不让其他程序new该类对象,那么该类在自己内部就要创建一个对象,否则该类就永远无法创建对象了。
3、该类将创建的对象对外(整个系统)提供,让其他程序获取并使用。
解决步骤:
- 将该类中的构造函数私有化。
- 在本类中创建一个本类对象。
- 定义一个方法,返回值类型是本类类型。让其他程序通过该方法就可以获取到该类对象。
单例的设计模式包括:
- 饿汉式
- 懒汉式
饿汉式 介绍:
缺点:系统启动时就会加载,会造成内存地址资源浪费
简单代码实现
package cn.hncu.designl.sigal;
//饿汉式
public class P1 {
private static final P1 p=new P1();//为了不让直接访问,将其私有化
private P1(){};//构造方法私有化
public static P1 get(){//通过类名可以掉这个方法,从而可以访问到私有的构造函数
System.out.println("11111111");
return p;//返回可以造对象的p
}
}
懒汉式:
和饿汉式不同,再调用的时候进行判断 new对象;在使用时创建对象
在判断时候需要将其上锁,不然多线程会出现问题(本人清测)
线程A希望使用SingletonClass,调用getInstance()方法。因为是第一次调用,A就发现instance是null的,于是它开始创建实例,就在这个时候,CPU发生时间片切换,线程B开始执行,它要使用SingletonClass,调用getInstance()方法,同样检测到instance是null——注意,这是在A检测完之后切换的,也就是说A并没有来得及创建对象——因此B开始创建。B创建完成后,切换到A继续执行,因为它已经检测完了,所以A不会再检测一遍,它会直接创建对象。这样,线程A和B各自拥有一个SingletonClass的对象——单例失败!
package cn.hncu.designl.sigal;
public class P2 {
private static P2 p=null;//与饿汉式不一样,这里先不要new 对象,但是同样的私有化
private P2(){}//私有化
public static synchronized P2 getinstance(){//若是多线程会出现错误(本人清测),所以需要synchronized将其锁起来
if(p==null){//为空就直接new 对象
p=new P2();
}
return p;//返回 造的对象
}
}
设计思想高深,这个只是简单的用代码稍微的演示下,下面是调用上面的测试程序
package cn.hncu.designl.sigal;
public class test {
public static void main(String[] args) {
P1 p1=P1.get();
P1 p2=P1.get();
System.out.println(p1==p2);//饿汉式单模式//直接就赋值
//懒汉式
P2 p3=P2.getinstance();//类名直接调
P2 p4=P2.getinstance();
System.out.println(p3==p4);
}
}
=========================补充
懒汉式 添加synchronized 关键词上锁效率低下。
新增几种
双重检测锁实现
public static SingletonLazy4 getInstance() {
if (instance == null) {
//都排队到这里了,就会执行多次新建对象
synchronized(SingletonLazy4.class){
if(instance == null ){
// 这里其实还是会有问题cpu 指令重排 ,5版本解决
// new 对象分为
//1 分配内存空间 2、初始化类成员 3、将对象指向分配内存地址
instance = new SingletonLazy4();
}
}
}
return instance;
}
上面 new 对象模式 可能会发生指令重排现象,还是可能出现线程安全问题
解决指令重排问题
// 声明私有对象
// 解决 指定重排问题
private volatile static SingletonLazy5 instance;
// 获取实例(单例对象)
public static SingletonLazy5 getInstance() {
// 第一次判断
if (instance == null) {
//都排队到这里了,就会执行多次新建对象
synchronized(SingletonLazy5.class){
if(instance == null ){
// 第二次判断
// 这里其实还是会有问题cpu 指令重排 ,5版本解决
// new 对象分为
//1 分配内存空间 2、初始化类成员 3、将对象指向分配内存地址
instance = new SingletonLazy5();
}
}
}
return instance;
}
上面的基本上完美解决多线程下单例问题
更好的建议:
内部类实现
private static class SingletonInstance{
private static final SingletonLazy6 instance = new SingletonLazy6();
}
public static SingletonLazy6 getInstance(){
return SingletonInstance.instance;
}
private SingletonLazy6(){
}
// 实现对应的方法
public void sayHi() {
// 输入对应的内容地址对象
System.out.println(this.toString() + "hi java- 懒汉");
}
枚举类实现
// 枚举类型是线程安全的,并且只会装载一次
private enum SingletonEnum {
INSTANCE;
// 声明单例对象
private final SingletonLazy7 instance;
// 实例化
SingletonEnum() {
instance = new SingletonLazy7();
}
private SingletonLazy7 getInstance() {
return instance;
}
}
// 获取实例(单例对象)
public static SingletonLazy7 getInstance() {
return SingletonEnum.INSTANCE.getInstance();
}
// 实现对应的方法
public void sayHi() {
// 输入对应的内容地址对象
System.out.println(this.toString() + "hi java- 懒汉");
}
最后:最好使用内部类实现单例,节省资源。枚举使用也好,但是使用者很少
实例代码地址: