前言
前排提示:这是一个学习笔记,参照马士兵老师和韩顺平老师设计模式的视频学习的笔记。记录下来纯为巩固和方便日后的复习!
参考:
韩顺平老师:https://www.bilibili.com/video/BV1G4411c7N4?from=search&seid=15531256878937056259
马士兵老师:https://www.bilibili.com/video/BV1tK411W7xx?from=search&seid=15531256878937056259
设计模式(序)——设计模式初识和原则
设计模式(一)——单例模式
设计模式(二)——策略模式
设计模式(三)——简单工厂和抽象工厂
单例模式
单例模式介绍:
单例模式是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
单例模式的八种方式:
- 饿汉式(静态常量)
- 饿汉式(静态代码块)
- 懒汉式(线程不安全)
- 懒汉式(线程安全,同步方法)
- 懒汉式(线程安全,同步代码块)
- 双重检查
- 静态内部类
- 枚举
单例模式的实现
饿汉式
饿汉式为在类装载的时候就完成实例化,饿汉式有两种实现方式,一种为静态常量、另一种为静态代码块。饿汉式是线程安全的!
饿汉式(静态常量)
public class Mgr01 {
//构造器私有化
private Mgr01(){}
//类的内部创建对象
private static final Mgr01 instance = new Mgr01();
//向外暴露一个静态的公共方法
private static Mgr01 getInstance(){
return instance;
}
public static void main(String[] args) {
Mgr01 mgr01 = Mgr01.getInstance();
Mgr01 mgr02 = Mgr01.getInstance();
System.out.println(mgr01==mgr02);
System.out.println(mgr01.hashCode());
System.out.println(mgr02.hashCode());
}
}
饿汉式(静态代码块)
public class Mgr02 {
private Mgr02(){}
private static Mgr02 instance;
static {
instance = new Mgr02();
}
private static Mgr02 getInstance(){
return instance;
}
public static void main(String[] args) {
Mgr02 instance = Mgr02.getInstance();
Mgr02 instance1 = Mgr02.getInstance();
System.out.println(instance == instance1);
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
总结
优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题
缺点:在类装载的时候就完成实例化,没有达到lazy loading的效果。如果从始至终从未使用过这个实例,则会造成内存浪费
如何避免线程安全问题:这种方式基于classloder机制避免了多线程的同步问题,不过,instance在类装载时就实例化,在单例模式中大多数都是调用getInstance方法,
但是导致类装载的原因有很多种,因此不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果
懒汉式
懒汉式为在需要的使用该类的时候在实例化,懒汉式有三种实现方式:直接实现(线程不安全)、同步方法(线程安全)、同步代码块(线程不安全)
直接实现
public class Mgr03 {
private Mgr03(){}
private static Mgr03 instance;
private static Mgr03 getInstance(){
if (instance==null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
instance = new Mgr03();
}
return instance;
}
}
单线程测试:
public class Mgr03 {
public static void main(String[] args) {
/**
* 单线程测试
*/
Mgr03 instance = Mgr03.getInstance();
Mgr03 instance1 = Mgr03.getInstance();
System.out.println(instance==instance1);
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
}
}
测试结果:
多线程测试:
public class Mgr03 {
public static void main(String[] args) {
/**
* 多线程测试
*/
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr03.getInstance().hashCode());
}).start();
}
}
}
结论:由此可见,懒汉式在多线程的情况下是不安全的。如果想要使这种模式在多线程模式下保证线程安全,就需要使用同步方法或同步代码块的方式改造实现方法。
同步方法
思想:在实例化的方法上加synchronized来验证一下。这样做保证了线程的安全,但是执行的效率降低了。
public class Mgr04 {
private Mgr04() {
}
private static Mgr04 instance;
private synchronized static Mgr04 getInstance() {
if (instance == null) {
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
instance = new Mgr04();
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
}
测试结果:
结论:线程安全!
同步代码块
思想:减小同步代码块,同步方法是在整个方法上进行加锁,而同步代码块是在已经知道没有实例化后再加锁判断。
public class Mgr05 {
private Mgr05() {
}
private static Mgr05 instance;
private static Mgr05 getInstance() {
if (instance == null) {
synchronized (Mgr05.class){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
instance = new Mgr05();
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
测试结果:
结论:线程不安全!
总结
懒汉式的出现是为了避免造成内存浪费而出现的,其中的三种实现方式中只有同步方法的方式是能够保证在多线程的情况下线程安全,但是这种方式降低了执行的效率。由此引出了双重检查的方式。
双重检查
实现方式:
public class Mgr061 {
private Mgr061() {
}
private static volatile Mgr061 instance;
private static Mgr061 getInstance() {
if (instance == null) {
synchronized (Mgr061.class) {
if (instance == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Mgr061();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
System.out.println(Mgr061.getInstance().hashCode());
}).start();
}
}
}
测试结果:
结论:线程安全!\
静态内部类
在类中构建一个静态内部类方法用于构建instance,当需要instance时再去实例化类,这样即达到了懒加载的目的也保证了线程安全。是目前最完美的一种方式。
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder{
private final static Mgr07 instance = new Mgr07();
}
private static Mgr07 getInstance(){
return Mgr07Holder.instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
测试结果:
枚举模式
借助jdk1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,还能防止反序列化(因为枚举没有构造方法)重新创建新的对象。这种也是趋近完美的写法之一。
public enum Mgr08 {
instance;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Mgr08.instance.hashCode())).start();
}
}
}
注意事项和细节:
- 单例模式保证了系统内存中该类只存在一个对象,节省了系统资源,对于一些需要频繁创建销毁的对象使用单例模式可以提高系统性能
- 当想实例化一个单例类的时候,必须要记住使用相应的获取对象的方法,而不是使用new
- 单例模式使用的场景:需要频繁的进行创建和销毁的对象、创建对象时耗时过多或耗费资源过多,但又经常用到的对象、工具类对象、频繁访问数据库或文件的对象