public class Test {
public static void main(String[] args) {
LazySingleton.getInstance();
}
}
//懒汉式单例
class LazySingleton{
private static LazySingleton instance= null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance==null){
instance= new LazySingleton();
}
return instance;
}
}
上面的懒汉式有什么缺点呢?
首先就是线程不安全,如果在多线程的情况下,首先线程一完成判断,但是还没有实例化对象时,线程二进入判断,此时线程一完成实例化,而线程二也会再次实例化,从而破坏单例。
解决方法是:加锁
public synchronized static LazySingleton getInstance(){
if (instance==null){
instance= new LazySingleton();
}
return instance;
}
这个单例又有什么缺点呢?
如果锁的是静态方法,那么相当于锁的整个类,比较消耗内存资源。因此引入双重检查机制。
public static LazySingleton getInstance(){
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
这个单例的优点就是锁的范围小了。降低了内存开销。
那么它真的完美了吗?其实还是有点小小隐患,那就是在实例化对象的时候发生重排序(在java的语言规范中是允许单线程进行重排序的,增加效率,但是在多线程中就会存在隐患),这时就需要引入我们的volatile关键字,来避免重排序。
class LazySingleton{
//加上volatile关键字,使的所有线程都能看到内存状态,保证内存的可见性。
private volatile static LazySingleton instance = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
使用了双重检查和volatile关键字之后,在性能和线程安全方面都得到了满足~~
public class Test {
public static void main(String[] args) {
HungrySingleton.getInstance();
}
}
//饿汉式
class HungrySingleton{
//final声明的变量必须在类加载完成的时候就赋值
private final static HungrySingleton instance;
static {
instance = new HungrySingleton();
}
private HungrySingleton(){
}
public static HungrySingleton getInstance(){
return instance;
}
}
饿汉式有什么优点呢?
那就是在类加载的时候就已经初始化了,避免了线程同步的问题,当然缺点也就是没了延迟加载的特性,如果没有使用过,就会造成资源的浪费。
还有一种基于静态内部类的单例模式,由于类初始化的延迟加载(只有一个线程会获得这个静态内部类对象的初始化锁),会使得线程2在线程1初始化类对象的时候看不到静态内部类中的重排序。
话不多说,直接上Demo吧。
public class StatIcinnerSingleton {
private StatIcinnerSingleton(){
}
private static class InnerClass{
private static StatIcinnerSingleton instance = new StatIcinnerSingleton();
}
public static StatIcinnerSingleton getInstance(){
return InnerClass.instance;
}
}
public class Test {
public static void main(String[] args) throws Exception {
LazySingleton instance = LazySingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("file"));
oos.writeObject(instance);
File f = new File("file");
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f));
LazySingleton newInstance = (LazySingleton) ois.readObject();
System.out.println(instance==newInstance); //结果为false
}
}
//懒汉式单例
class LazySingleton implements Serializable{
private volatile static LazySingleton instance = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
console打印结果为false,单例已经被破坏了~
如何解决呢?
//在单例类中加入这个方法即可
private Object readResolve(){
return instance;
}
它的作用是不管序列化重新实例化对象没有,都会返回指定的这个对象。
public class Test {
public static void main(String[] args) throws Exception {
Class c = LazySingleton.class;
Constructor constructor = c.getDeclaredConstructor();
constructor.setAccessible(true);
//正常单例
LazySingleton instance = LazySingleton.getInstance();
LazySingleton newInstance = (LazySingleton) constructor.newInstance();
System.out.println(instance==newInstance); //打印false
}
}
//懒汉式单例
class LazySingleton implements Serializable{
private volatile static LazySingleton instance = null;
private LazySingleton(){
}
public static LazySingleton getInstance(){
if (instance==null){
synchronized (LazySingleton.class){
if (instance==null){
instance = new LazySingleton();
}
}
}
return instance;
}
}
console打印false,单例又被破坏了~,因为通过反射打开了构造器的权限(constructor.setAccessible(true))。
那又如何解决呢?
对于饿汉式与静态内部类单例(因为在类加载的时候就已经创建了本类对象),可以在构造器中加入反射拦截代码即可(判断对象如果不为空则抛出异常)。
至于懒汉式嘛,现在还没有想到如何防止反射攻击(汗。。。),那位大佬路过,可以讨论下~