Java多线程编程七(单例模式)

饿汉模式(立即加载)

“饿汉模式”也就是立即加载,在使用类时对应的对象已经创建

public class MySingleton  {
    private static MySingleton mySingleton = new MySingleton();

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        return mySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName() + "  " + MySingleton.getInstance().hashCode());
                }
            }).start();
        }
    }
}

运行结果为:

Thread-0  321580105
Thread-1  321580105
Thread-2  321580105

由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即加载饿汉式的单例模式。

懒汉模式(延迟加载)

延迟加载和立即加载相对,也就是在使用时才创建,通常的实现是在get()方法中进行new实例化。

懒汉单例简单使用

修改上面的单例代码为:

public class MySingleton  {

    private static MySingleton mySingleton;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        if (mySingleton == null) {
            mySingleton = new MySingleton();
        }
        return mySingleton;
    }
}

其他不变,运行结果为:

Thread-0  500866883
Thread-1  500866883
Thread-2  500866883

由结果可知,三个线程中的hashCode一样,则证明所打印的是同一个对象,即立即延迟懒汉式的单例模式。

懒汉单例的缺点

虽然上述代码实现了单例模式,但是在多线程的环境下,“懒汉式”的单例就有一定几率出现错误,不能保持单例的状态。举个栗子:

public class MySingleton {

    private static MySingleton mySingleton;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        try {
            if (mySingleton == null) {
                Thread.sleep(3000);
                mySingleton = new MySingleton();
                System.out.println("创建单例对象 " + mySingleton);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"  "+MySingleton.getInstance().hashCode());
                }
            }).start();
        }
    }
}

运行结果如下:

创建单例对象 cn.appblog.thread.MySingleton@182acfc9
Thread-0  405658889
创建单例对象 cn.appblog.thread.MySingleton@182acfc9
Thread-1  405658889
创建单例对象 cn.appblog.thread.MySingleton@9c3310a5
Thread-2  158381581

由结果可知,不再是单例。由于多个线程操作一个变量,导致其不确定性,所以我们需要对其进行同步处理,可以在getInstance()方法前加上synchronized关键字,如:

运行结果为:

public synchronized static MySingleton getInstance() {
    try {
        if (mySingleton == null) {
            Thread.sleep(3000);
            mySingleton = new MySingleton();
            System.out.println("创建单例对象 " + mySingleton);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return mySingleton;
}

运行结果为:

创建单例对象 cn.appblog.thread.MySingleton@619d7c8
Thread-0  102356936
Thread-1  102356936
Thread-2  102356936

由于在同一时间只能有一个线程访问,只有当获取锁的线程将其释放其他线程才能访问getInstance()同步方法,也就成功避免了非线程安全问题。

我们还可以利用同步代码块来解决这个问题,如:

public static MySingleton getInstance() {
    try {
        synchronized (MySingleton.class) {
            if (mySingleton == null) {
                Thread.sleep(3000);
                mySingleton = new MySingleton();
                System.out.println("创建单例对象 " + mySingleton);
            }
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    return mySingleton;
}

或者使用ReentrantLock

public class MySingleton {

    private static MySingleton mySingleton;

    private static ReentrantLock lock = new ReentrantLock();

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        try {
            lock.lock();
            if (mySingleton == null) {
                Thread.sleep(3000);
                mySingleton = new MySingleton();
                System.out.println("创建单例对象 " + mySingleton);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return mySingleton;
    }
}

或者

public class MySingleton {

    private static MySingleton mySingleton;

    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        try {
            lock.writeLock().lock();
            if (mySingleton == null) {
                Thread.sleep(3000);
                mySingleton = new MySingleton();
                System.out.println("创建单例对象 " + mySingleton);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
        return mySingleton;
    }
}

上述方法都可以解决这个非线程安全问题,但是其执行效率低下。同步代码块可以同步和异步混合分别执行来提高线程的执行效率,我们只关注关于修改共享变量的那些逻辑代码即可,如果按照这种思想来修改如下:

public class MySingleton {

    private static MySingleton mySingleton;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        try {
            if (mySingleton == null) {
                synchronized (MySingleton.class) {
                    Thread.sleep(3000);
                    mySingleton = new MySingleton();
                    System.out.println("创建单例对象 " + mySingleton);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mySingleton;
    }
}

运行结果如下:

创建单例对象 cn.appblog.thread.MySingleton@108c737f
Thread-0  277650063
创建单例对象 cn.appblog.thread.MySingleton@db6222b
Thread-1  230058813
创建单例对象 cn.appblog.thread.MySingleton@1dda492b
Thread-2  500844802

由此来看,虽然程序的运行效率提高了,但是最核心的多线程安全问题并未得到解决,因此我们采用DCL双检查锁机制(在同步代码块里再进行一次判空,这样在下个线程获取锁来执行时就会判断当前是否已经创建,可以避免再次重新创建实例)如:

public class MySingleton {

    private static MySingleton mySingleton;

    private MySingleton() {
    }

    public static MySingleton getInstance() {
        try {
            if (mySingleton == null) {
                Thread.sleep(3000);
                synchronized (MySingleton.class) {
                    if (mySingleton == null) {
                        mySingleton = new MySingleton();
                        System.out.println("创建单例对象 " + mySingleton);
                    }
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return mySingleton;
    }
}

运行结果:

创建单例对象 cn.appblog.thread.MySingleton@8f62e66
Thread-0  150351562
Thread-1  150351562
Thread-2  150351562

由结果可知,成功解决非线程安全问题和效率低下的问题。

静态内置类模式

采用静态内部类的静态对象来实现单例

public class MySingleton {

    private static class MySingletonHelper {
        private static MySingleton mySingleton = new MySingleton();
    }
    private MySingleton() {
    }

    public static MySingleton getInstance() {
        return MySingletonHelper.mySingleton;
    }
}

同样的main方法运行结果为:

Thread-0  1603718986
Thread-1  1603718986
Thread-2  1603718986

序列化和反序列化的单例实现

如果遇到了序列化对象,使用默认的方式运行得到的结果还是多例的。

public class MySingleton implements Serializable {
    private static class MySingletonHelper {
        private static MySingleton mySingleton = new MySingleton();
    }
    private MySingleton() {
    }
    public static MySingleton getInstance() {
        return MySingletonHelper.mySingleton;
    }

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

运行结果为:

1670675563
189568618

出现多例情况(具体原因将会在后续文章中进行剖析,这里就直接给出解决方案)

由于序列化/反序列化会破坏单例模式,所以需要在单例模式中加上如下方法,返回我们的单例变量:

public class MySingleton implements Serializable {
    private MySingleton() {
    }
    private static class MySingletonHelper {
        private static MySingleton mySingleton = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHelper.mySingleton;
    }

    protected Object readResolve() {
        System.out.println("调用 readResolve 方法!");
        return MySingletonHelp.mySingleton;
    }
}

就解决我们单例被破坏的问题。

使用static代码块实现单例

public class MySingleton {
    private static MySingleton mySingleton = null;
    static {
        mySingleton = new MySingleton();
    }
    private MySingleton() {
    }
    public static MySingleton getInstance() {
        return mySingleton;
    }

    public static void main(String[] args) {
        for (int i = 0; i < 3; i++) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println(Thread.currentThread().getName()+"  "+MySingleton.getInstance().hashCode());
                }
            }).start();
        }
    }

}

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/13/java-multi-threag-programming-singleton/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Java多线程编程七(单例模式)
饿汉模式(立即加载) “饿汉模式”也就是立即加载,在使用类时对应的对象已经创建 public class MySingleton { private static MySingleton mySingleton = n……
<<上一篇
下一篇>>
文章目录
关闭
目 录