java单例模式升级版

常见的单例模式有5五种,各有优缺点,至于有哪5种呢?如下:

1:恶汉式(线程安全,调用效率高,但是不能延迟加载)

2:懒汉式(线程安全,但是调用效率不高,但是可以延迟加载)

3:双重检测锁式(线程安全,但是效率很低)

4:静态内部类式(线程安全 调用效率高,可以延迟加载)

5:枚举单例(线程安全 调用效率高,不能延迟加载)

现在写代码分别对上面几种方式做一个简单的说明:

恶汉式:

package cn.pattern.signinstance;
/**
 * 恶汉式
 * @author admin
 */
public class SingleDemo1 {
private static SingleDemo1 INSTANCE = new SingleDemo1();
private SingleDemo1(){}
public static SingleDemo1 getInstance(){
return INSTANCE;
}
}

这就是恶汉式的代码,简单,但是有个问题就是SingleDemo1 类在被类加载器加载到内存中,但是还没开始用getInstance方法式,对象先被创建了,这样就带来一个问题就是getInstance方法可能在整个项目中就没一次被调用,这样就带来了浪费资源(其实就是内存),这是恶汉式的一个缺点,优点就是它很早就被类装载器加载到了内存中,所以不管你在外部如果调用它都不会造成线程安全问题,因为它预先已经创建了改对象,所以不管在外部怎么调用都是之前创建的对象

懒汉式:

所谓懒汉式就是什么时候用到对象才去创建对象,这样就避免了资源的浪费,这是懒汉式相对恶汉式的一个优点,代码如下:

package cn.pattern.signinstance;
/**
 * 懒汉式
 */
public class SingleDemo2 {
private static SingleDemo2 INSTANCE =null;
private SingleDemo2(){}
public static SingleDemo2 getInstance(){
if(INSTANCE==null){
INSTANCE = new SingleDemo2();
}
return INSTANCE;
}
}

但是这个会有个问题就是多个线程去调用getInstance()方法会造成对象不唯一,也就是创建多个对象的情况,这样就破坏了单例模式的原则了,单例模式本来就是要保证对象在内存中的唯一性,那么是在哪行代码引起的呢?分析如图:


解决这个问题就是要实现同步了,同步意思就是在某一个时刻只能有一个线程访问该方法,这样就保证了只能创建一个对象,线程是安全了,但是由于调用要进行等待,所以造成了调用效率不是很高,因为锁的存在,使用synchronized对方法进行同步:

public static synchronized SingleDemo2 getInstance(){
if(INSTANCE==null){
INSTANCE = new SingleDemo2();
}
return INSTANCE;
} 

懒汉式的有点是可以延迟加载,避免了资源浪费 线程安全, 缺点是由于要实现同步所以调用效率不高 因为每次调用都是要等待锁被释放

双重检测锁式:

这个出现是基于上面的懒汉式每次都要去判断锁是否释放了,这个只判断一次,相对于优化了访问效率,代码如下:

package cn.pattern.signinstance;
/**
 * 双重检测
 * @author admin
 */
public class SingleDemo3 {
private volatile static SingleDemo3 instance = null;  
private SingleDemo3() {}  
public static SingleDemo3 getInstance() {  
 if (instance == null) {  
  synchronized (SingleDemo3.class) {// 1  
   if (instance == null) {// 2  
    instance = new SingleDemo3();// 3  
   }  
  }  
 }  
 return instance;  
} 
}

但是这个由于编译器优化原因和JVM虚拟机内部模式原因偶尔会错,所以这种最好不要用,这个效率比懒汉式效率高,但是比恶汉式效率低

静态内部类的实现

代码如下:

package cn.pattern.signinstance;
/**
 * 静态内部类方式实现单例
 * @author admin
 */
public class SingleDemo4 {
private SingleDemo4(){}
private static class  SingleInnerDemo4{
private static final SingleDemo4 INSTANCE = new SingleDemo4();
}
public static  SingleDemo4 getInstance(){
return SingleInnerDemo4.INSTANCE;
}
}

这个不像恶汉式那样类被装在到内存中就实例化对象,静态内部类是没有的,只有外部调用了getInstance()方法时,才会加载和初始化静态内部类,同时类加载的过程是安全的,所以是线程安全的,而且实例化对象时使用了final修饰,所以一旦赋值就不能改变其值,所以静态内部类的方式实现单例优点就是线程安全 调用效率高,而且还达到了延迟加载的效果,很多开源项目中单例采用这种方式

枚举实现单例,

代码:

package cn.pattern.signinstance;
public enum SingleDemo5{
INSTANCE;
}
package cn.pattern.signinstance;
public class SingleDemo6 {
public static void main(String[] args) {
SingleDemo5 s1= SingleDemo5.INSTANCE;
SingleDemo5 s2= SingleDemo5.INSTANCE;
System.out.println(s1==s2);
}
}

枚举实现单例

优点:

枚举在底层实现就保证了它是单例,这样就避免了通过反射和反序列化的漏洞

缺点:

不能延迟加载

破解单例模式(不包含枚举)

下面就以恶汉式为例 利用反射对单例进行操作,你会发现单例的漏洞

package cn.pattern.signinstance;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
 * 测试几种单例模式的效率
 */
public class Client {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
SingleDemo1 singleDemo1 = SingleDemo1.getInstance();
SingleDemo1 singleDemo2 = SingleDemo1.getInstance();
System.out.println("singleDemo1="+singleDemo1);
System.out.println("singleDemo2="+singleDemo2);
try {
Class<SingleDemo1> clazz = (Class<SingleDemo1>) Class.forName("cn.pattern.signinstance.SingleDemo1");
Constructor<SingleDemo1> constructor = clazz.getDeclaredConstructor(null);
constructor.setAccessible(true);//跳过检查权限
SingleDemo1 s1 = constructor.newInstance();
SingleDemo1 s2 = constructor.newInstance();
System.out.println("s1="+s1);
System.out.println("s2="+s2);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} 
}
}

打印结果:

singleDemo1=cn.pattern.signinstance.SingleDemo1@2a139a55
singleDemo2=cn.pattern.signinstance.SingleDemo1@2a139a55
s1=cn.pattern.signinstance.SingleDemo1@15db9742
s2=cn.pattern.signinstance.SingleDemo1@6d06d69c

你会发现前二个是同一个对象,后二个是不同的对象,这就是利用了反射对单例进行了破坏,解决方案其实可以在你的构造函数中判断是否已经实力化了,如果已经实例化了就抛出异常

private SingleDemo1(){
if(INSTANCE!=null){  多次调用直接抛出异常
throw new RuntimeException();
}
}

现在再执行的话就会报错了:

singleDemo1=cn.pattern.signinstance.SingleDemo1@2a139a55
singleDemo2=cn.pattern.signinstance.SingleDemo1@2a139a55
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:422)
at cn.pattern.signinstance.Client.main(Client.java:20)
Caused by: java.lang.RuntimeException
at cn.pattern.signinstance.SingleDemo1.<init>(SingleDemo1.java:10)
... 5 more

下面是利用反序列化的方式破解单例模式,代码如下

package cn.pattern.signinstance;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
 */
public class Client {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
SingleDemo1 singleDemo1 = SingleDemo1.getInstance();
SingleDemo1 singleDemo2 = SingleDemo1.getInstance();
System.out.println("singleDemo1="+singleDemo1);
System.out.println("singleDemo2="+singleDemo2);
try {
FileOutputStream fos = new FileOutputStream("e:/data.txt");
ObjectOutputStream oos = new ObjectOutputStream(fos);
oos.writeObject(singleDemo1);
oos.close();
fos.close();
} catch (Exception e) {
e.printStackTrace();
} 
//读取数据
try {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("e:/data.txt"));
SingleDemo1 s1 = (SingleDemo1) ois.readObject();
System.out.println("s1="+s1);
} catch (Exception e) {
e.printStackTrace();
} 
}
}

当然SingleDemo1类要实现Serializable接口才行,打印结果

singleDemo1=cn.pattern.signinstance.SingleDemo1@2a139a55
singleDemo2=cn.pattern.signinstance.SingleDemo1@2a139a55
s1=cn.pattern.signinstance.SingleDemo1@55f96302

你会发现singleDemo1和s1的值不相同,那么这种如何防止破坏单例的呢?就是在反序列化的时候,如果写了readResolve()方法则直接返回之前创建的对象,而不创建新的对象,

解决方案:

/**
 * 恶汉式
 * @author admin
 */
public class SingleDemo1 implements Serializable{
private static final long serialVersionUID = 1L;
private static SingleDemo1 INSTANCE = new SingleDemo1();
private SingleDemo1(){
if(INSTANCE!=null){
throw new RuntimeException();
}
}
public static SingleDemo1 getInstance(){
return INSTANCE;
}
public Object readResolve() throws ObjectStreamException{
return INSTANCE;
}
}

现在执行再打印结果:

singleDemo1=cn.pattern.signinstance.SingleDemo1@2a139a55
singleDemo2=cn.pattern.signinstance.SingleDemo1@2a139a55
s1=cn.pattern.signinstance.SingleDemo1@2a139a55

ok,这样就解决了反序列化破坏单例的实现

现在是比较5种方式实现单例的效率

package cn.pattern.signinstance;
import java.util.concurrent.CountDownLatch;
/**
 * 比较5种创建单例实现的效率
 */
public class TimeClinet {
public static void main(String[] args) throws Exception {
int threadNum = 10;
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<10000000;j++){
SingleDemo1 s = SingleDemo1.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//mian线程阻塞,等待countDownLatch计算器为0 就不阻塞执行下面的代码
long end = System.currentTimeMillis();
System.out.println("恶汉式总消耗的时间:"+(end-start));
}
}

结果是:

恶汉式总消耗的时间:14

package cn.pattern.signinstance;
import java.util.concurrent.CountDownLatch;
/**
 * 比较5种创建单例实现的效率
 */
public class TimeClinet {
public static void main(String[] args) throws Exception {
int threadNum = 10;
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<10000000;j++){
SingleDemo2 s = SingleDemo2.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//mian线程阻塞,等待countDownLatch计算器为0 就不阻塞执行下面的代码
long end = System.currentTimeMillis();
System.out.println("懒汉式总消耗的时间:"+(end-start));
}
}
结果是:
懒汉式总消耗的时间:1022
package cn.pattern.signinstance;
import java.util.concurrent.CountDownLatch;
/**
 * 比较5种创建单例实现的效率
 */
public class TimeClinet {
public static void main(String[] args) throws Exception {
int threadNum = 10;
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<10000000;j++){
SingleDemo3 s = SingleDemo3.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//mian线程阻塞,等待countDownLatch计算器为0 就不阻塞执行下面的代码
long end = System.currentTimeMillis();
System.out.println("双重检测锁式总消耗的时间:"+(end-start));
}
}

结果是:

双重检测锁式总消耗的时间:24

package cn.pattern.signinstance;
import java.util.concurrent.CountDownLatch;
/**
 * 比较5种创建单例实现的效率
 */
public class TimeClinet {
public static void main(String[] args) throws Exception {
int threadNum = 10;
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<10000000;j++){
SingleDemo4 s = SingleDemo4.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//mian线程阻塞,等待countDownLatch计算器为0 就不阻塞执行下面的代码
long end = System.currentTimeMillis();
System.out.println("静态内部类方式实现单例总消耗的时间:"+(end-start));
}
}

结果是:

静态内部类方式实现单例总消耗的时间:23

package cn.pattern.signinstance;
import java.util.concurrent.CountDownLatch;
/**
 * 比较5种创建单例实现的效率
 */
public class TimeClinet {
public static void main(String[] args) throws Exception {
int threadNum = 10;
long start = System.currentTimeMillis();
final CountDownLatch countDownLatch = new CountDownLatch(threadNum);
for(int i=0;i<threadNum;i++){
new Thread(new Runnable() {
public void run() {
for(int j=0;j<10000000;j++){
SingleDemo5 s = SingleDemo5.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
countDownLatch.await();//mian线程阻塞,等待countDownLatch计算器为0 就不阻塞执行下面的代码
long end = System.currentTimeMillis();
System.out.println("枚举实现单例方式总消耗的时间:"+(end-start));
}
}

结果是:

枚举实现单例方式总消耗的时间:19

总结:

效率从高到底:如图:特别说明这是在我自己的机制下跑的结果


ok,写完!










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值