常用的设计模式-单例模式

这是我的学习笔记,纯手打,本想写在纸质笔记本上的,但时间一久就容易丢,所以还是记在网络上吧:

意图:保证每个类,只有一个实例,并且提供一个全局的访问点

场景:需要严格的控制全局变量时,如线程对象、数据库连接池对象,不需要多个实例的对象,如工具类。

双重检测加锁的单例实现:

class LazySingleton{ private volatile static LazySingleton instance; private LazySingleton(){ } public static LazySingleton getInstance(){ if (null == instance){ synchronized(LazySingleton.class){ if(null == instance){ instance = new LazySingleton(); } } } return instance; }}

这里用了synchronized,先扩展一下笔记synchronized的四种用法

>>>

一、synchronized同步锁为普通对象:

public void function(){ synchronized (object) { //doSomething… }}

当某个线程要访问如上代码块中的内容时,若该线程获得object对象的锁,那么就获得了执行权,否则此线程被阻塞,直到其他线程释放了object对象的锁。

举个例子:

public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(new Object()); // new了5个新对象到数组种 } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ Object syncObj; public Sync(Object syncObj) { this.syncObj = syncObj; } public void run(){ synchronized (syncObj) { System.out.println(Thread.currentThread().getName()+"运行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"结束…"); } }}

运行结果:

Thread-0运行中…Thread-1运行中…Thread-2运行中…Thread-3运行中…Thread-4运行中…Thread-1结束…Thread-0结束…Thread-3结束…Thread-4结束…Thread-2结束…

看到这5个线程并没有按顺序执行,他们之间不是同步的。这是因为这5个线程的syncObj并不是指向同一个对象,他们之间不存在同步锁的竞争,所以是非同步的。将程序改为:

public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; Object object = new Object(); //改了这里 for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(object); //数组里的对象都是同一个,前面是5个不一样的 } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ Object syncObj; public Sync(Object syncObj) { this.syncObj = syncObj; } public void run(){ synchronized (syncObj) { System.out.println(Thread.currentThread().getName()+"运行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"结束…"); } }}

运行结果:

Thread-0运行中…Thread-0结束…Thread-4运行中…Thread-4结束…Thread-2运行中…Thread-2结束…Thread-3运行中…Thread-3结束…Thread-1运行中…Thread-1结束…

这5个线程都达到了效果,但是5个线程的执行顺序并不是固定的,这是编译是重排序造成的。

所以,若想要多个线程同步,则这些线程必须竞争同一个同步锁。

二、synchronized同步锁为类

类其实也是一个对象,可以去按照普通对象的方式去理解。不同之处在于,普通对象作用于某个实例,而类对象作用于整个类。

上面的例子我改改:

public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(); } for(Sync sync : syncs){ sync.start(); } }}class Sync extends Thread{ public void run(){ synchronized (SyncTest.class) { // 改了这里 System.out.println(Thread.currentThread().getName()+"运行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"结束…"); } }}

运行结果:

Thread-0运行中…Thread-0结束…Thread-3运行中…Thread-3结束…Thread-1运行中…Thread-1结束…Thread-4运行中…Thread-4结束…Thread-2运行中…Thread-2结束…

这些线程同样实现了同步,因为他们的同步锁是同一个对象SyncTest类对象。

需要注意的是,类对象锁和普通对象锁是不同的两个锁(即使这个对象是这个类的实例),他们之间互不干扰。

public class SyncTest { public static void main(String args[]){ Sync[] syncs = new Sync[5]; for (int i = 0; i < syncs.length; i++) { syncs[i] = new Sync(); } Sync1 sync1 = new Sync1(new SyncTest()); for(Sync sync : syncs){ sync.start(); //类对象锁跑5个线程 } sync1.start(); //普通对象锁跑一个线程 }}class Sync extends Thread{ public void run(){ synchronized (SyncTest.class) { //类对象锁 System.out.println(Thread.currentThread().getName()+"运行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"结束…"); } }} class Sync1 extends Thread{ SyncTest syncTest; public Sync1(SyncTest syncTest){ this.syncTest = syncTest; } public void run(){ synchronized (syncTest) { //普通对象锁 System.out.println(Thread.currentThread().getName()+"运行中…"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"结束…"); } }}

运行结果:

Thread-0运行中…Thread-5运行中…Thread-0结束…Thread-5结束…Thread-4运行中…Thread-4结束…Thread-2运行中…Thread-2结束…Thread-3运行中…Thread-3结束…Thread-1运行中…Thread-1结束…

可以看到,虽然Sync1中的对象锁是SyncTest的实例,但是Sync1与Sync的run方法中的synchronized代码块并没有实现同步,他们可以同时访问这段代码。验证了互相不干扰。

三、synchronized修饰普通方法

synchronized修饰方法在本质上和修饰代码块是一样的,他们都是通过同步锁来实现同步的。

public synchronized void syncFunction(){ //doSomething…}

synchronized修饰普通方法中的同步锁就是这个对象本身,即 this

看代码:

class Sync extends Thread{ public synchronized void syncFunction(){ doSomething(); } //syncFunction() 和 syncFunction2() 实现的同步效果是一样的。 public void syncFunction2(){ synchronized (this) { doSomething(); } } private void doSomething(){ //doSomething… }}

当类中某个方法test()被synchronized关键字所修饰时,所有不同的线程访问这个类的同一个实例的test()方法都会实现同步的效果。不同的实例之间不存在同步锁的竞争,也就是说,不同的线程访问这个类不同实例的test()方法并不会实现同步。这很容易理解,因为不同的实例同步锁不同,每个实例都有自己的”this”。

四、synchronized修饰静态方法

public static synchronized void syncFunction(){ //doSomething…}

同样的,synchronized作用于静态方法时,跟使用类对象作为静态锁的效果是一样的,此时的类对象就是静态方法所属的类。

不同的线程访问某个类不同实例的syncFunction()方法(被synchronized修饰的静态方法,如上)时,他们之间实现了同步效果。结合上面的解释,这种情况也很好理解:此时不同线程竞争同一把同步锁,这就是这个类的类对象锁。

总结

理解synchronized的关键就在于:若想要多个线程同步,则这些线程必须竞争同一个同步锁。这个同步锁,可以理解为一个对象。

>>>

扩展完毕,继续说单例模式

我们知道,创建一个对象大致分为三步:

step1 开辟空间

step2 初始化空间

step3 赋值

但是编译器、即时编译,CPU可能对字节码进行优化,对编译后的字节码进行指令的重新排序。比如将第2步和第3步置换位置、那就会出现有一个状况:

线程A赋值完毕,但未完成初始化时,线程B在走第一个IF检查时,发现instance实例不为null,直接return未初始化的对象,出现异常。

加入volatile关键字给实例变量,是为了防止JVM进行指令重排而出现的问题。

private volatile static LazySingleton instance;

上述时懒汉式实现单例,意为在需要时才去实例化。

那么对应的有饿汉式:

class HungrySingleton{ //静态属性public static String name=”JJ”;private static HungrySingleton instance = new HungrySingleton(); //构造私有化private HungrySingleton(){ } //获取实例的方法public static HungrySingleton getInstance(){ return instance; }}

只有在主动使用对应的类时,才会触发实例的初始化,如当前类是启动类即main函数所在的类,直接进行new操作,访问静态属性、访问静态方法、用反射访问、初始化一个类的子类等,都会触发实例的初始化操作。

加载的初始化阶段就完成了实例的初始化。本质上是借助了JVM类加载机制,保证实例的唯一性(初始化过程只会执行一次)和线程安全(JVM以同步的形式来完成类加载的整个过程)。

类加载过程:

1.加载二进制数据到内存中,生成对应的Class数据结构

2.连接:验证是否符合规范,准备给类的静态成员变量赋默认值 ,解析

3.初始化:给类的静态变量赋初值

JDK中的Runtime类就是使用了饿汉式。

如果通过反射来获取实例对象呢?是否和getInstance()获取的实例对象是同一个?

答案:不是

反射获取:

//通过构造函数实现Constructor declaredConstructor = HungrySingleton.class.getDeclaredConstractor();//访问控制权设置为truedeclaredConstructor.setAccessible( true );//通过反射实例化HungrySingleton hungrySingleton = declaredConstructor.new Instance();//通过饿汉式实例化HungrySingleton instance = HungrySingleton.getInstance();//会输出falseSystem.out.print(hungrySingleton == instance );

为了避免私有构造被反射暴力使用的问题,可以在私有构造方法中加多一个判断instance是否为null,如果不为null,则抛出RuntimeException异常。

部类单例模式:

class InnerClassSingleton{private static InnerClassSingleton instance = new InnerClassSingleton(); //构造方法私有化private InnerClassSingleton(){}//获取实例的方法public static InnerClassSingleton getInstance(){ //返回的还是InnerClassSingleton的实例 return SingletonHolder.instance; }//静态内部类private static class SingletonHolder{private static InnerClassSingleton instance = new InnerClassSingleton();}}

相对于饿汉式,该方式只在调用getInstance()方法时才会初始化。是饿汉模式和懒汉模式的结合。

枚举单例模式:

enmu EnmuSingleton{INSTANCE; public void print(){ System.out.println(“xxx”); }}

在JVM中生成的INSTANCE实例类型是static final类型的。可以通过calss文件反编译看到。

另外,还可以通过反序列化的方式来获取另一个单例,这两个“单例”是不相同的,这就相当于对单例模式进行了破坏。

解决办法:实现序列化接口,指定serialVersionUID的值。这样就保证了实例被序列化,反序列化是的版本都是一样的。否则会报异常。

还有些类使用到了双重检测单例:

比如:Spring中的ReactiveAdapterRegistry、tomcat中的TomcatURLStreanHandlerFactory.

OK,单例模式差不多就先学到这里了。

郑重声明:本文内容及图片均整理自互联网,不代表本站立场,版权归原作者所有,如有侵权请联系管理员(admin#wlmqw.com)删除。
(0)
用户投稿
上一篇 2022年6月27日
下一篇 2022年6月27日

相关推荐

  • 《2022好声音》再爆导师阵容,1+4+4模式,与圈内人爆料出入较大

    目前音综节目非常火,很多音综节目都受到了广大网友的追捧。但很多节目做了几季就停播了,而真正算得上老牌音综节目的目前只有浙江卫视的《中国好声音》。 《中国好声音》已经成功举办了十季,…

    2022年6月12日
  • 一次空指针的排查盛宴

    在一次上线的过程中,通过观察日志发现有java.lang.NullPointerException的异常出现: 异常堆栈 但这个空指针异常并不是我们的服务抛出的,显示的是下游服务抛…

    2022年6月29日
  • 空间中心等关于临近空间大气密度反演算法的研究获进展

    来源:【中国科学院】 中国科学院国家空间科学中心复杂航天系统电子信息技术院重点实验室先进测量技术研究室利用X射线掩星探测手段,突破了两种用于高精度临近空间大气密度反演算法,包括基于…

    2022年8月16日
  • 优雅关闭线程实践

    前言 在多线程编程中会需要动态创建线程来执行任务,在完成后释放该线程,使用Excuotrs创建的线程,提供了shutDown方法进行线程的关闭,但使用原生的Thread和Runab…

    2022年6月15日
  • 如果你的糖化血红蛋白超过7%,用这4个方法可以降下来

    如果你的糖化血红蛋白水平不达标,已经提示之前2-3个月血糖控制不佳,延误时间越长,并发症风险越大。 本文推荐4个可以降低糖化血红蛋白的方法。 1.通过饮食控制餐后血糖 防止摄入碳水…

    2022年8月16日
  • FTX宣布推出新IEO:IndiGG,IndiGG将导入P2E模式-

    FTX 宣布推出新 IEO 的预售,以下是本次 IEO IndiGG 的规则整理介绍: 据FTX公告,本次IEO为IndiGG,是去中心化游戏公会Yield Guild Games…

    2022年6月30日
  • 分享16种上班族兼职赚外快的方法(上班族兼职赚外快的方法有哪些)

    想要兼职创业或者是在家创业的人,选择网上赚钱的方法是最好的,那么,如今网上赚钱的方法有哪些呢?下面就让我们一起来了解一下吧。 网上赚钱的方法1、网上写博客赚钱 这个应该说是很多人都…

    2022年10月22日
  • 韩安冉离婚不到十天,又传出好事将近

    韩安冉离婚不到十天,又传出好事将近 昨天有一位画画博主爆料,称她半夜刷短视频的时候,被韩安冉找上,并添加了联系方式,韩安冉找她的目的是为了让她画一个一家三口的卡通画,并做一对情侣头…

    2022年8月26日
  • 沙雕文案短句子

    1.我的摆烂一生:上学《我不念了》,上班《我不干了》,老了《我不活了》。 2.我做决定:反复纠结、草率决定、迅速后悔。 3.我的优点:勇于认错。我的缺点:坚决不改。 4.这世界上唯…

    2022年9月8日
  • 腾讯ROG游戏手机6Pro评测:或许是第一款榨干骁龙8+的产品

    对于游戏玩家来说,ROG这个名字一定不会陌生。作为目前国内的顶级高端硬件设备品牌之一,ROG的产品在配置、设计以及做工方面都可圈可点,赢得了不少消费者的喜爱。 腾讯ROG游戏手机6…

    2022年7月7日

联系我们

联系邮箱:admin#wlmqw.com
工作时间:周一至周五,10:30-18:30,节假日休息