单例模式详解
单例模式详解
单例模式就是只能有一个实例的模式;最大的特点是构造器私有。建议看视频。
单例模式分为两种:
饿汉式:直接将类的实例初始化好,可能会存在资源浪费的情况;
懒汉式:用的时候再初始化实例,比较常用。
饿汉式
特点:
- 构造器私有
- 构建一个静态常量表示类的实例
- 构建一个静态getInstance()方法,外部可以获得类的实例
- 多线程下也可以保证实例的唯一性
package com.example.juc;
public class TestHungryMan {
private byte[] data1 = new byte[1024 * 1024];
private byte[] data2 = new byte[1024 * 1024];
private byte[] data3 = new byte[1024 * 1024];
private byte[] data4 = new byte[1024 * 1024];
private byte[] data5 = new byte[1024 * 1024];
private TestHungryMan() {
System.out.println(Thread.currentThread().getName());
}
private final static TestHungryMan HUNGRY_MAN = new TestHungryMan();
public static TestHungryMan getInstance() {
return HUNGRY_MAN;
}
}
class Test {
public static void main(String[] args) {
// 多线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TestHungryMan testHungryMan = TestHungryMan.getInstance();
System.out.println(testHungryMan);
}).start();
}
}
}
Thread-0
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
com.example.juc.TestHungryMan@6b7011dd
懒汉式
普通懒汉式
特点:
- 构造器私有
- 创建一个静态变量代表实例
- 构建一个公开的静态方法getInstance()来获取实例,;里面的逻辑为实例为空时才创建
- 多线程时单例失效
package com.example.juc;
public class TestLazyMan {
private TestLazyMan() {
System.out.println(Thread.currentThread().getName());
}
private static TestLazyMan lazyMan;
public static TestLazyMan getInstance() {
if (lazyMan == null) {
lazyMan = new TestLazyMan();
}
return lazyMan;
}
}
class Test1 {
public static void main(String[] args) {
// 正常单线程获取实例,没问题
// TestLazyMan lazyMan = TestLazyMan.getInstance();
// 多线程 无法保证实例唯一
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TestLazyMan testLazyMan = TestLazyMan.getInstance();
System.out.println(testLazyMan);
}).start();
}
}
}
result:
Thread-0
com.example.juc.TestLazyMan@22e7a307
Thread-2
Thread-3
com.example.juc.TestLazyMan@1acc1ca0
Thread-4
Thread-1
com.example.juc.TestLazyMan@39ae8a9b
Thread-7
Thread-6
com.example.juc.TestLazyMan@6bfcab86
Thread-9
com.example.juc.TestLazyMan@384e8869
Thread-8
Thread-5
com.example.juc.TestLazyMan@d166de5
com.example.juc.TestLazyMan@3261ea9b
com.example.juc.TestLazyMan@2131513b
com.example.juc.TestLazyMan@46a00ba1
com.example.juc.TestLazyMan@72bb9f6e
DCL懒汉式(双层检查锁模式)
特点:
-
其他同普通懒汉式
-
getInstance()里面的逻辑采用双层检查锁模式:先做一次非同步的检查,将类锁住,再做一次同步的检查,保证多线程单例
-
由于类的初始化是非原子性操作,可能会导致代码出错
类的初始化:
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
由于指令重排,执行顺序可能是132, 就会出问题
所以要使用双层检查锁+原子性操作(volatile)
package com.example.juc;
public class TestLazyMan {
private TestLazyMan() {
System.out.println(Thread.currentThread().getName());
}
private volatile static TestLazyMan lazyMan;
// 双重检查锁模式(Double-Check-Lock) DCL懒汉式
public static TestLazyMan getInstance() {
// 一次检查 非同步
if (lazyMan == null) {
synchronized (TestLazyMan.class) {
// 二次检查 同步
if (lazyMan == null) {
lazyMan = new TestLazyMan();
}
}
}
return lazyMan;
}
}
class Test1 {
public static void main(String[] args) {
// 正常单线程获取实例,没问题
// TestLazyMan lazyMan = TestLazyMan.getInstance();
// 多线程 无法保证实例唯一
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TestLazyMan testLazyMan = TestLazyMan.getInstance();
System.out.println(testLazyMan);
}).start();
}
}
}
Thread-0
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
com.example.juc.TestLazyMan@22e7a307
通过反射获取
package com.example.juc;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestLazyMan {
private TestLazyMan() {
System.out.println(Thread.currentThread().getName());
}
private static TestLazyMan lazyMan;
// 双重检查锁模式(Double-Check-Lock) DCL懒汉式
public static TestLazyMan getInstance() {
// 一次检查 非同步
if (lazyMan == null) {
synchronized (TestLazyMan.class) {
// 二次检查 同步
if (lazyMan == null) {
lazyMan = new TestLazyMan();
}
}
}
return lazyMan;
}
}
class Test1 {
public static void main(String[] args) throws Exception {
// 多线程
for (int i = 0; i < 10; i++) {
new Thread(() -> {
TestHungryMan testHungryMan = TestHungryMan.getInstance();
System.out.println(testHungryMan);
}).start();
}
}
}
result
main
com.example.juc.TestLazyMan@681a9515...
main
com.example.juc.TestLazyMan@3af49f1c
com.example.juc.TestLazyMan@681a9515
静态内部类
特点:
- 能有效避免懒汉式的多线程单例失效问题
- 但是不能避免饿汉式的资源浪费问题
- 感觉没啥用
package com.example.juc;
public class TestStaticInner {
private TestStaticInner() {
}
public static TestStaticInner getInstance() {
return Inner.staticInner;
}
public static class Inner {
private static final TestStaticInner staticInner = new TestStaticInner();
}
}
安全问题
以上3种方法均为不安全的方法
以DCL懒汉式为例
通过反射创建实例会使单例模式失效
package com.example.juc;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
public class TestLazyMan {
private TestLazyMan() {
System.out.println(Thread.currentThread().getName());
}
private volatile static TestLazyMan lazyMan;
// 双重检查锁模式(Double-Check-Lock) DCL懒汉式
public static TestLazyMan getInstance() {
// 一次检查 非同步
if (lazyMan == null) {
synchronized (TestLazyMan.class) {
// 二次检查 同步
if (lazyMan == null) {
lazyMan = new TestLazyMan();
}
}
}
return lazyMan;
}
}
class Test1 {
public static void main(String[] args) throws Exception {
// 正常单线程获取实例,没问题
TestLazyMan lazyMan = TestLazyMan.getInstance();
System.out.println(lazyMan + "... ");
// 通过反射创建新实例获取
Constructor<TestLazyMan> declaredConstructor = TestLazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
TestLazyMan lazyMan1 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
// 通过反射方法获取
Method getInstance = TestLazyMan.class.getMethod("getInstance", null);
TestLazyMan invoke = (TestLazyMan) getInstance.invoke(lazyMan1);
System.out.println(invoke);
}
}
main
com.example.juc.TestLazyMan2@681a9515
main
com.example.juc.TestLazyMan2@3af49f1c
解决:构造函数里面进行判断,但是多个反射创建实例又会失效
package com.example.juc;
import java.lang.reflect.Constructor;
public class TestLazyMan2 {
private TestLazyMan2() {
synchronized (TestLazyMan.class) {
// 二次检查 同步
if (lazyMan != null) {
throw new RuntimeException("不能再次创建实例");
}
}
System.out.println(Thread.currentThread().getName());
}
private volatile static TestLazyMan2 lazyMan;
// 双重检查锁模式(Double-Check-Lock) DCL懒汉式
public static TestLazyMan2 getInstance() {
// 一次检查 非同步
if (lazyMan == null) {
synchronized (TestLazyMan.class) {
// 二次检查 同步
if (lazyMan == null) {
lazyMan = new TestLazyMan2();
}
}
}
return lazyMan;
}
}
class Test2 {
public static void main(String[] args) throws Exception {
// 正常单线程获取实例,没问题
// TestLazyMan2 lazyMan = TestLazyMan2.getInstance();
// System.out.println(lazyMan + "... ");
// 通过反射创建新实例获取
Constructor<TestLazyMan2> declaredConstructor = TestLazyMan2.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
TestLazyMan2 lazyMan1 = declaredConstructor.newInstance();
TestLazyMan2 lazyMan2 = declaredConstructor.newInstance();
System.out.println(lazyMan1);
System.out.println(lazyMan2);
}
}
道高一尺,魔高一丈
枚举类破解了反射的不安全
package com.example.juc;
import java.lang.reflect.Constructor;
public enum TestEnumSingle {
INSTANCE;
public TestEnumSingle getInstance() {
return INSTANCE;
}
public static void main(String[] args) throws Exception {
TestEnumSingle instance = TestEnumSingle.INSTANCE;
Constructor<TestEnumSingle> declaredConstructor = TestEnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
TestEnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
}
反编译源码
使用jad反编译工具 到\target\classes\com\example\juc目录下,运行命令
jad -sjava TestEnumSingle.class
可以看到TestEnumSingle类的构造器是有参构造器
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: TestEnumSingle.java
package com.example.juc;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
public final class TestEnumSingle extends Enum
{
public static TestEnumSingle[] values()
{
return (TestEnumSingle[])$VALUES.clone();
}
public static TestEnumSingle valueOf(String name)
{
return (TestEnumSingle)Enum.valueOf(com/example/juc/TestEnumSingle, name);
}
private TestEnumSingle(String s, int i)
{
super(s, i);
}
public TestEnumSingle getInstance()
{
return INSTANCE;
}
public static void main(String args[])
throws Exception
{
TestEnumSingle instance = INSTANCE;
Constructor declaredConstructor = com/example/juc/TestEnumSingle.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
TestEnumSingle instance2 = (TestEnumSingle)declaredConstructor.newInstance(new Object[0]);
System.out.println(instance);
System.out.println(instance2);
}
public static final TestEnumSingle INSTANCE;
private static final TestEnumSingle $VALUES[];
static
{
INSTANCE = new TestEnumSingle("INSTANCE", 0);
$VALUES = (new TestEnumSingle[] {
INSTANCE
});
}
}