目录
单例模式
单例模式是java中老生常谈的设计模式,在工作中相信大家也没少接触,就小编个人而言,单例模式的主要应用场景如下:
适用于项目中频繁获取对象的场景,例如:获取缓存对象、获取一些工具类对象等等,由于这些对象使用频率较高,所以在获取对象时,我们使用单例模式指定获取一个对象即可。
下面小编带大家再次温习一下单例模式的写法,这里将介绍单例模式的五种写法,
饿汉模式
代码结构如下:
- 私有的静态的 最终的 对象 直接new
- 私有的 无参构造方法
- 共有的 静态的 实例方法
饿汉模式是单例模式中常用的写法之一,主要的特点是在定义对象的时候就直接new一个对象,详细代码如下:
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
* 单例模式 饿汉式
* 上来就new对象
**/
public class SignletonHungry {
//1. 私有的静态的最终的对象
private static final SignletonHungry singl=new SignletonHungry();
//2. 私有的无参构造函数
private SignletonHungry(){
}
//3. 公共的静态的实例方法
public static SignletonHungry getInstance(){
return singl;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonHungry.getInstance().hashCode());
}).start();
}
}
}
在类的下方我们定义了一个测试方法,用来模拟验证在多线程并发访问时,每次通过单例类获取的对象的hashcode方法是否一致,如果一致则代表线程安全,执行结果如下:
可以看到执行结果是一致的,这种饿汉写法一般在实际项目中应用的也是最多的,
优点:这种写法比较简单,就是在类装载的时候就完成实例化,避免了线程同步问题。
缺点:但是因为在指定对象时就进行初始化,在类比较大的时候,也会造成一定的资源消耗。
懒汉模式
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法
为了避免上述所说的饿汉式的缺点,延伸出了懒汉式的单例写法,在定义对象时,并不直接进行初始化,在实际的实例化方法里面,才进行初始化操作,这样就节省了一定的资源,具体实现代码如下:
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
*
* 单例模式 懒汉式
* 调用实例方法时才new对象
* 节省空间 缺点是线程不安全
**/
public class SignletonFull {
//1. 私有的静态的对象 先不new 默认为null值
private static SignletonFull signletonFull;
//2. 私有的无参构造器
private SignletonFull(){}
//3. 公共的静态的方法
public static SignletonFull getInstance() throws InterruptedException {
if(signletonFull==null){
Thread.sleep(1000);
signletonFull=new SignletonFull();
}
return signletonFull;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
try {
System.out.println("获取的hashCode是: "+SignletonFull.getInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
同样,在这个类的下方我们也定义了一个测试方法,用来模拟验证在多线程并发访问时,每次通过单例类获取的对象的hashcode方法是否一致,如果一致则代表线程安全,执行结果如下:
我们可以看到,懒汉模式在多线程并发获取单例类时,存在现场安全的问题,那么既然存在线程安全问题,我们怎么去改善这个问题呢?请看线程锁模式。
线程锁模式
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法,在判断对象是否为空时加上synchronize修饰
通过线程锁的写法可以解决懒汉模式下存在的线程安全问题,具体实现代码如下:
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
*
* 单例模式之 加锁
* 线程安全 缺点是效率低 受synchronized锁升级的影响
**/
public class SignletonThread {
//1. 私有的静态的对象
private static SignletonThread signletonThread;
//2. 私有的构造方法
private SignletonThread(){}
//3. 公共的静态的实例方法 在if里面加上锁synchronized
public static SignletonThread getInstance(){
if (signletonThread==null){
synchronized (SignletonThread.class){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
signletonThread=new SignletonThread();
}
}
return signletonThread;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonThread.getInstance().hashCode());
}).start();
}
}
}
同样,在这个类的下方我们也定义了一个测试方法,用来模拟验证在多线程并发访问时,每次通过单例类获取的对象的hashcode方法是否一致,如果一致则代表线程安全,执行结果如下:
我们可以看到,执行结果并不如人意,为什么呢,这是因为在执行到synchronized代码快的时候,有线程已经获取到了对象,从而导致获取的对象不一致的情况,那么如何解决这个问呢?
双重判断模式
代码结构如下:
- 私有的静态的对象 为空 不new
- 私有的无参构造方法
- 共有的静态的实例方法,在判断对象是否为空时加上synchronize修饰
- 在判断里面再次判断是否为空
package designmodel.signelton;
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
*
* 单例写法
* 双重判断式
**/
public class SignletonThreadTwo {
//1. 私有的静态的对象
private static SignletonThreadTwo signletonThreadTwo;
//2. 私有的构造方法
private SignletonThreadTwo(){}
//3. 公共的静态的实例方法 在if里面加上锁synchronized 在锁块中继续判断是否为空
public static SignletonThreadTwo getInstance(){
if (signletonThreadTwo==null){
synchronized (SignletonThreadTwo.class){
if(signletonThreadTwo==null){
signletonThreadTwo=new SignletonThreadTwo();
}
}
}
return signletonThreadTwo;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonThreadTwo.getInstance().hashCode());
}).start();
}
}
}
同样,在这个类的下方我们也定义了一个测试方法,用来模拟验证在多线程并发访问时,每次通过单例类获取的对象的hashcode方法是否一致,如果一致则代表线程安全,执行结果如下:
我们看执行结果,虽然执行结果是我们想要的,但是由于引入了synchronized代码块,所以也引入了轻量级锁、重量级锁的概念,虽然保障了线程安全,但是却失去了性能加成并且容易导致死锁,所以,有没有什么办法,既能线程安全,又能保障效率呢?
静态内部类模式
代码结构如下:
- 私有的无参构造器
- 私有的静态的内部类
- 在内部类中定义私有的最终的静态的对象,new一个
- 定义公共的实例方法,返回内部类.对象
package designmodel.signelton;
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
* 单例模式
* 通过静态内部类实现懒加载与线程安全
* 利用JVN特性实现 JVM在加载类和内部类的时候 只会在运行的时候加载一次 从而保证线程安全和懒加载
**/
public class SignletonStaticClass {
//1. 私有的无参构造器
private SignletonStaticClass(){}
//2. 私有的静态的内部类
private static class SignletonStatic{
//3. 在私有的内部类中定义私有的 最终的 静态的对象
private final static SignletonStaticClass signletonStaticClass=new SignletonStaticClass();
}
//4. 公共的静态的实例方法
public static SignletonStaticClass getInstance(){
return SignletonStatic.signletonStaticClass;
}
//测试方法
public static void main(String[] args) {
//利用for循环 模拟多线程环境调用
for (int i = 0; i < 100; i++) {
new Thread(()->{
//看每次获取对象的hashcode是否一致 判断是否获取了同一个对象
System.out.println("获取的hashCode是: "+SignletonStaticClass.getInstance().hashCode());
}).start();
}
}
}
在执行验证代码时,我们可以看到,通过此写法保障了线程安全和效率,这个写法的原理类似于饿汉式,利用JVN特性实现 JVM在加载类和内部类的时候 只会在运行的时候加载一次 从而保证线程安全和懒加载。
策略模式
代码结构如下:
- 定义策略接口,定义通用方法。
- 定义N个实现类,实现接口,重新方法。
- 定义Context上下文类,利用多态进行封装。
- 使用时通过Context上下文类进行调用,在构造函数中传入实现类的对象。
策略模式:策略模式是一种行为型模式,它将对象和行为分开,将行为定义为 一个行为接口 和 具体行为的实现。策略模式最大的特点是行为的变化,行为之间可以相互替换。每个if判断都可以理解为就是一个策略,可以使得算法可独立于使用它的用户而变化。
使用场景:
1. 假设现在某超市有三个等级的会员,普通会员,VIP1,VIP2。
2. 在结账的时候,三个登记的会员购买了同一种商品,普通会员不打折,VIP1打9折,VIP2打8折
定义策略接口:
package designmodel.celve;
/**
* @Author luocong
* @Description //TODO
* @Date 12:20 2022/11/8
* @Param
* @return
* 定义策略接口
* 案例场景:
* 有三种会员 购买相同数量和单价的产品时 需要打不同的折扣
**/
public interface StrategyInt {
//price价格 n数量
public double getPrice(double price,int n);
}
定义普通会员,实现策略接口:
package designmodel.celve;
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
* 实现类1 实现接口中定义的计算价格方法
* 普通会员类 不打折
**/
public class NormalPerson implements StrategyInt {
//普通会员不打折
@Override
public double getPrice(double price, int n)
{
System.out.println("普通会员不打折.....");
return (price*n);
}
}
定义vip1会员,实现策略接口:
package designmodel.celve;
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
*实现类2 实现接口中定义的计算价格方法
*VIP1会员 打9折
**/
public class Vip1Person implements StrategyInt{
//VIP1客户 打9折
@Override
public double getPrice(double price, int n) {
System.out.println("VIP1打9折.....");
return (price*n)*0.9;
}
}
定义vip2会员,实现策略接口:
package designmodel.celve;
/**
* @{NAME}
* @Description TODO
* @Author luocong
* @Date
* @Version 1.0
* 实现类2 实现接口中定义的计算价格方法
* VIP2会员类 打8折
**/
public class Vip2Person implements StrategyInt {
@Override
public double getPrice(double price, int n) {
System.out.println("VIP2打8折...