标签:cat oid 第一个 饿汉式 == 说明 单例对象 过程 返回
单例模式是指某个类有唯一的实例。
最常见的获取单例的方法有两种:饿汉式和懒汉式。
public class Single1 {
private static Single1 single1;
private Single1(){}
public static Single1 getInstance(){
if (single1 == null){
single1 = new Single1();
}
return single1;
}
}
懒汉式单例模式,意思是调用getInstance()后才会生成对象,并返回对象,即需要的时候才会生成一个对象。
注意:getInstance()方法为static修饰的静态方法,使得方法属于类,通过[Single1.]便可调用getInstance(),避免其他类中创建Single1对象,另外private修饰的构造方法表明,只能在该类中生成对象。
懒汉式单例模式的特点是延迟加载,在需要该对象的时候才会生成对象,节省了不必要的空间;但该模式也存在一个致命的缺点,后面会讲到。
public class Single2 {
private static final Single2 SINGLE2 = new Single2();
private Single2(){}
public static Single2 getInstance(){
return Single2.SINGLE2;
}
}
饿汉式单例模式,意思是类加载就会立马生成一个对象,为了保证只能生成一个该类的对象,所以用关键字final修饰,即不可变。当需要该对象的时候,调用getInstance()方法即可返回该唯一的对象。
饿汉式单例模式的特点:饿汉式故名思议好比处于饥饿状态,类加载后立马生成该类的实例,不管需不需要;这容易造成空间的浪费,相比懒汉式单例,饿汉式单例不可用。
饿汉式单例模式还可以换一种方法来实现,即便不推荐使用;饿汉式静态模块单例模式:
public class Single3 {
private Single3(){}
private static Single3 single3;
static
{
single3 = new Single3();
}
public static Single3 getInstance(){
return single3;
}
}
饿汉式静态模块单例模式的本质跟上面的饿汉式单例模式一样,只不过把直接生成单例的过程放到了静态代码块,即在类加载的过程中会执行静态代码块的内容,即生成了single3实例。
注意:这里的single3引用没有final关键字,因为一个类加载只会执行一次静态代码块,所以能确保single3的实例是唯一的。
上面说到懒汉式单例模式,即Single1有一个致命的缺点,我们通过代码来说明:
public class Client {
static Single1 single1;
public static void main(String[] args) {
for (int i = 0; i < 10; i++){
Runnable runnable = new Runnable(){
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
single1 =Single1.getInstance();
System.out.println(single1);
}
};
new Thread(runnable).start();
}
}
}
代码含义:把懒汉式单例模式放到多线程下执行。
Thread.sleep(2000)是让当前执行单例模式的线程休眠2秒,让线程不那么快执行完,便于看到效果。
运行后的某一种结果:
com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@34be8216
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@762589c3
com.design.single.Single1@474e8d67
com.design.single.Single1@474e8d67
com.design.single.Single1@34be8216
com.design.single.Single1@23194cf5
很明显,10个线程调用懒汉式单例模式后返回的对象居然有4个,这与单例模式的含义相违背,表明懒汉式单例模式在多线程下不可行,这是懒汉式单例模式致命的缺点。
分析执行过程可知,当线程1在执行if(single1 == null)后执行singe1 = new Single1()生成对象的同时,线程2也正好在判断if(single1 == null),由于线程1的对象还没有生成,所以线程2的判断为true,便进一步执行single1 = new Single1(),所以便会生成多个Single1对象。
为了解决这个问题,我们自然想到synchronized关键字,即在获取single1对象的方法getInstance()上加锁,使其成为
public class Single1 {
private static Single1 single1;
private Single1(){}
public static synchronized Single1 getInstance(){
if (single1 == null){
single1 = new Single1();
}
return single1;
}
}
同步方法getInstance()能保证每次只有一个线程进入方法内部,这样便能保证单例。
同步方法的特点:通过synchronized关键字使得线程获得Single1类的内置锁从而保证每次只有一个线程执行getInstance()。进一步分析执行过程:线程1执行方法getInstance时获取Single1的内置锁,此时线程2过来想要执行方法getInstance,发现方法所属类的锁已经被线程1占有,所以线程2被阻塞,接着线程3,线程4…继续被阻塞,这样导致的结果就是执行的效率非常低。我们想想,其实只有线程1进入方法getInstance生成实例后,方法2、3、4…都不需要生成实例,因此也就没必要阻塞后面的线程,所以可以缩小同步的范围,即把从同步方法变成同步代码块。
public class Single1 {
private static volatile Single1 single1;
private Single1(){}
public static Single1 getInstance(){
if (single1 == null){
synchronized(Single1.class){
if (single1 == null){
single1 = new Single1();
}
}
}
return single1;
}
}
双重检查的单例模式把同步从方法级别移到方法内部,只对必要的代码块进行同步;注意这里有两个判空,所以称为双重检查。第一次判空是所有线程都会执行的,当线程1判空后,就会获取Single1类的内置锁,线程1则势必会执行single1 = new Single1(),生成single1实例;假如线程1在执行同步代码块的时候,线程2进入方法getInstance,第一次判空为true,此时Single1的内置锁被线程1占有,因此被阻塞,当线程1执行完后退出同步代码块释放Single1的内置锁,线程2就会进入同步代码块,此时若线程3进来,因为线程1已经生成single1对象,所以线程3的第一次判空为false,则线程3执行返回对象,线程2进行第二次判空同样为false,直接返回single1实例。后面的线程在第一个判空处便为false。
双重检查单例模式的特点就是线程安全,延迟加载,效率高,比较常用。
注意:声明single1对象时,使用了volatile关键字。volatile关键字可以保证single1对象的可见性和有序性;这是防止指令重排从而保证single1对象的唯一。
静态内部类的单例模式:
public class Single4 {
private Single4(){
}
private static class Single4Inner{
private static final Single4 SINGLE4 = new Single4();
}
public static Single4 getInstance(){
return Single4Inner.SINGLE4;
}
}
静态内部类的单例模式与饿汉式单例模式有点类似,唯一的不同在于,饿汉式单例模式中单例对象是随着Single2类加载而生成,而静态内部类单例模式则通过静态内部类产生单例对象,其利用静态内部类不会随着外部类的加载而加载的特性使得当getInstance方法被调用后Single4Inner类才会被加载,从而生成SINGLE4对象,同时用static和final修饰,保证只会生成一个SINGLE4对象,保证了其线程的安全。
public enum Single5 {
SINGLE_5;
public Single5 getInstance() {
return SINGLE_5;
}
}
枚举类单例模式是最实用的一种单例模式。枚举类本身带有私有的构造方法,而每个枚举对象都是static和final修饰的对象,表明对象只能被实例化一次,所以在枚举实例的时候就会产生单例。
以上是所有产生单例的方法,总结有:饿汉式单例,懒汉式单例,饿汉式静态模块单例,同步方法单例,双重检查单例,静态内部类单例以及枚举类单例。
2 单例模式的原理
单例模式能够保证一个类仅有一个实例,并提供一个访问它的全局访问点。
3 单例模式的特点
故名思议,单例模式表明某个类只有一个实例。
标签:cat oid 第一个 饿汉式 == 说明 单例对象 过程 返回
原文地址:https://www.cnblogs.com/fplblog/p/11100182.html