单例模式:
1.手写单例模式
2.线程安全的单例模式
什么是单例
单例类在整个程序中只能有一个实例,这个类负责创建自己的对象,并确保只有一个对象创建。
比如:数据库连接池
饿汉式:线程安全,反射和序列化不安全
package com.feicheng.bean;
/**
-
饿汉式
-
@author Lenovo
*/
public class Singleton1 {/**
- 私有静态对象
*/
private static final Singleton1 instance = new Singleton1();
/**
- 私有构造器
*/
private Singleton1(){}
/**
-
对外提供获取实例的方法
-
@return
*/
public static Singleton1 getInstance(){return instance;
}
private Object readResolve(){
return instance;
}
- 私有静态对象
}
代码实现要点:
a)私有构造器
b)持有该类的属性
c)对外提供获取实例的静态方法
优点:我们在获取对象的过程中,获取到的是一个对象,线程安全
/**
-
使用线程获取
*/
@Test
public void test2(){for (int i = 0; i <20; i++) {
new Thread(new Runnable() { @Override public void run() { System.out.println(Singleton1.getInstance()); } }).start();
}
}
缺点:在反射和反序列化中是不安全的
/** -
对反射来讲,是不安全的
-
@throws Exception
*/
@Test
public void test3() throws Exception {Class clazz = Singleton1.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = (Singleton1) constructor.newInstance();
System.out.println(singleton1 == singleton2);
}
在序列化中需要加上
private Object readResolve(){return instance;
}
登记式:线程安全,是一个加强版的饿汉式,防止反射攻击,反序列化安全,支持反序列化
package com.feicheng.bean;
/**
-
登记式
-
@author Lenovo
*/
public class Singleton2 {/**
-
私有静态内部类
*/
private static class SingletonHolder{private static Singleton2 instance = new Singleton2();
}
/**
-
私有构造器
*/
private Singleton2(){System.out.println(“Singleton reload”);
if (SingletonHolder.instance != null){
throw new IllegalStateException();
}
}
/**
-
对外提供获取实例的方法
-
@return
*/
public static Singleton2 getInstance(){return SingletonHolder.instance;
}
}
测试方法:
package com.feicheng.episode;
-
import com.feicheng.bean.Singleton1;
import com.feicheng.bean.Singleton2;
import org.junit.Test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Singleton2Test {
@Test
public void test1(){
// Singleton2 singleton1 = Singleton2.getInstance();
//
// Singleton2 singleton2 = Singleton2.getInstance();
//
// System.out.println(singleton1 == singleton2);
/**
* 这样不会加载构造方法
*/
try {
Class.forName("com.feicheng.bean.Singleton2");
// 只有加上这个才在加载构造方法
Singleton2.getInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
/**
* 使用反射,也是不安全的
*/
@Test
public void test2() throws Exception {
Class clazz = Singleton2.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton2 singleton1 = Singleton2.getInstance();
Singleton2 singleton2 = (Singleton2) constructor.newInstance();
System.out.println(singleton1 == singleton2);
}
/**
* 对反射来讲,是不安全的,
* @throws Exception
*/
@Test
public void test3() throws Exception {
Class clazz = Singleton1.class;
Constructor constructor = clazz.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = (Singleton1) constructor.newInstance();
System.out.println(singleton1 == singleton2);
}
}
要想反射安全需要在类里面构造方法加上:
/**
-
私有构造器
*/
private Singleton2(){System.out.println(“Singleton reload”);
if (SingletonHolder.instance != null){
throw new IllegalStateException();
}
}
枚举式:线程安全,防止反射攻击
package com.feicheng.bean;
/**
-
枚举式
-
@author Lenovo
*/
public enum Singleton3 {INSTANCE{
@Override
protected void doSomeThing() {System.out.println("doSomeThing"); }
};
protected abstract void doSomeThing();
}
懒汉式:线程不安全,延迟加载,(两种加同步,效率低)
双检索:线程安全,需要在类上添加Volatile
package com.feicheng.bean;
/**
-
懒汉式
-
@author Lenovo
*/
public class Singleton4 {/**
- 私有静态对象
*/
private static volatile Singleton4 instance = null;
/**
- 私有构造器
*/
private Singleton4(){}
/**
-
对外提供获取实例的方法
-
@return
*/
public static Singleton4 getInstance(){if (instance == null){
return new Singleton4();
}
return instance;
}
// 若要使用懒汉式线程安全,则需要将构造方法重写成这样
/**- 使用双检索的形式
- @return
*/
public static Singleton4 getInstanceSy(){
if (instance == null){
synchronized (Singleton4.class){
if (instance == null){
return new Singleton4();
}
}
}
return instance;
}
// 但这种方式是是不是100%线程安全呢?需要在对象前面加一个Volatile,这样就不会执行指令重排
}
若执行instance = new Singleton4()会执行如下操作
(1)分配对象内存空间
(2)初始化对象
(3)instance指向(1)中分配的空间
在某些编译器上,可能会出现指令重排:
(1)分配对象内存空间
(2)instance指向(1)中分配的空间(但此时对象没有被初始化)
(3)初始化对象
- 私有静态对象
ThreadLocal:在一个线程里面是单列的,但在不同的线程里面是多列的
不加锁,以空间换时间,为每个线程提供变量的独立副本,可以保证各自线程中是单例的,但不同线程中不保证
package com.feicheng.bean;
/**
-
ThreadLocal
-
@author Lenovo
*/
public class Singleton5 {private static Singleton5 instance = null;
private Singleton5(){}
private static final ThreadLocal < Singleton5> threadLocal = new ThreadLocal < Singleton5>(){
@Override
protected Singleton5 initialValue() {return new Singleton5(); }
};
public static Singleton5 getInstance(){
return threadLocal.get();
}
}
测试方法:
package com.feicheng.episode;
import com.feicheng.bean.Singleton4;
import com.feicheng.bean.Singleton5;
import org.junit.Test;
import java.io.IOException;
public class Singleton5Test {
@Test
public void test1(){
Singleton5 singleton1 = Singleton5.getInstance();
Singleton5 singleton2 = Singleton5.getInstance();
System.out.println(singleton1 == singleton2);
}
@Test
public void test2(){
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Singleton5 singleton1 = Singleton5.getInstance();
Singleton5 singleton2 = Singleton5.getInstance();
System.out.println(Thread.currentThread().getName() + "----" + (singleton1 == singleton2) + "---" + singleton1 + "," + singleton2);
}
}).start();
}
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
CAS:无锁乐观策略,线程安全
package com.feicheng.bean;
import java.util.concurrent.atomic.AtomicReference;
/**
-
CAS
-
@author Lenovo
*/
public class Singleton6 {/**
- 对象引用,进行原子类封装
*/
private static final AtomicReference instance = new AtomicReference < Singleton6>();
/**
-
私有化构造方法
*/
private Singleton6(){System.out.println(“Singleton6”);
};
public static final Singleton6 getInstance() {
for (; ; ) { Singleton6 singleton6 = instance.get(); if (singleton6 != null) { return singleton6; } singleton6 = new Singleton6(); if (instance.compareAndSet(null, singleton6)) { return singleton6; } }
}
}
测试方法:
package com.feicheng.episode; - 对象引用,进行原子类封装
import com.feicheng.bean.Singleton5;
import com.feicheng.bean.Singleton6;
import org.junit.Test;
import java.io.IOException;
public class Singleton6Test {
@Test
public void test1(){
for (int i = 0; i < 20; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton6.getInstance());
}
}).start();
}
try {
System.in.read();
} catch (IOException e) {
e.printStackTrace();
}
}
}
总结:
*饿汉式:线程安全、反射不安全、反序列化不安全
登记式:线程安全、防止反射攻击、反序列化不安全
枚举式:线程安全、支持反序列化、反序列化安全、防止反射攻击
懒汉式:线程不安全、延迟加载、(两种加同步,效率低)
* 双检锁:线程安全、volatile
ThreadLocal:不加锁,以空间换时间,为每个线程提供变量独立的副本,可以保证各自线程中是单例的,但是不同线程之间不保证
CAS:无锁乐观策略,线程安全