单例模式
不再手动创建对象,通过调用方法获取全局唯一的实例.
饿汉式
类一加载,实例就被创建,且只创建一次,线程安全,高效,但长时间不使用,浪费资源,
public class Hungry {
// 可能会浪费空间
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 Hungry(){
}
private static Hungry HUNGRY = new Hungry();
//返回单例对象
public static Hungry getInstance(){
return HUNGRY;
}
}
懒汉式
public class LazyTest {
private static LazyTest instance = null;
private LazyTest() {
}
//效率较低
public static LazyTest getInstance1() {
if (instance == null) {
instance = new LazyTest();
}
return instance;
}
}
延迟加载,节省资源,但线程不安全,可能创建出多个对象.
优化:加锁
- 方法同步,线程安全,但效率低
- 同步代码块,只锁new语句,效率稍高,但仍存在线程安全
- DCL双重检测锁定
DCL 懒汉式
DCL(double-checked-locking)双重检测锁定
public class LazyTest {
//volatile 禁止指令重排
private static volatile LazyTest instance = null;
private LazyTest(){
}
public static LazyTest getInstance2(){
if(instance == null){
synchronized (LazyTest.class){
if(instance == null){
instance = new LazyTest();
}
}
}
return instance;
}
}
- 如果对象已存在,就直接返回对象,不会进行锁判定,比方法锁效率高.
- new 创建对象不是一个原子性操作,可能发生指令重排.例如:线程A执行了13还没构造,线程B这时if判断不为null但对象还没初始化,就会发生问题.所以要使用volatile禁止指令重排.
- 1.分配内存空间
- 2.执行构造方法,初始化对象
- 3.把这个对象指向这个空间
- 使用反射可以破坏DCL懒汉式的单例
//一个使用getInstance方法创建对象,一个使用反射创建对象
public static void main(String[] args) throws Exception {
LazyTest instance = LazyTest .getInstance();
Constructor<LazyTest > declaredConstructor =LazyTest .class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyTest instance2 = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(instance2);
}
解决: 在构造函数中判断是否已经创建过对象
private LazyTest(){
synchronized (LazyTest.class){
if(instance != null){
throw new RuntimeException("不要试图使用反射创建对象");
}
}
}
但这时还是可以使用来反射创建两个对象.
解决:使用信号量来判断
private static boolean flog = false;
private LazyTest(){
synchronized (LazyTest.class){
if (flog == false){
flog = true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
如果逆向分析出信号量仍然可以创建两个对象.这时只能使用枚举类,因为枚举不能使用反射的newInstance()创建对象.
枚举式
因为枚举构造函数只会执行一次的特性,所以其本身就是单例的.
public class EnumTest {
private EnumTest() {
}
public static EnumTest getInstance(){
return Test.INSTANCE.instance;
}
//通过枚举类创建对象
private enum Test{
INSTANCE; //通过INSTANCE调用枚举类的方法变量
private EnumTest instance=null;
//枚举类不允许声明 public的构造函数
Test() {
instance = new EnumTest();
}
}
}