单例模式:(Singleton Pattern):
保证在整个应用程序的生命周期中,任何时刻,单例类的实例都是只存在一个(也可以不存在)。
单例模式需要注意的几个点
- 设置私有构造器;
- 线程安全的保证;
- 延迟加载;
- 序列化和反序列化引发的安全问题;
- 如何防御反射攻击。
实现单例模式的方式
- 懒汉模式
- 饿汉模式
- 使用枚举
饿汉模式1
package com.mark.singleton;
/**
* 单例模式:饿汉模式
* 应用场合:有些对象只需要一个就可以了(配置日志文件,工具类,线程池,缓存,日志对象)
* 作用:保证整个应用程序中某个实例有且只有一个
* @author mark
*
*/
public class Singleton {
//1.默认构造方法私有化,不允许外部通过new创建对象
private Singleton(){
}
//2.创建类的唯一实例:使用private static 修饰(在类加载的时候会实例化这个对象)
private static Singleton instance = new Singleton();
//3.向外提供一个返回实例的方法:使用public static 修饰
public static Singleton getInstance(){
return instance;
}
}
饿汉模式2
package com.mark.example.singleton;
import com.mark.annotations.ThreadSafe;
/**
* author:Mark
* date:2018/7/30 23:31
* 饿汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
public class SingletonTest6 {
//私有的构造函数
private SingletonTest6() {
}
//单例对象
private static SingletonTest6 instance = null;
//在静态代码块中实例出这个对象【这个静态代码块和静态变量的声明的顺序】
static {
instance = new SingletonTest6();
}
//静态工厂方法
public static SingletonTest6 getInstance(){
return instance;
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}
懒汉模式
懒汉因为自己太懒了,在声明对象的时候不进行初始化。
package com.mark.singleton;
/**
* 单例模式:懒汉模式
* 应用场合:有些对象只需要一个就可以了(配置日志文件,工具类,线程池,缓存,日志对象)
* 作用:保证整个应用程序中某个实例有且只有一个
* @author mark
*
*/
public class Singleton2 {
//1.默认构造方法私有化,不允许外部通过new创建对象
private Singleton2(){
}
//2.创建类的唯一实例:使用private static 修饰
private static Singleton2 instance;
//3.向外提供一个返回实例的方法:使用public static 修饰
public static Singleton2 getInstance(){
// 【在多线程环境下】如果A、B两个都执行到if (instance == null) { 那么就会被初始化两次(线程不安全)
if(instance == null){//如果实例为空,才进行实例化,也就是仅仅第一调用该方法才进行对象的实例化
instance = new Singleton2();
}
return instance;
}
}
测试类
package com.mark.singleton;
import org.junit.Test;
public class SingletonTest {
//饿汉模式的测试
@Test
public void Singleton() {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
if(s1 == s2){
System.out.println("饿汉模式:同一个实例");
}else{
System.out.println("饿汉模式:不同实例");
}
}
//懒汉模式的测试
@Test
public void Singleton2() {
Singleton2 s3 = Singleton2.getInstance();
Singleton2 s4 = Singleton2.getInstance();
if(s4 == s3){
System.out.println("懒汉模式:同一个实例");
}else{
System.out.println("懒汉模式:不同实例");
}
}
}
运行结果:
懒汉模式:同一个实例
饿汉模式:同一个实例
区别
饿汉模式:实例化单例类的对象在类加载的时候(线程安全);
懒汉模式:实例化单例类的对象在第一次调用获取实例方法的时候(线程不安全);
懒汉模式如何设计为线程安全?
既然上面代码中的懒汉模式是线程不安全的?那么在哪些地方加锁可以保证线程安全呢?
demo:加上同步锁,可以保证线程安全,但是此时锁住的静态方法,相当于锁住了整个类。结论:使用同步锁可以保证线程安全,但是影响性能。
package com.mark.example.singleton;
import com.mark.annotations.NotRecommend;
import com.mark.annotations.ThreadSafe;
/**
* author:Mark
* date:2018/7/30 23:31
* 懒汉模式
* 单例实例在类装载时进行创建
*/
@ThreadSafe
@NotRecommend
public class SingletonTest3 {
//私有的构造函数
private SingletonTest3() {
}
//单例对象
private static SingletonTest3 instance = null;
//静态工厂方法
//【线程安全】,但是正因为加上了同步锁,会导致同一个时间内只会允许一个线程来访问该方法
public static synchronized SingletonTest3 getInstance(){//加上了同步锁
if (instance == null) {
instance = new SingletonTest3();
}
return instance;
}
}
同步锁锁住整个方法是线程安全的。但是也是正因为在执行的时候会锁住整个方法,这样在多线程的环境会会带来性能方面的开销等问题,如果只是对 instance = new SingletonTest3();这一行代码加锁,效果会怎样呢?
demo2:double check(双重检测)
package com.mark.example.singleton;
import com.mark.annotations.NotThreadSafe;
/**
* author:Mark
* date:2018/7/30 23:31
* 懒汉模式 --》双重同步锁单例
* 单例实例在第一次使用时被创建
*/
@NotThreadSafe
public class SingletonTest4 {
//私有的构造函数
private SingletonTest4() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
// JVM和cpu优化,发生了指令重排
// 1、memory = allocate() 分配对象的内存空间
// 3、instance = memory 设置instance指向刚分配的内存
// 2、ctorInstance() 初始化对象
//单例对象
private static SingletonTest4 instance = null;
//静态工厂方法
//线程不安全,如果发生了以上的指令重排,当A线程执行3的时候,这时候B刚好到外层判断的位置,判断已经有实例,
// (但是实际上没有实例化对象来),B就返回实例,那么后面就会发生空指针异常
//那么解决这个问题就是用 volatile【请看下面的demo3】
public static SingletonTest4 getInstance(){
if (instance == null) {//B //双重检测机制
synchronized (SingletonTest4.class) {//这个时候将synchronized加到类上
if (instance == null) {
instance = new SingletonTest4();//A-->3
}
}
}
return instance;
}
}
结果:可能会发生指令重排序使得线程不安全。
demo3:优化指令重排序的double check:使用volatile
package com.mark.example.singleton;
import com.mark.annotations.ThreadSafe;
/**
* author:Mark
* date:2018/7/30 23:31
* 懒汉模式 --》双重同步锁单例
* 单例实例在第一次使用时被创建
*/
@ThreadSafe
public class SingletonTest5 {
//私有的构造函数
private SingletonTest5() {
}
// 1、memory = allocate() 分配对象的内存空间
// 2、ctorInstance() 初始化对象
// 3、instance = memory 设置instance指向刚分配的内存
private volatile static SingletonTest5 instance = null;
public static SingletonTest5 getInstance(){
if (instance == null) {//双重检测机制
synchronized (SingletonTest5.class) {//加上了同步锁
if (instance == null) {
instance = new SingletonTest5();
}
}
}
return instance;
}
}
结果:线程安全
使用枚举实现单例
package com.mark.example.singleton;
import com.mark.annotations.Recommend;
import com.mark.annotations.ThreadSafe;
/**
* author:Mark
* date:2018/7/30 23:52
* 使用枚举实现单例【最安全】
*/
@ThreadSafe
@Recommend
public class SingletonTest7 {
//私有的构造函数
private SingletonTest7(){
}
public static SingletonTest7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
// CC;
private SingletonTest7 singletonTest7;
// JVM保证这个方法绝对只调用一次
Singleton(){
System.out.println("枚举的构造方法");
singletonTest7 = new SingletonTest7();
}
public SingletonTest7 getInstance(){
return singletonTest7;
}
}
public static void main(String[] args) {
System.out.println(getInstance().hashCode());
System.out.println(getInstance().hashCode());
}
}