java的单例详解

今天我的一个开发项目可能需要用到单例来实现一些功能,因此在网上各大博客看到许多的关于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单例在多线程的实现和安全。














评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

风吹千里

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值