源无极

导航

 

前言

        本章非常重要,通过单例和多线程的结合,在这个过程中会出现很多

以前没有考虑过的情况,一些不良的程序设计如果用在项目中,将可能

遇到非常大的麻烦。本案例也说明了,线程与某些技术相结合时要考虑的

问题很多,本章学习考虑的问题是:

 如何使单例模式遇到多线程是安全的,正确的

一、立即加载/ ‘ 饿汉模式’

        立即加载就是使用类的时候已经将对象创建完毕,常见的办法就是new 实例化

从语境看就是“ 捉急”,“ 急迫” 的意思,饿汉模式是在调用之前,实例已经被创建了。

package com.it.po.thread13;

public class MyObject {
//立即加载
    private static MyObject myObject=new MyObject();
    private MyObject() {//private修饰就是防止该类再次被实例化
    }
    public static MyObject getMyObject(){
        //该方法没有同步,还是存在线程不安全的问题。
        return myObject;
    }
}

 

package com.it.po.thread13;
public class MyThread1 extends Thread {
    @Override
    public void run() {
        super.run();
        //同一个对象hashCode 得到的值一样
        System.out.println(MyObject.getMyObject().hashCode());
    }
}

 

package com.it.po.thread13;
public class Run1 {
    public static void main(String[] args) {
        MyThread1 my1 = new MyThread1();
        MyThread1 my2 = new MyThread1();
        MyThread1 my3 = new MyThread1();
        my1.start();
        my2.start();
        my3.start();
    }
}
6723139
6723139
6723139

 

同一个对象,hashCode一样

 

二、延迟加载 / ‘ 懒汉模式’

延迟加载就是在调用的时候,实例才会被创建,在调用get方法时new 对象

这种情况就是懒汉模式

package com.it.po.thread13;
public class MyObject2 {
  //延迟加载
    private static MyObject2 myObject;
    private MyObject2() {//private修饰就是防止该类再次被实例化
    }
    public static MyObject2 getMyObject(){
        if(myObject==null){
            myObject=new MyObject2();
        }
        return myObject;
    }
}
package com.it.po.thread13;
public class MyThread2 extends Thread {
    @Override
    public void run() {
        super.run();
        //同一个对象hashCode 得到的值一样
        System.out.println(MyObject2.getMyObject().hashCode());
    }
}
package com.it.po.thread13;
public class Run2 {
    public static void main(String[] args) {
        MyThread2 my1 = new MyThread2();
        MyThread2 my2 = new MyThread2();
        MyThread2 my3 = new MyThread2();
        my1.start();
        my2.start();
        my3.start();
       
    }
}
4419973
4419973
4419973

 

其实在多线程的情况下,就会出现多个实例的情况。

三、延时加载 / ‘ 懒汉模式’的缺点

看一下懒汉式情况下多线程的出现的问题

package com.it.po.thread13;
public class MyObject2 {
  //延迟加载
    private static MyObject2 myObject;
    private MyObject2() {//private修饰就是防止该类再次被实例化
    }
    public static MyObject2 getMyObject(){
        if(myObject==null){
            try {
                System.out.println("线程 "+Thread.currentThread().getName()+" waiting");
                Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作
                myObject=new MyObject2();
                System.out.println("线程 "+Thread.currentThread().getName()+" end ");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return myObject;
    }
}

 

package com.it.po.thread13;
public class MyThread2 extends Thread {
    @Override
    public void run() {
        super.run();
        //同一个对象hashCode 得到的值一样
        System.out.println(MyObject2.getMyObject().hashCode());
    }
}
package com.it.po.thread13;
public class Run2 {
    public static void main(String[] args) {
        MyThread2 my1 = new MyThread2();
        MyThread2 my2 = new MyThread2();
        MyThread2 my3 = new MyThread2();
        my1.start();
        my2.start();
        my3.start();
       
    }
}
线程 Thread-0 waiting
线程 Thread-1 waiting
线程 Thread-2 waiting
线程 Thread-1 end 
线程 Thread-0 end 
4372721
4372721
线程 Thread-2 end 
22840778

 

错误的单例模式

(一)解决上面的办法

加一个关键字 synchronized    ,其他不变

package com.it.po.thread13;
public class MyObject2 {
  //延迟加载
    private static MyObject2 myObject;
    private MyObject2() {//private修饰就是防止该类再次被实例化
    }
   synchronized   public static MyObject2 getMyObject(){
        if(myObject==null){
            try {
                Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作
                myObject=new MyObject2();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return myObject;
    }
}

 

16371033
16371033
16371033

 

       此方法效率其实是很低下的,一个线程想得到对象就必须等

上一个线程执行完之后才可以获取对象。

  (二)另一个解决办法同步代码块

package com.it.po.thread13;
public class MyObject2 {
  //延迟加载
    private static MyObject2 myObject;
    private MyObject2() {//private修饰就是防止该类再次被实例化
    }
      public static MyObject2 getMyObject(){
        if(myObject==null){
            try {
                Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作
                synchronized (MyObject2.class) {
                    myObject = new MyObject2();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
        return myObject;
    }
}

 

4419973
22840778
4372721

 

此方法只是对关键代码加同步,但是还不是线程安全

如何解决懒汉式遇到的多线程,又顾上效率问题呢?

(三)使用DCL双检查锁机制。(线程安全)

package com.it.po.thread13;
public class MyObject3 {
  //延迟加载
    private static MyObject3 myObject;
    private MyObject3() {//private修饰就是防止该类再次被实例化
    }
      public static MyObject3 getMyObject(){
            try {
                if(myObject!=null) {
                }else {//为空时
                    Thread.sleep(3000);//模拟在创建对象之前做的一些准备工作
                    synchronized (MyObject3.class) {
                        if(myObject==null){
                            myObject = new MyObject3();
                        }
                    }
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        return myObject;
    }
}

 

package com.it.po.thread13;
public class MyThread3 extends Thread {
    @Override
    public void run() {
        super.run();
        //同一个对象hashCode 得到的值一样
        System.out.println(MyObject3.getMyObject().hashCode());
    }
}
package com.it.po.thread13;
public class Run3 {
    public static void main(String[] args) {
        MyThread3 my1 = new MyThread3();
        MyThread3 my2 = new MyThread3();
        MyThread3 my3 = new MyThread3();
        my1.start();
        my2.start();
        my3.start();
       
    }
}

 

28911017
28911017
28911017

 

双重检查锁功能,成功解决了懒汉模式遇到的多线程问题

四、使用静态内置类实现单例模式(线程安全)

package com.it.po.thread13;
public class MyObject4 {
    private MyObject4() {
    }
    private static class MyObjectHandler{//静态内置类
        private static MyObject4 myObject4=new MyObject4();
    }
    public static MyObject4 getInstance(){
        return MyObjectHandler.myObject4;
    }
}

 

package com.it.po.thread13;
public class MyThread4 extends Thread {
    @Override
    public void run() {
        super.run();
        //同一个对象hashCode 得到的值一样
        System.out.println(MyObject4.getInstance().hashCode());
    }
}
package com.it.po.thread13;
public class Run4 {
    public static void main(String[] args) {
        MyThread4 my1 = new MyThread4();
        MyThread4 my2 = new MyThread4();
        MyThread4 my3 = new MyThread4();
        my1.start();
        my2.start();
        my3.start();
       
    }
}
6723139
6723139
6723139

 

五、序列化与反序列化的单例模式

    以上静态内置类可以达到线程安全的效果,但是遇到序列化对象时

使用默认的方式运行得到的结果还是多例的。

 

package com.it.po.thread13;

import java.io.Serializable;

public class MyObject5 implements Serializable {
    private static final long serializable=888L;
    private MyObject5() {
    }
    private static class MyObjectHandler{//静态内置类
        private static final MyObject5 myObject4=new MyObject5();
    }
    public static MyObject5 getInstance(){
        return MyObjectHandler.myObject4;
    }
   /* public static  MyObject5 readResolve() {
        System.out.println("调用了readResolve 方法");
        return MyObjectHandler.myObject4;
    }*/
}

 

 

 

 

package com.it.po.thread13;

import java.io.*;

public class Run5 {
    public static void main(String[] args) {
        try {
            MyObject5 instance = MyObject5.getInstance();
            FileOutputStream fileOutputStream = new FileOutputStream(new File("po.txt"));
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(instance);
            objectOutputStream.close();
            fileOutputStream.close();
            System.out.println(instance.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }catch (IOException e2){
            e2.printStackTrace();
        }
        try {
            FileInputStream fileInputStream = new FileInputStream(new File("po.txt"));
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            MyObject5 myObject = (MyObject5) objectInputStream.readObject();
            objectInputStream.close();
            fileInputStream.close();
            System.out.println(myObject.hashCode());
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

 

 

 

 

19997786
14519747

 

 

 

MyObject5 放开

运行

 

 

14519747
14519747

 

 

 

六、使用static代码块实现多例模式(线程安全)

 

package com.it.po.thread13;

public class MyObject6 {
//立即加载
    private static MyObject6 myObject=null;
    private MyObject6() {//private修饰就是防止该类再次被实例化
    }
    static {
        myObject=  new MyObject6();
    }
    public static MyObject6 getMyObject(){
        return myObject;
    }
}

 

 

 

 

package com.it.po.thread13;
public class MyThread5 extends Thread {
    @Override
    public void run() {
        super.run();
    for(int i=0;i<5;i++){
        System.out.println(MyObject6.getMyObject().hashCode());
    }
    }
}

 

 

 

 

package com.it.po.thread13;
public class Run6 {
    public static void main(String[] args) {
        MyThread5 my1 = new MyThread5();
        MyThread5 my2 = new MyThread5();
        MyThread5 my3 = new MyThread5();
        my1.start();
        my2.start();
        my3.start();
       
    }
}

 

5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549
5585549

 

 

六、使用enum枚举数据类型实现单例模式

    和静态代码块相似,使用枚举类时,构造方法会被自动调用

利用这个特性实现单例模式。

具体可以参考设计模式的单例模式部分。

posted on 2019-12-14 13:06  源无极  阅读(96)  评论(0)    收藏  举报