一,单例模式分为两种,饿汉式和懒汉式
1.饿汉式,是一旦加载类,就会创建单例对象,把对象加载到内存
/**
* 特点:空间换时间
* 优点:线程安全,加载速度快,单例直接在内存中停留
* 缺点:浪费内存资源
* 适用场景:一定要使用到的的全局单例对象,才是用这种
*/
2.懒汉式,使用的时候才去创建实例对象,并且只能创建一个
/**
* 特点:时间换空间
* 优点:不会浪费内存空间,使用的时候才加载进内存
* 缺点:加载速度稍慢,普通懒汉式可能线程不安全
*
* 注意:可以通过 双检,内部类 实现线程安全的懒汉式.
* 懒汉模式的三种线程安全写法:双重检查锁、静态内部类、枚举单例,此三种皆可解决线程不安全问题
* 疑问 枚举如何实现? 目前咱们先实现 双重检查锁、静态内部类.
*/
二,饿汉式实践案例
//2.1 饿汉式
//具体详情见类 Hungry
/**
* 饿汉式
*
*/
class Hungry{
//所谓饿汉就是,一旦加载类就自己生成实例.不需要外界来调用就已经存在了.
//是属于空间换时间的操作,适用于需要快速访问的对象. 对时间敏感,对空间不敏感.
//需要响应快,占用内存可以接受的情况下使用.
//单例模式 之所以是单例,就是只能自己获取实例对象,不能在其他地方创建.
//为了不让在其他地方创建实例,需要对构造器进行私有
private Hungry(){
System.out.println("初始化:Hungry");
}
//需要在类一旦加载的时候就创建对象,使用 static修饰,同样私有
private static Hungry hungry =new Hungry();
//另外需要一个 给外界获取 对象的方法,不然就没法使用了
public static Hungry getHungry(){
System.out.println("获取实例:hungry");
return hungry;
}
}
//听说 饿汉式安全,何以见得,实践出真知
@Test
public void testHungryThread(){ //注意 使用 @Test 修饰符 不能使用 private
Thread t1=new Thread(()->{
System.out.println("t1:"+ Hungry.getHungry().hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+Hungry.getHungry().hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+Hungry.getHungry().hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+Hungry.getHungry().hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+Hungry.getHungry().hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
初始化:Hungry
获取实例:hungry
t1:1965862841
获取实例:hungry
t2:1965862841
获取实例:hungry
t3:1965862841
获取实例:hungry
t4:1965862841
获取实例:hungry
t5:1965862841
//结果发现 出现Hungry.getHungry().hashCode()始终是相同的
//说明 饿汉式是线程安全
三,懒汉式实践案例
3.1 普通懒汉式
//具体详情见类:Lazy
/**** 懒汉式
* * 1.私有构造方法
* * 2.私有静态引用
* * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
*
* */
class Lazy {
private Lazy(){
System.out.println("初始化:Lazy");
};
private static Lazy lazy;
public static Lazy getLazy() {
/* try{
Thread.sleep( 10+System.currentTimeMillis()%10);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
if(lazy==null){
lazy = new Lazy();
}
System.out.println("获取实例:lazy");
return lazy;
}
}
//听说 普通的懒汉式不安全,何以见得,实践出真知
/*
* @description: 测试普通懒汉式的线程安全方法
* @author: ksana404
* @date: 2022/6/27 14:41
* @param: []
* @return: void
**/
@Test
public void testLazyThread(){
Thread t1=new Thread(()->{
System.out.println("t1:"+ Lazy.getLazy().hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+Lazy.getLazy().hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+Lazy.getLazy().hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+Lazy.getLazy().hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+Lazy.getLazy().hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
打印结果:
初始化:Lazy
获取实例:lazy
t2:435739066
初始化:Lazy
获取实例:lazy
t1:1965862841
初始化:Lazy
获取实例:lazy
t3:32376888
初始化:Lazy
获取实例:lazy
t4:1028166201
初始化:Lazy
获取实例:lazy
t5:178478969
//结果发现 Lazy.getLazy().hashCode() 出现了不同实例
//说明 普通懒汉式 非线程安全
四,懒汉式安全写法
//懒汉模式的三种线程安全写法:双重检查锁、静态内部类、枚举单例
4.1 懒汉式线程安全写法1 双重检查锁(double check lock)
使用syn,代码见:DoubleSynLazy
/**
* * 1.私有构造方法
*
* * 2.私有静态引用
* 2.1使用volatile 修饰
*
* * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
* 3.1使用同步代码块去判断是否需要new,在同步块里面和外面均需要判断一次.
*/
class DoubleSynLazy{
//1.私有构造方法
private DoubleSynLazy(){};
//2.私有静态引用 2.1使用 volatile 修饰
//volatile目的 使得不同线程都能获取 instance 最新的数据.
//线程共享数据
private volatile static DoubleSynLazy instance;
public static DoubleSynLazy getInstance(){
//1.第1次检查
if(null==instance){
//2.使用同步代码块 同步这个判断
// synchronized(instance) 可能会有空指针异常
synchronized (DoubleSynLazy.class){
//3.第2次检查
if(null==instance){
instance = new DoubleSynLazy();
}
}
}
return instance;
}
}
/* DoubleSynLazy 测试方法
* @description:
* @author: ksana404
* @date: 2022/6/27 18:46
* @param: []
* @return: void
**/
@Test
public void testDoubleSynLazyThread(){
Thread t1=new Thread(()->{
System.out.println("t1:"+ DoubleSynLazy.getInstance().hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+DoubleSynLazy.getInstance().hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+DoubleSynLazy.getInstance().hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+DoubleSynLazy.getInstance().hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+DoubleSynLazy.getInstance().hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
/**答应如下
t1:1955884916
t2:1955884916
t3:1955884916
t5:1955884916
t4:1955884916
* 说明是线程 安全的
*/
4.1.2 懒汉式线程安全写法1.2 使用 lock 替代 syn,代码见:DoubleLockLazy
/**
* * 1.私有构造方法
*
* * 2.私有静态引用
* 2.1使用volatile 修饰
* 2.2声明 lock
*
* * 3.公开静态获取本类实例对象的方法(有则直接获取,没有则新建new)
* 3.1使用同步代码块去判断是否需要new,在同步块里面和外面均需要判断一次.
*/
class DoubleLockLazy{
private DoubleLockLazy(){};
private static volatile DoubleLockLazy instance;
private static Lock lock = new ReentrantLock();
public static DoubleLockLazy getInstance(){
//1.第1次检查
if(null==instance){
//2.使用同步代码块 同步这个判断
lock.lock();
//3.第2次检查
if(null==instance){
instance = new DoubleLockLazy();
}
lock.unlock();
}
return instance;
}
}
/* DoubleLockLazy 测试方法
* @description:
* @author: ksana404
* @date: 2022/6/27 18:46
* @param: []
* @return: void
**/
@Test
public void testDoubleLockLazyThread(){
Thread t1=new Thread(()->{
System.out.println("t1:"+ DoubleLockLazy.getInstance().hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+DoubleLockLazy.getInstance().hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+DoubleLockLazy.getInstance().hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+DoubleLockLazy.getInstance().hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+DoubleLockLazy.getInstance().hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
/**答应如下
- t5:2048417961
- t4:2048417961
- t3:2048417961
- t2:2048417961
- t1:2048417961
- 说明是线程 安全的
*/
4.2 懒汉式线程安全写法2 使用静态内部类 代码见:InnerLazy
```java
```java
/**
* 静态内部类 实现线程安全懒汉式
*
*/
class InnerLazy {
// 私有内部类,按需加载,用时加载,也就是延迟加载
private static class Holder{
private static InnerLazy innerLazy =new InnerLazy();
}
//私有构造
private InnerLazy(){}
//公开 静态 获取方法(通过内部类 获取)
public static InnerLazy getInstance(){
return InnerLazy.Holder.innerLazy;
}
//类加载的方式是按需加载,且只加载一次。
/*
我们已经在上面提到,类加载的方式是按需加载,且只加载一次。因此,在上述单例类被加载时,
就会实例化一个对象并交给自己的引用,供系统使用。换句话说,在线程访问单例对象之前就已经创建好了。
再加上,由于一个类在整个生命周期中只会被加载一次,因此该单例类只会创建一个实例,
也就是说,线程每次都只能也必定只可以拿到这个唯一的对象。
因此就说,饿汉式单例天生就是线程安全的。
* 如上述代码所示,我们可以使用内部类实现线程安全的懒汉式单例,这种方式也是一种效率比较高的做法。
* 至于其为什么是线程安全的,其与问题 “为什么说饿汉式单例天生就是线程安全的?” 相类似
* */
}
/* InnerLazy 测试方法
* @description:
* @author: ksana404
* @date: 2022/6/27 18:46
* @param: []
* @return: void
**/
@Test
public void testInnerLazyThread(){
Thread t1=new Thread(()->{
System.out.println("t1:"+ InnerLazy.getInstance().hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+InnerLazy.getInstance().hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+InnerLazy.getInstance().hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+InnerLazy.getInstance().hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+InnerLazy.getInstance().hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
/**答应如下
t5:223491742
t2:223491742
t3:223491742
t4:223491742
t1:223491742
* 说明是线程 安全的
*/
4.3 懒汉式线程安全写法3 使用枚举枚举单例 代码见:EnumLazy
/**
* 枚举单例
**/
enum EnumLazy{
INSTANCE;
public void businessMethod() {
System.out.println("我是一个枚举单例!");
}
}
//EnumLazy
/* EnumLazy 测试方法
* @description:
* @author: ksana404
* @date: 2022/6/27 18:46
* @param: []
* @return: void
**/
@Test
public void testEnumLazyThread(){
Thread t1=new Thread(()->{
System.out.println("t1:"+ EnumLazy.INSTANCE.hashCode());
});
Thread t2=new Thread(()->{
System.out.println("t2:"+EnumLazy.INSTANCE.hashCode());
});
Thread t3=new Thread(()->{
System.out.println("t3:"+EnumLazy.INSTANCE.hashCode());
});
Thread t4=new Thread(()->{
System.out.println("t4:"+EnumLazy.INSTANCE.hashCode());
});
Thread t5=new Thread(()->{
System.out.println("t5:"+EnumLazy.INSTANCE.hashCode());
});
t1.start();
t2.start();
t3.start();
t4.start();
t5.start();
}
/**答应如下
t2:111745349
t1:111745349
t3:111745349
t4:111745349
t5:111745349
* 说明是线程 安全的
*/