单例设计模式及线程安全
这边讲解一下单例设计模式。
所谓的单例设计模式顾名思义只能生成一个实例对象。
- 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级的对象而言,是非常可观的一笔系统开销。
- 由于new操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻垃圾回收压力,缩短垃圾回收停顿时间。
- 单例模式的主要结构有私有化的构造函数,提供静态方法获得已经生成的对象
饿汉式单例
饿汉式名字的来源是因为在类被加载的时候就生成对象,该实现方式是线程安全的。
/**
* 单例设计模式(饿汉式)
* @author ashinlee
* @date 2018/3/20
* @time 20:45
*/
public class Singleton {
private Singleton(){
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
在此类中定义了一个静态的对象,该对象在类被加载的时候就会生成,提供了getInstance方法获得生成的对像,这也是最简单的实现方式
懒汉式单例
懒汉式相对比较复杂,因为对象是在调用方法的时候才创建的,所以涉及到对于共享变量的线程安全问题。
package fun.javaweb.blog.practice;
/**
* 单例设计模式(懒汉)
* @author 李幸
* @date 2018/3/27
* @time 21:42
*/
public class MyLazySingleton {
private static MyLazySingleton myLazySingleton = null;
private MyLazySingleton(){
}
public static MyLazySingleton getInstance(){
if(myLazySingleton == null){
myLazySingleton = new MyLazySingleton();
}
return myLazySingleton;
}
}
很明显,生成对象是在调用方法时才生成,在多线程的环境下,如果一个线程进入判断myLazySingleton等于null然后该线程被挂起,那么下一个线程也判断myLazySingleton等于null那么这两个线程会生成不同的对象,这就好比你进门的同时没来得及关门,别人也可以进来,这样房间里就会有两个人了。
线程安全的懒汉式单例
多线程实现方式这边不多提。
package fun.javaweb.blog.practice;
/**
* 单例设计模式(懒汉)
* @author 李幸
* @date 2018/3/20
* @time 20:47
*/
public class LaszySingleton {
private LaszySingleton(){
}
private static LaszySingleton instance = null;
public static LaszySingleton getInstance(){
// synchronized (LaszySingleton.class){
if(instance == null){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LaszySingleton();
}
// }
return instance;
}
public static void main(String[] args) {
TestSingleTon testSingleTon = new TestSingleTon();
Thread thread1 = new Thread(testSingleTon);
Thread thread2 = new Thread(testSingleTon);
thread1.start();
thread2.start();
}
}
class TestSingleTon implements Runnable{
@Override
public void run() {
LaszySingleton laszySingleton = LaszySingleton.getInstance();
System.out.println(laszySingleton);
}
}
以上代码为线程安全的单例模式实现,运行以上代码可以看到在同步代码快被注释的情况下,启动两个线程会打印出两个不同的对象地址,说明生成了不同的对象,sleep用于方法出错几率。
而加上同步代码块后相当于一个线程进入同步代码块时带了把锁,只有出了这个代码快锁才会释放,下一个线程才会进入。也就相当于进入房间后马上关上门,这样别人就进不来了。
效率问题
被synchronized所修饰的代码块为同步代码块,那么进入这个代码块的线程都必须等上一个进入的释放锁后才能进入,这样导致效率被降低了,就引入了下面的double check 双重检查
double check
public static LaszySingleton getInstance(){
if(instance == null){
synchronized (LaszySingleton.class){
if(instance == null){
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new LaszySingleton();
}
}
}
return instance;
}
只需修改获得对象的方法在synchronized代码块的外层再添加一层if判断,这样如果有线程已经进入了同步代块并生成了对象,那么之后的线程都只需要判断外层的if即可,不需要再进入同步代码块判断,这样效率就会提升。
想一起交流或者有问题的朋友可以关注我的公众号,里面有群聊连接可以一起交流遇到的问题,如果失效可以后台回复我,每天会同步更新
博客连接 : AshinLee’s blog
公众号: