这是我写的第一篇文章,目的是为了自己加深理解和以后的用的时候看看,希望各位大神指点不当之处,让我这个小小菜鸟进一步的学习,谢谢!!
今天我遇到了一个题,就是单例模式的,单例模式创建的是同一个对象吗?在多线程中是不是呢?想要完美应该怎么写单例呢?
首先说说单例模式(Singleton Pattern):
目的:单例的作用就是创建一个唯一的实例,防止对象泛滥。
分类:单例一般分为懒汉式和饿汉式
1、懒汉式:
class LoveSQQ{
private static LoveSQQ sqq = null;
private LoveSQQ(){
}
public static LoveSQQ getLoveSQQ(){
if(sqq == null){
sqq = new LoveSQQ();
}
return sqq;
}
2、饿汉式:
public class LoveSQQ{
private static LoveSQQ sqq = new LoveSQQ();
private LoveSQQ(){}
public static LoveSQQ getLoveSQQ(){
return sqq;
}
}
这是两种相信大家都熟悉,我想说的就是这两种模式是不是线程安全的呢? 首先我测试了第一种懒汉式的
public class SingletonTest {
public static void main(String[] args) {
for(int i=0;i<10;i++){
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(10000);
System.out.println(Thread.currentThread().getName()+"----"+LoveSQQ.getLoveSQQ());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
}).start();
new Thread(new Runnable(){
public void run() {
while(true){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"----"+LoveSQQ.getLoveSQQ());
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
}).start();
}
}
}
class LoveSQQ{
private static LoveSQQ sqq = null;
private LoveSQQ(){
}
public static LoveSQQ getLoveSQQ(){
if(sqq == null){
System.out.println("创建实例");
sqq = new LoveSQQ();
}
return sqq;
} 得到结果:创建实例
创建实例
创建实例
Thread-5----com.wwy.thread.LoveSQQ@e0e1c6
创建实例
Thread-9----com.wwy.thread.LoveSQQ@6ca1c
创建实例
Thread-11----com.wwy.thread.LoveSQQ@1bf216a
创建实例
Thread-7----com.wwy.thread.LoveSQQ@12ac982
Thread-3----com.wwy.thread.LoveSQQ@10d448
Thread-13----com.wwy.thread.LoveSQQ@1389e4
Thread-17----com.wwy.thread.LoveSQQ@1389e4
Thread-15----com.wwy.thread.LoveSQQ@1389e4
这说明上面写的懒汉式是线程不安全的,为什么呢?
分析:在多个线程来执行上面的那个LoveSQQ类的getLoveSQQ()方法的时候,当线程1进去的时候创建了一个实例,但还没终止那个方法的时候,cpu切换了另一个线程,线程2有创建了一个实例,就出现了上面的结果。造成这个结果的原因就是sqq=new LoveSQQ();这句不具有原子性(原子性:就是一句话要么执行,要么不执行,不可分的),而它在jvm中是分了好几步来做的,大致分为三类:
1、给sqq实例分配空
2、初始化sqq构造器
3、将sqq对象指向分配的内存空(注意这里sqq就不是null了 )
就是这些步骤造成了它不具有原子性,也就线程不安全了接下来要考虑线程安全的问题,我首先想到的就是synchronized,实现同步互斥不就可以了吗?看下面的代码:
class LoveSQQ{
private static LoveSQQ sqq = null;
private LoveSQQ(){
}
public synchronized static LoveSQQ getLoveSQQ(){
if(sqq == null){
System.out.println("创建实例");
sqq = new LoveSQQ();
}
return sqq;
}
这样虽然是线程安全了但是同样也很大的降低了它的性能。写成同步代码块是不是能提高一下性能呢?
class LoveSQQ{
private static LoveSQQ sqq = null;
public synchronized static LoveSQQ getLoveSQQ(){
if(sqq == null){
synchronized(LoveSQQ.class){
if(sqq == null){
sqq = new LoveSQQ();
}
}
}
return sqq;
}
}
这样写可以至少比上面的性能好了一点。但肯定不是最完美的,在网上我看到一种使用静态内部类的
class LoveSQQ{
private LoveSQQ(){}
private static class Singleton{
private final static LoveSQQ sqq = new LoveSQQ();
}
public static LoveSQQ getLoveSQQ(){
return Singleton.sqq;
}
}
这一种性能就比较好多了。说到这里就让我们先来看看饿汉式,通过前面的测试得到饿汉式他是线程安全的,为什么呢?因为它里面的实例相当于一个静态常量,在类初始化的时候就初始化了,每个线程调用的都是同一个实例。而使用静态内部类的和饿汉式很相似,它是通过内部类把饿汉式改为了懒汉式,灵活性大大的提高了。