简言
单列模式:单一,也就是说一个类只能有一个对象。就类似于有些软件只能打开一次,当需要保证一个对象在内存中的唯一性时,就需要引入单列模式。
实现步骤
创建单列模式分三步操作:
1.将构造函数私有化
2.在类中创建一个本类对象
3.提供一个公有的接口来返回创建的类
相关说明
我们访问类里面的数据时,分两种情况:
1.通过实例化对象,然后通过对象的引用“.”出方法或属性。
2.通过类名“.”调用类里面静态的方法或属性。
当我们将类的构造方法私有化时,此时我们无法在类外实例化对象,所以只能通过第二种方法在内部创建一个static修饰的该类对象,然后定义一个static修饰的公有的方法将创建的类返回出去。static修饰的数据是跟随整个类的,在类加载的时候跟着类一起创建,由于构造函数私有化了,所以这个类也就创建了一次。
饿汉式
创建一个Singlehungry类
package com.single_mode;
/**
* 单列模式:一个类只能被创建一次,将类的构造方法私有化,提供公有的返回该对象的方法
*
* 饿汉式:加载类的时候就创建了创建了一个Single实例
*/
public class Singlehungry {
private String name;
private int age;
public String getName() { return name; }
public int getAge() { return age; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
private Singlehungry(){ }//将构造函数私有化
private static Singlehungry single=new Singlehungry();
public static Singlehungry getInstance(){
return single;
}//提供公有访问的接口,将该对象返回出去
}
创建一个测试类Main类
package com.single_mode;
public class Main{
public static void main(String[] args) {
//饿汉式
Singlehungry single1=Singlehungry.getInstance();
single1.setName("筱静");
single1.setAge(20);
System.out.println("single1:"+single1.getAge()+" "+single1.getName());
Singlehungry single2=Singlehungry.getInstance();
System.out.println("single2:"+single2.getAge()+" "+single2.getName());
}
}
运行结果:
single1和single2是指向的同一个对象
让我们画张图来了解一下:
当我们Singlehungry.加载类时,这个类的静态属性在方法区也就随着加载,首先在堆区new了一个Singlehungry的对象,假设它的地址为0x10,方法区的single保存了堆区Singlehungry对象的地址,即single=0x10。然后在栈区调用getInstance()方法,返回single,并用single1来接收,single1也就指向堆区的Singlehungry对象,然后set进行赋值。再次调用getInstance()方法时,调用之前加载好的Singlehungry对象,也就是single2=single=0x10。所以再次get时,它的值是相同的。
懒汉式
创建一个Singlelazy类
package com.single_mode;
/**
* 懒汉式:
*/
public class Singlelazy {
private String name;
private int age;
private static Singlelazy singlelazy=null;
private Singlelazy(){ }
// public static Singlelazy getInstence(){ //线程不安全
// if(singlelazy==null)
// singlelazy=new Singlelazy();
// return singlelazy;
// }
public static Singlelazy getInstence(){
if(singlelazy==null) {
synchronized(Singlelazy.class) {
if (singlelazy == null)
singlelazy = new Singlelazy();
}
}
return singlelazy;
}
public String getName() { return this.name; }
public int getAge() { return age; }
public void setName(String name) { this.name = name; }
public void setAge(int age) { this.age = age; }
}
在我们用懒汉式发生了延时加载,首先方法区里面singlelazy赋值为null,在调用getInstence()方法时先判断一下,然后创建对象,而饿汉式在加载类的同时single就在堆区new了一个对象,这也是它们的区别所在。
由于方法区是线程共享的,懒汉式又有if判断然后创建对象。因此在多线程的情况下可能存在线程安全问题,需要线程同步用到synchonized关键字。如果将getInstence()整个函数个锁住,开销可能比较大,不太可取。可直接将判断那部分锁住即可。
或许你会问为什需要加上两个if判断?
假设有A,B,C三个线程,首先A线程抢到了CPU的执行权,执行 singlelazy==null 再执行下一个singlelazy==null 并且锁上,当A刚第二次判断时,CPU的执行权可能被C抢到了(CPU高速不断的切换线程,给我们的感觉每个进程异步执行),C线程进行第一层判断,由于第二层if上锁了,在A线程执行完毕之前 ,C线程无法继续执行的,只能等待了。C在等待的过程中CPU可能又切到了A线程,A线程接着执行,当A线程创建对象后,把锁打开 return singlelazy;。B线程得到了CPU的执行权,第一轮if判断结束false 就直接return singlelazy;最后C线程的到CPU执行权,由于A线程已把锁打开,C线程继续刚才的执行,进行第二次判断false,return singlelazy;最终得到的是A线程所创建的一个对象,解决了线程互斥问题。
每日鸡汤:耐得住寂寞、经得起诱惑。你会发现通往成功的道路并不拥挤,因为能坚持到最后的没有几个!
Over !