示例1:
饿汉式单例
package com.单例模式;
//饿汉式(类加载时就初始化)
public class SingletonHungryV1 {
//加载类的时候。直接初始化
private static SingletonHungryV1 singleton = new SingletonHungryV1();
//私有化该类,防止从外部创建该对象。
private SingletonHungryV1(){}
public static SingletonHungryV1 getSingleton(){
return singleton;
}
}
懒汉式单例
package com.单例模式;
//懒汉式(调用时再初始化 )
public class SingletonLazyV1 {
//首先在静态区定义一个SingletonLazyV1对象
private static SingletonLazyV1 singletonLazy;
//更进一步做法是,构造函数改为私有的,防止从外部初始化。也可以不用。
//private SingletonLazyV1() {}
private static SingletonLazyV1 getSingletonLazy(){
//判断静态区是否存在该对象
if(singletonLazy == null){
//不存在,则初始化
singletonLazy = new SingletonLazyV1();
}
//返回
return singletonLazy;
}
}
多线程同步单例:
package com.单例模式;
public class SingletonSynchronizedV2 {
//首先在静态区定义一个SingletonSynchronizedV2对象
private static SingletonSynchronizedV2 singletonSynchronized;
//更进一步做法是,构造函数改为私有的,防止从外部初始化。也可以不用。
private SingletonSynchronizedV2() {}
//在getSingletonSynchronized方法前加synchronized关键是修饰方法,锁的对象是同一个SingletonSynchronized
private static synchronized SingletonSynchronizedV2 getSingletonSynchronized(){
/*
* 此处有一个问题,在多线程的情况下,有两个线程同时执行了if(singletonSynchronized == null)
* 这段代码,那么两个线程都创建了一个对象,这样就造成了不是单例
* 解决办法:在方法上加锁,当一个线程正在创建的时候,另外的线程阻塞
* */
if(singletonSynchronized == null){
//不存在,则初始化
singletonSynchronized = new SingletonSynchronizedV2();
}
//返回
return singletonSynchronized;
}
}
双重检查提高效率 创建单例
package com.单例模式;
public class SingletonDoubleCheckV3 {
//首先在静态区定义一个SingletonLazy对象
private static SingletonDoubleCheckV3 singletonDoubleCheck;
//更进一步做法是,构造函数改为私有的,防止从外部初始化。也可以不用。
private SingletonDoubleCheckV3() {}
//在getSingletonSynchronized方法前加synchronized关键是修饰方法,锁的对象是同一个SingletonSynchronized
/*
* 在getSingletonDoubleCheck方法上加锁的方式,太影响性能了
* 比如两个线程,都需要获取SingletonDoubleCheck对象,当线程一获取对象的时候,
* 线程二必须等待。(即除了线程一外,所有线程必须等待),不管此时静态区是否已经存在该对象,
* 都得等待,显然不合理。如果静态区已经有了该对象,直接return SingletonDoubleCheck对象即可
* 大家都不必等待,只有真正去创建该对象时,加锁等待。
* 改进办法:进行双重检查
*
* */
private static SingletonDoubleCheckV3 getSingletonDoubleCheck(){
/*
* 首先判断该对象是否在静态区存在,存在,大家都不用等待,直接得到对象即可
* 否则,就加锁创建对象
* 第一个if (instance == null),其实是为了解决Version2中的效率问题,只有instance为null的时候,
* 才进入synchronized的代码段——大大减少了几率。
* 第二个if (instance == null),则是跟直接再方法上加锁一样,是为了防止可能出现多个实例的情况。
*/
if(singletonDoubleCheck == null){
synchronized(SingletonDoubleCheckV3.class){
if(singletonDoubleCheck == null){
//不存在,则初始化
singletonDoubleCheck = new SingletonDoubleCheckV3();
}
}
}
//返回
return singletonDoubleCheck;
}
}
防止指令重排创建单例
package com.单例模式;
public class SingletonVolatileV4 {
//首先在静态区定义一个SingletonLazy对象
/*
* 声明SingletonVolatileV4类型的变量前加上volatile关键字的原因:
* singletonVolatileV4 = new SingletonVolatileV4();这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
* 步骤1.给SingletonVolatileV4对象分配内存
* 步骤2.调用SingletonVolatileV4类的构造函数,初始化成员变量,形成实例
* 步骤3.将SingletonVolatileV4对象指向分配的内存空间(这个时候SingletonVolatileV4对象才非null)
*
* 但是在 JVM 的即时编译器中存在指令重排序的优化。
* 也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。
* 如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 singletonVolatileV4 已经是非 null 了(但却没有进行初始化),
* 所以线程二会直接返回 singletonVolatileV4,然后使用,然后顺理成章地报错了。
*
* 就是说,由于有一个『singletonVolatileV4已经不为null但是仍没有完成初始化』的中间状态,
* 而这个时候,如果有其他线程刚好运行到第一层if (singletonVolatileV4 == null)这里,
* 这里读取到的singletonVolatileV4已经不为null了,所以就直接把这个中间状态的singletonVolatileV4拿去用了,
* 就会产生问题。
*
* 这里的关键在于——线程T1对singletonVolatileV4的写操作没有完成,线程T2就执行了读操作。
*
* volatile关键字的一个作用是禁止指令重排,把singletonVolatileV4声明为volatile之后,
* 对它的写操作就会有一个内存屏障(什么是内存屏障?),
* 这样,在它的赋值完成之前,就不用会调用读操作。
*
* 引用注意:volatile阻止的不是singletonVolatileV4 = new SingletonVolatileV4();
* 这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,
* 不会调用读操作(if(singletonVolatileV4 == null))。
* */
private static volatile SingletonVolatileV4 singletonVolatileV4;
//更进一步做法是,构造函数改为私有的,防止从外部初始化。也可以不用。
private SingletonVolatileV4() {}
//在getSingletonSynchronized方法前加synchronized关键是修饰方法,锁的对象是同一个SingletonSynchronized
/*
* 在getSingletonDoubleCheck方法上加锁的方式,太影响性能了
* 比如两个线程,都需要获取SingletonDoubleCheck对象,当线程一获取对象的时候,
* 线程二必须等待。(即除了线程一外,所有线程必须等待),不管此时静态区是否已经存在该对象,
* 都得等待,显然不合理。如果静态区已经有了该对象,直接return SingletonDoubleCheck对象即可
* 大家都不必等待,只有真正去创建该对象时,加锁等待。
* 改进办法:进行双重检查
*
* */
private static SingletonVolatileV4 getSingletonVolatileV4(){
/*
* 首先判断该对象是否在静态区存在,存在,大家都不用等待,直接得到对象即可
* 否则,就加锁创建对象
* 第一个if (instance == null),其实是为了解决Version2中的效率问题,只有instance为null的时候,
* 才进入synchronized的代码段——大大减少了几率。
* 第二个if (instance == null),则是跟直接再方法上加锁一样,是为了防止可能出现多个实例的情况。
*/
if(singletonVolatileV4 == null){
synchronized(SingletonVolatileV4.class){
if(singletonVolatileV4 == null){
//不存在,则初始化
singletonVolatileV4 = new SingletonVolatileV4();
}
}
}
//返回
return singletonVolatileV4;
}
}
其他方式创建单例:静态内部类形式
package com.单例模式;
/*
* 这种写法非常巧妙:
* 对于内部类SingletonInnerStaticInner,它是一个饿汉式的单例实现,
* 在SingletonInnerStaticInner初始化的时候会由ClassLoader来保证同步,
* 使singletonInnerStatic是一个真·单例。
* 同时,由于SingletonInnerStatic是一个内部类,
* 只在外部类的SingletonInnerStatic的getSingletonInnerStatic()中被使用,
* 所以它被加载的时机也就是在getInstance()方法第一次被调用的时候。
*
* 它利用了ClassLoader来保证了同步,同时又能让开发者控制类加载的时机。
* 从内部看是一个饿汉式的单例,但是从外部看来,又的确是懒汉式的实现。
* 简直是神乎其技。
* */
public class SingletonInnerStatic {
//采用静态内部类
private static class SingletonInnerStaticInner{
private static final SingletonInnerStatic singletonInnerStatic = new SingletonInnerStatic();
}
private SingletonInnerStatic(){}
public static final SingletonInnerStatic getSingletonInnerStatic(){
return SingletonInnerStaticInner.singletonInnerStatic;
}
}
注意:多线程中一般使用静态类部类来实现单例。
参考地址:聊聊java中的单例