Java中单例模式的使用

1.单例模式考点:

手写单例模式
线程安全的单例模式

2.什么是单例模式

单例类在整个程序中只能有一个实例,这个类负责创建自己的对象
比如:数据库连接池

3. 饿汉式:线程安全,反序列和反射不安全

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;
}

}
代码实现要点:
1. 私有构造器
2.持有该类的属性
3.对外提供获取实例的静态方法
优点:我们在获取对象的过程中,获取到的是一个对象,线程安全
/**
* 使用线程获取
*/
@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;
}

4. 登记式:线程安全,是一个加强版的饿汉式,防止反射攻击,反序列化安全,支持反序列化

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();
}
}

5. 枚举式:线程安全,防止反射攻击

package com.feicheng.bean;
/**
* 枚举式
* @author Lenovo
*/
public enum Singleton3 {
INSTANCE{
@Override
protected void doSomeThing() {
System.out.println("doSomeThing");
}
};

protected abstract void doSomeThing();

}

6.懒汉式:线程不安全,延迟加载,(两种加同步,效率低)

7. 双检索:线程安全,需要在类上添加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)初始化对象

8.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();
}
}
}

9. CAS:无锁乐观策略,线程安全

package com.feicheng.bean;
import java.util.concurrent.atomic.AtomicReference;

/**
* CAS
* @author Lenovo
*/
public class Singleton6 {

/**
* 对象引用,进行原子类封装
*/
private static final AtomicReference<Singleton6> 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();
}
}
}

10. 总结:

*饿汉式:线程安全、反射不安全、反序列化不安全
登记式:线程安全、防止反射攻击、反序列化不安全
枚举式:线程安全、支持反序列化、反序列化安全、防止反射攻击
懒汉式:线程不安全、延迟加载、(两种加同步,效率低)
*双检锁:线程安全、volatile
ThreadLocal:不加锁,以空间换时间,为每个线程提供变量独立的副本,可以保证各自线程中是单例的,但是不同线程之间不保证
CAS:无锁乐观策略,线程安全