java的单例详解

来源:互联网 发布:网络没问题app网络异常 编辑:程序博客网 时间:2024/06/07 14:08

今天我的一个开发项目可能需要用到单例来实现一些功能,因此在网上各大博客看到许多的关于java多线程的单例模式,虽然网上有许多这方面的博客,但是这是我通过学习和自己实验理解之后总结出来的,不一定适合所有人。

单例的几种写法

1、懒汉式(线程不安全)[不可用]

实验代码

package test;public class Test {       public static void main(String[] args) {                       A obj1 = new A();        B obj2 = new B();                obj1.start();        obj2.start();            }    }class T{    public int b ;    private T(){        b = 1;    }    private static T obj;    public static T getinstance(){        if (obj==null){            obj = new T();        }        return obj;    }}class A extends Thread{    @Override    public void run(){        T obj1= T.getinstance();        for(int i=0;i<10;i++){                System.out.println("A"+obj1.b);        obj1.b++;        }    }}class B extends Thread{    @Override    public void run(){        T obj2 = T.getinstance();        for(int i=0;i<10;i++){                System.out.println("B"+obj2.b);        obj2.b++;        }    }}

实验结果:

run:
B1
A1
B2
A2
B3
A3
A4
A5
B4
B5
A6
B6
A7
B7
B8
A8
B9
A9
B10
A10

由以上结果显示,T类创建了两个完全不相关的对象。这种写法起到了Lazy Loading的效果,但是只能在单线程下使用。如果在多线程下,一个线程进入了if (obj== null)判断语句块,还未来得及往下执行,另一个线程也通过了这个判断语句,这时便会产生多个实例。很明显A、B就是这种情况,产生了两个不相关的实例。所以在多线程环境下一定不可使用这种方式。

这里我要说明一下Lazy Loading的含义:从字面解释就是懒惰加载,其实这个解释并不准确,应该是延迟加载,就是不用类的实例不用在初始化装载,只有到使用的时候才加载。

2、懒汉式(线程安全,同步方法)[不推荐用]

这种方法就是在getinstance方法添加同步锁

实验代码:

package test;public class Test {        public static void main(String[] args) {               A obj1=new A();        B obj2 = new B();                obj1.start();        obj2.start();            }    }class T{    public int b ;    private T(){        b = 1;    }    private static T obj;    public static synchronized T getinstance(){        if (obj==null){            obj = new T();        }        return obj;    }}class A extends Thread{    @Override    public void run(){        T obj1= T.getinstance();        for(int i=0;i<10;i++){                System.out.println("A"+obj1.b);        obj1.b++;        }    }}class B extends Thread{    @Override    public void run(){        T obj2 = T.getinstance();        for(int i=0;i<10;i++){                System.out.println("B"+obj2.b);        obj2.b++;        }    }}
实验结果:

run:
A1
B1
A2
B3
A4
B5
A6
B7
B9
A8
B10
A11
B12
A13
B14
A15
B16
A17
B18
A19
可见这种也能实现多线程的单例安全,但是就是效率太低了,每个线程在想获得类的实例时候,执行getinstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return就行了。方法进行同步效率太低要改进。这是只有两个线程竞争,如果在分布式中可能有多个线程竞争效率会更低。

3、懒汉式(同步代码块)[视情况而定,但如果可用也会降低效率同样不推荐使用]

实验代码:

package test;public class Test { static void main(String[] args) {                       A obj1=new A();        B obj2 = new B();                obj1.start();        obj2.start();            }    }class T{    public int b ;    private T(){        b = 1;    }    private static T obj;    public static  T getinstance(){       if (obj==null){                 //synchronized(T.class){                                       //   if (obj==null){            synchronized(T.class){     //       obj = new T();            obj = new T();             //   }            }                          //}        }        return obj;    }}class A extends Thread{    @Override    public void run(){        T obj1= T.getinstance();        for(int i=0;i<10;i++){                System.out.println("A"+obj1.b);        obj1.b++;        }    }}class B extends Thread{    @Override    public void run(){        T obj2 = T.getinstance();        for(int i=0;i<10;i++){                System.out.println("B"+obj2.b);        obj2.b++;        }    }}


结果:

run:
B1
A1
B2
A2
B3
A3
B4
B5
A4
A5
A6
A7
B6
B7
B8
A8
B9
A9
B10
A10
同一一样。但是有趣的是,作者如果把整个if语句同步(像注释中一样的写法),能够保证单例安全。因此在这个例子中关键还是A、B两个线程竞争if判断语句。同时这个写法也会降低效率。我看到网上的博客还有双重检查通过两次检查,其中第二次检查同步来保证线程安全。但是我觉得没有我注释中写的效率高,只进行了一次检查。

4、饿汉式(静态常量)[可用]

实验代码:

package test;public class Test {    public static void main(String[] args) {             A obj1=new A();        B obj2 = new B();        obj1.start();        obj2.start();                   }    }class T{    public int b ;    private T(){        b = 1;    }    private static final T obj = new T();    public static  T getinstance(){        return obj;    }}class A extends Thread{    @Override    public void run(){        T obj1= T.getinstance();        for(int i=0;i<10;i++){                System.out.println("A"+obj1.b);        obj1.b++;        }    }}class B extends Thread{    @Override    public void run(){        T obj2 = T.getinstance();        for(int i=0;i<10;i++){                System.out.println("B"+obj2.b);        obj2.b++;        }    }}


结果

run:
A1
A2
A3
A4
A5
A6
A7
A8
A9
A10
B1
B12
B13
B14
B15
B16
B17
B18
B19
B20

优点:这种写法比较简单,就是在类装载的时候就完成实例化。避免了线程同步问题。
缺点:在类装载的时候就完成实例化,没有达到Lazy Loading的效果。如果从始至终从未使用过这个实例,则会造成内存的浪费。

5、静态内部类[推荐用]

实验代码:

package test;public class Test {    public static void main(String[] args) {        A obj1=new A();        B obj2 = new B();        obj1.start();        obj2.start();            }    }class T{    public int b ;    private T(){        b = 1;    }    private static class T1{        private static final T obj = new T();    }    public static T getinstance(){        return T1.obj;    }}class A extends Thread{    @Override    public void run(){        T obj1= T.getinstance();        for(int i=0;i<10;i++){                System.out.println("A"+obj1.b);        obj1.b++;        }    }}class B extends Thread{    @Override    public void run(){        T obj2 = T.getinstance();        for(int i=0;i<10;i++){                System.out.println("B"+obj2.b);        obj2.b++;        }    }}
结果:

run:
A1
A2
B1
A3
B4
A5
B6
A7
B8
B10
B11
B12
A9
A14
A15
B13
A16
B17
A18
B19
这种方式跟饿汉式方式采用的机制类似,但又有不同。两者都是采用了类装载的机制来保证初始化实例时只有一个线程。不同的地方在饿汉式方式是只要Singleton类被装载就会实例化,没有Lazy-Loading的作用,而静态内部类方式在Singleton类被装载时并不会立即实例化,而是在需要实例化时,调用getInstance方法,才会装载SingletonInstance类,从而完成Singleton的实例化。

以上就是我结合网上其他博主的博客,加上自己的实验理解总结的关于java单例在多线程的实现和安全。