单例模式:确保一个类只有一个实例,并且提供一个全局的访问点。
我们先看一下标准的懒汉方式。
public class SingletonOne {
private static SingletonOne Sing;
private SingletonOne(){}
public static SingletonOne getInstance(){
if(Sing==null){
Sing=new SingletonOne();
}
return Sing;
}
}
分析一下这种写法。
首先声明了一个私有的静态变量
然后我们可以在外部调用
SingletonOne.getInstance()获取对象实例。
但是现在我们模拟一下场景,如果同时两个或者更多线程同时去调用
SingletonOne.getInstance()
会出现什么情况呢,假设线程一先判断完Sing是否为null,这时候JVM把CPU资源切换到了第二个线程,第二个线程去再去判断,发现还是为null,那么将会创建两个不同的对象。如果在确保只有一个线程访问的情况下,这种方式是可以的
我们举个例子:
public class Test {
public static void main(String[] args) {
for (int i = 0; i < 50; i++) {
Thread th=new Thread(new NewSingleton());
th.start();
}
}
}
class NewSingleton implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println(SingletonOne.getInstance());
}
}
这时候我创建了50个线程 同时去调用SingletonOne.getInstance() 我们看一下打印结果。
com.wy.singleton.SingletonOne@22b5ecf4
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@22b5ecf4
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e
....
这里我只列举几条 我们发现 获取的对象是不一样的。
所以这种方式不适合多线程的情况。
那么有人会说了 使用synchronized 同步方法 或者同步代码块不就可以解决这个问题吗 好 我们实验一下。
我们改动一下getInstance方法
public synchronized static SingletonOne getInstance(){
if(Sing==null){
Sing=new SingletonOne();
}
return Sing;
}
(在静态方法中使用synchronized ,默认锁为类的类 相当于SingletonOne.class)
然后我们再次运行一下 结果如下
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
发现这个获取的是一致的。
但是使用synchronized会造成比较耗时,确保只有一个线程进入此方法。 我们同时开启50个线程 那么他会一个个进入。
绝大部分的耗时操作都用在synchronized修饰符的同步准备上,因此从性能上说很不划算。
public class SingletonOne {
private static SingletonOne Sing;
private SingletonOne(){}
public static SingletonOne getInstance(){
synchronized (SingletonOne.class) {
if(Sing==null){
Sing=new SingletonOne();
}
return Sing;
}
}
}
这种方式与上面那个方式类示 每次调用getInstance()还是要进行同步的。
我们再改一下
public class SingletonOne {
private static SingletonOne Sing;
private SingletonOne(){}
public static SingletonOne getInstance() {
if (Sing == null) {
synchronized (SingletonOne.class) {//第二个线程会在这里等待锁 第二个线程已经判断Sing==null 那么第二个线程会再次new一次的
Sing = new SingletonOne();//假设第一个线程走到这里
}
}
return Sing;
}
}
我们再看标准的饿汉方式。
根据JLS(Java Language Specification)中的规定 ,一个类在一个ClassLoader中只会被初始化一次,这点是JVM本身保证的,那就把初始化实例的事情扔给JVM好了,代码被改成这样:也就是标准的饿汉式写法
public class SingletonOne {
private static SingletonOne Sing=new SingletonOne();;
private SingletonOne(){}
public static SingletonOne getInstance() {
return Sing;
}
}
public class SingletonOne {
private static class SingleHolder{
static final SingletonOne sing=new SingletonOne();
}
private SingletonOne(){}
public static SingletonOne getInstance(){
return SingleHolder.sing;
}
}
这两种方式都是标准的线程安全的写法。