(1)单例模式
【单例模式(Singleton Pattern):】
定义:
Ensure a class has only one instance, and provide a global point of access to it.
直译:确保一个类只有一个实例,并提供对它的全局访问点(只允许通过全局访问点获取实例对象)。
(2)单例模式实现要点
一般情况下,访问类中某变量、方法:
可以通过 new 进行对象实例化,再通过 "对象名.变量名"、"对象名.方法名" 的形式获取。
可以通过 static 修饰(全局)变量、方法,再通过 "类名.变量名"、"类名.方法名" 的形式获取。可避免使用 new 进行对象实例化。
为了保证一个类只存在一个实例,应该保证其有且只有 一次 实例化 机会:
应该保证其构造方法不能在该类以外的地方被调用(防止使用 new 进行对象实例化)。
构造方法只能在该类中被调用一次。
注:
对象实例化常见方式: new、序列化、克隆、反射。
基本实现要点:
构造方法私有化(防止使用 new 进行对象实例化)。
在类的内部进行一次实例化(构造方法只能在该类中被调用一次)。
对外提供一个全局访问点(全局变量、全局方法等),可以通过 "类名.变量名" 或者 "类名.方法名" 的形式获取实例对象(避免使用 new 进行对象实例化)。
注:
反射会破坏 构造方法的私有化,需要注意,后面会介绍。
序列化、克隆 等操作可能会破坏单例模式。需要注意。
(3)使用场景
当频繁创建、销毁某个对象时,可以考虑单例模式。
当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。
(1)实现方式
【饿汉式:】
静态变量
静态代码块
枚举(推荐)
【懒汉式:】
静态方法
synchronized 同步方法
synchronized 同步代码块
双重检查
静态内部类
(2)饿汉式、懒汉式 区别
【基本区别:】
懒汉式 在需要使用对象的时候才进行实例化操作。
饿汉式 在类加载时完成实例化操作,可能暂时还不用该对象(占用内存)。
【饿汉式:】
核心:
饿汉式借助 JVM 的类加载机制,在 类加载的初始化阶段 完成 实例化操作。
类初始化阶段 只会执行一次,从而保证实例的唯一性 以及 线程安全。
当类被主动使用时,才会导致类的初始化。而被动使用时,不会导致类的初始化。
主动使用类的方式:
类的 main 方法被调用时。
执行 new 实例化操作时。
访问静态变量、静态方法时。
实例化子类时(先触发父类初始化)。
反射调用某类时。
JVM 类加载过程可参考: https://www.cnblogs.com/l-y-h/p/13496969.html#_label1_5
【懒汉式:】
核心:
在需要使用对象的时候才进行实例化操作。
多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。
(1)基本说明
【核心思路:】
使用 static 关键字,借助类加载过程,进行实例的初始化。
使用 private 修饰 构造方法,保证构造方法私有化。
提供一个全局访问点(类名.变量名 或者 类名.方法名)获取对象。
【可用方式:】
静态变量
静态方法
静态代码块
【优点:】
在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
【缺点:】
在类加载的初始化阶段完成了实例化,没有实现懒加载(Lazy Loading),可能造成内存的浪费(在不需要使用的时候被创建)。
(2)代码实现(静态变量)
public 修饰变量,直接通过 "类名.变量名" 的方式获取对象。
class HungrySingleton {
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton singleton = new HungrySingleton();
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
}
}
public class Test {
public static void main(String[] args) {
HungrySingleton singleton = HungrySingleton.singleton;
HungrySingleton singleton2 = HungrySingleton.singleton;
System.out.println(singleton == singleton2); // true,为同一个对象
}
}
(3)代码实现(静态方法)
private 修饰变量,不允许通过 "类名.变量名" 的形式访问。
public 修饰方法,通过 "类名.方法名" 的方式获取对象。
class HungrySingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton = new HungrySingleton();
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) {
HungrySingleton singleton = HungrySingleton.getInstance();
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // true,为同一个对象
}
}
(4)代码实现(静态代码块)
静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。
class HungrySingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
// 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
static {
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) {
HungrySingleton singleton = HungrySingleton.getInstance();
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // true,为同一个对象
}
}
(5)这就完了吗?
当然不是了,这样写只是防止了通过 new 实例化对象。
对象实例化的方式还有 反射、序列化、克隆 等操作。
这些操作是否会破坏单例模式?需要思考一下。
(1)类主动使用时,才会进行类的初始化
类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
比如:
进行反射获取私有构造方法时,并不会触发 类加载过程。
如下代码执行后,静态代码块中的 "start..." 不会输出。
import java.lang.reflect.Constructor;
class HungrySingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
// 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
static {
System.out.println("start...");
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
hungrySingletonConstructor.setAccessible(true);
}
}
(2)反射破坏
如下代码所示,反射调用构造方法时,会进行类加载过程(输出 "start..." ),然后构建一个实例。
此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
即 反射对 单例模式造成了破坏。
import java.lang.reflect.Constructor;
class HungrySingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
// 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
static {
System.out.println("start...");
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
hungrySingletonConstructor.setAccessible(true); // 此时,还不会触发 类加载过程
HungrySingleton singleton = hungrySingletonConstructor.newInstance(); // 此时,触发 类加载过程,并创建一个实例
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // false,不为同一个对象
}
}
(3)防止反射破坏(未必会生效)
在构造方法中,判断实例是否已经被创建。
类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。
import java.lang.reflect.Constructor;
class HungrySingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
// 在 static 代码块中进行实例化,同样在 类加载初始化阶段 执行
static {
System.out.println("start...");
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
if (singleton != null) {
throw new RuntimeException("实例已存在,不允许重复创建");
}
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Class<HungrySingleton> hungrySingletonClass = HungrySingleton.class;
Constructor<HungrySingleton> hungrySingletonConstructor = hungrySingletonClass.getDeclaredConstructor();
hungrySingletonConstructor.setAccessible(true);
HungrySingleton singleton = hungrySingletonConstructor.newInstance();
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // true,为同一个对象
}
}
(1)序列化、反序列化
序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
注:
序列化就是把实体对象状态按照一定的格式写入到有序字节流。
反序列化就是从有序字节流重建对象,恢复对象状态。
【反序列化核心代码:】
ObjectInputStream 中的 readOrdinaryObject() 方法
private Object readOrdinaryObject(boolean unshared) throws IOException {
Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(desc.forClass().getName(), "unable to create instance").initCause(ex);
}
if (obj != null && handles.lookupException(passHandle) == null && desc.hasReadResolveMethod()) {
Object rep = desc.invokeReadResolve(obj);
if (rep != obj) {
handles.setObject(passHandle, obj = rep);
}
}
return obj;
}
【关注点一:(调用构造函数实例化)】
desc.isInstantiable()
如果一个 serializable/externalizable 的类可以在运行时被实例化,那么该方法就返回true。
desc.newInstance()
通过反射调用无参构造创建一个对象。
注:
此处调用的无参构造,与类本身的无参构造方法有差别。
从实际效果上看,此处仅触发了类加载,并未触发类的构造函数。与前面提到的反射有区别。
没有深入研究,有兴趣的可以帮忙解答一下。
【关注点二:(自定义对象生成策略)】
desc.hasReadResolveMethod()
如果一个 serializable/externalizable 接口的类中包含 readResolve() 方法,则返回 true。
desc.invokeReadResolve(obj)
通过反射的方式调用要被反序列化的类的 readResolve() 方法。
handles.setObject(passHandle, obj = rep)
如果 readResolve() 返回的实例与构造方法创建的不同,则以 readResolve() 方法创建的实例为准。
(2)反序列化破坏
如下代码所示,通过反序列化创建了个对象。
从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
即 反序列化对 单例模式造成了破坏。
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
class HungrySingleton implements Serializable {
private static final long serialVersionUID = 42L;
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
static {
System.out.println("start..."); // start...
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
if (singleton != null) {
throw new RuntimeException("实例已存在,不允许重复创建");
}
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
// oos.writeObject(HungrySingleton.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
HungrySingleton singleton = (HungrySingleton) ois.readObject();
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // false,不为同一对象
}
}
(3)防止反序列化破坏
通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。
import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
class HungrySingleton implements Serializable {
private static final long serialVersionUID = 42L;
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
static {
System.out.println("start..."); // start...
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
if (singleton != null) {
throw new RuntimeException("实例已存在,不允许重复创建");
}
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
// 定义 readResolve() 方法,返回类加载过程中创建的实例对象(反序列化时返回此对象)
private Object readResolve() {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
// ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
// oos.writeObject(HungrySingleton.getInstance());
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
HungrySingleton singleton = (HungrySingleton) ois.readObject();
HungrySingleton singleton2 = HungrySingleton.getInstance();
System.out.println(singleton == singleton2); // true,为同一对象
}
}
(1)克隆破坏
如下代码所示,通过克隆创建了个对象。
调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
即 克隆对 单例模式造成了破坏。
class HungrySingleton implements Cloneable {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
static {
System.out.println("start..."); // start...
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
if (singleton != null) {
throw new RuntimeException("实例已存在,不允许重复创建");
}
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
// 重写 clone() 方法,返回 clone 对象
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
public class Test {
public static void main(String[] args) throws Exception {
HungrySingleton singleton = HungrySingleton.getInstance();
HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
System.out.println(singleton == singleton2); // false,不是同一对象
}
}
(2)防止克隆破坏
保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。
class HungrySingleton implements Cloneable {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static HungrySingleton singleton;
static {
System.out.println("start..."); // start...
singleton = new HungrySingleton();
}
// 构造器私有化(防止通过new创建实例对象)
private HungrySingleton() {
if (singleton != null) {
throw new RuntimeException("实例已存在,不允许重复创建");
}
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static HungrySingleton getInstance() {
return singleton;
}
// 重写 clone() 方法,返回 clone 对象
@Override
public Object clone() throws CloneNotSupportedException {
return singleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
HungrySingleton singleton = HungrySingleton.getInstance();
HungrySingleton singleton2 = (HungrySingleton) singleton.clone();
System.out.println(singleton == singleton2); // true,是同一对象
}
}
(1)基本说明
【基本说明:】
写个简单的 enum 类,然后反编译一下 javap -c xx.class。
可以看到底层就类似于 饿汉式 静态代码块 的写法。在类加载的初始化阶段完成实例化操作。
【优点:】
在类加载的初始化阶段完成了实例化,仅加载一次。保证对象的唯一性 以及 线程安全。
可以防止 克隆、反序列化、反射 破坏单例模式。
写法简单。
(2)反编译一下 enum 类
【EnumSingleton】
enum EnumSingleton {
INSTANCE;
}
【javap -c EnumSingleton.class】
final class pattern.sington.EnumSingleton extends java.lang.Enum<pattern.sington.EnumSingleton> {
public static final pattern.sington.EnumSingleton INSTANCE;
public static pattern.sington.EnumSingleton[] values();
Code:
0: getstatic #1 // Field $VALUES:[Lpattern/sington/EnumSingleton;
3: invokevirtual #2 // Method "[Lpattern/sington/EnumSingleton;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lpattern/sington/EnumSingleton;"
9: areturn
public static pattern.sington.EnumSingleton valueOf(java.lang.String);
Code:
0: ldc #4 // class pattern/sington/EnumSingleton
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class pattern/sington/EnumSingleton
9: areturn
static {};
Code:
0: new #4 // class pattern/sington/EnumSingleton
3: dup
4: ldc #7 // String INSTANCE
6: iconst_0
7: invokespecial #8 // Method "<init>":(Ljava/lang/String;I)V
10: putstatic #9 // Field INSTANCE:Lpattern/sington/EnumSingleton;
13: iconst_1
14: anewarray #4 // class pattern/sington/EnumSingleton
17: dup
18: iconst_0
19: getstatic #9 // Field INSTANCE:Lpattern/sington/EnumSingleton;
22: aastore
23: putstatic #1 // Field $VALUES:[Lpattern/sington/EnumSingleton;
26: return
}
【等价于:】
public final class EnumSingleton extends Enum< EnumSingleton> {
public static final EnumSingleton INSTANCE;
public static EnumSingleton[] values();
public static EnumSingleton valueOf(String s);
static {
INSTANCE = new EnumSingleton(name, ordinal);
};
}
(3)防止反射破坏
枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。
【代码实现:】
import java.lang.reflect.Constructor;
enum EnumSingleton {
INSTANCE;
}
public class Test {
public static void main(String[] args) throws Exception {
Class<EnumSingleton> enumSingletonClass = EnumSingleton.class;
Constructor<EnumSingleton> enumSingletonConstructor = enumSingletonClass.getDeclaredConstructor(String.class, int.class);
enumSingletonConstructor.setAccessible(true);
// newInstance 会出现异常,java.lang.IllegalArgumentException: Cannot reflectively create enum objects
EnumSingleton enumSingleton = enumSingletonConstructor.newInstance();
EnumSingleton enumSingleton2 = EnumSingleton.INSTANCE;
System.out.println(enumSingleton == enumSingleton2);
}
}
【原因分析:】
newInstance() 方法中进行判断,若为枚举类型,则抛异常。
@CallerSensitive
public T newInstance(Object ... initargs) throws IllegalArgumentException
{
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
}
(4)防止克隆破坏
枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
(5)防止序列化破坏
序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。
【代码实现:】
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
enum EnumSingleton {
INSTANCE;
}
public class Test {
public static void main(String[] args) throws Exception {
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
oos.writeObject(enumSingleton);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
EnumSingleton enumSingleton2 = (EnumSingleton) ois.readObject();
System.out.println(enumSingleton == enumSingleton2); // true,是同一个对象
}
}
【反序列化核心代码:】
ObjectInputStream 中的 readEnum() 方法。
读入并返回枚举常量,如果枚举类型不可解析,则返回 null。
private Enum<?> readEnum(boolean unshared) throws IOException {
ObjectStreamClass desc = readClassDesc(false);
int enumHandle = handles.assign(unshared ? unsharedMarker : null);
String name = readString(false);
Enum<?> result = null;
Class<?> cl = desc.forClass();
if (cl != null) {
try {
@SuppressWarnings("unchecked")
Enum<?> en = Enum.valueOf((Class)cl, name);
result = en;
} catch (IllegalArgumentException ex) {
throw (IOException) new InvalidObjectException(
"enum constant " + name + " does not exist in " +
cl).initCause(ex);
}
if (!unshared) {
handles.setObject(enumHandle, result);
}
}
handles.finish(enumHandle);
passHandle = enumHandle;
return result;
}
(1)基本说明
【核心思路:】
使用 private 修饰 构造方法,保证构造方法私有化。
提供一个静态的公共方法,在调用该方法时,才去创建实例对象。(全局访问点,通过 "类名.方法名" 获取对象)。
【可用方式:】
静态方法
synchronized 同步方法
synchronized 同步代码块
双重检查
静态内部类
【优点:】
懒加载,需要使用对象时才会去实例化操作,提高内存利用率。
【缺点:】
多线程环境下,多个线程可能同时使用对象,需要考虑线程安全问题,防止并发访问生成多个实例。
(2)代码实现(静态方法)
如下代码所示,只允许通过 "类名.方法名" 的方式获取对象。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,通过 "类名.变量名" 访问
public static FullSingleton getInstance() {
if (fullSingleton == null) {
fullSingleton = new FullSingleton();
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
FullSingleton fullSingleton = FullSingleton.getInstance();
FullSingleton fullSingleton2 = FullSingleton.getInstance();
System.out.println(fullSingleton == fullSingleton2); // true,是同一个对象
}
}
(3)这就完了吗?
当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
此时单例模式被破坏,线程不安全。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
if (fullSingleton == null) {
fullSingleton = new FullSingleton();
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@f3f9f4b
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@26af6bb1
}
});
thread.start();
thread2.start();
}
}
(4)代码实现(synchronized 同步方法)
为了保证线程安全,可以使用 synchronized 关键字实现同步。
注:
synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
public static synchronized FullSingleton getInstance() {
if (fullSingleton == null) {
fullSingleton = new FullSingleton();
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@40788638
}
});
thread.start();
thread2.start();
}
}
(5)这就完了吗?
当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。
(6)代码实现(synchronized 同步代码块)
如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
if (fullSingleton == null) {
synchronized(FullSingleton.class) {
fullSingleton = new FullSingleton();
}
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@5eb39c2b
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@401a5cff
}
});
thread.start();
thread2.start();
}
}
(7)这就完了吗?
当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。
(1)代码实现
如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
if (fullSingleton == null) {
synchronized(FullSingleton.class) {
if (fullSingleton == null) {
fullSingleton = new FullSingleton();
}
}
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance());
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance());
}
});
thread.start();
thread2.start();
}
}
(2)这就完了吗?
当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
如下所示,反编译一下代码,可以看到实例化操作的相关指令。
【Test.java】
public class Test {
public static void main(String[] args) {
Test test = new Test();
}
}
【javap -c Test.class】
public class pattern.sington.Test {
public pattern.sington.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: new #2 // class pattern/sington/Test
3: dup
4: invokespecial #3 // Method "<init>":()V
7: astore_1
8: return
}
【关注 main 函数:】
new 指令在 堆内存中为 Test 对象分配内存空间。
invokespecial 指令,执行实例初始化操作。
astore_1 指令,将栈顶引用类型值存入变量(即 使对象指向 堆内存空间)。
即分为三步:
1、分配内存空间。
2、实例初始化
3、实例指向内存空间
注:
按照常理说,1、2、3 是按照顺序执行的。
但是 JVM 会根据处理器特性,对指令进行优化(指令重排序),从而提高性能。
指令重排,意味着指令可能不会按照指定顺序执行。
【回到上例的 双重检查的代码:】
发生指令重排,new 实例化操作按照 1、3、2 的顺序执行。
假设线程 A 执行完 1、3,但 2 还未执行完,即对象已指向内存空间,但是还没有初始化。
此时线程 B 执行 getInstance() 代码,由于对象已指向内存空间,判断对象是否为 null 时返回 false, 跳过 synchronized 代码块。
此时线程 B 拿到的实例对象,由于初始化并未完成,使用对象将可能出现错误(引用逃逸)。
注:
synchronized 并非原子性操作,可能发生指令重排。
使用 voliate 可以通过 内存屏障 禁止指令重排序。
(3)代码实现(voliate )
使用 voliate 修饰 变量,禁止指令重排序。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
// volatile 防止指令重排
private static volatile FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去检查并创建一个实例对象。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
if (fullSingleton == null) {
synchronized(FullSingleton.class) {
if (fullSingleton == null) {
fullSingleton = new FullSingleton();
}
}
}
return fullSingleton;
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@79af4c1c
}
});
thread.start();
thread2.start();
}
}
(1)基本说明
静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。
【核心思路:】
使用 private 修饰 构造方法,保证构造方法私有化。
在类的内部定义一个静态内部类(只有被调用时,才会被加载),并在内部类中实例化对象。
提供一个静态的公共方法,在调用该方法时,调用静态内部类。(全局访问点,通过 "类名.方法名" 获取对象)。
【优点:】
定义内部类,只有在用到的时候才回去加载,实现懒加载。
使用 static 定义内部类,利用 JVM 类加载机制保证 线程安全。
(2)代码实现
如下代码所示,定义一个静态内部类。
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
return InnerInstance.INSTANCE;
}
// 定义静态内部类
private static class InnerInstance {
public static final FullSingleton INSTANCE = new FullSingleton();
}
}
public class Test {
public static void main(String[] args) throws Exception {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println(FullSingleton.getInstance()); // pattern.sington.FullSingleton@2a75ae17
}
});
thread.start();
thread2.start();
}
}
(3)这就完了吗?
当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
解决方式与 饿汉模式的解决方式类似。
(4)防止序列化破坏
重写 readResolve() 方法,返回实例对象。
import java.io.*;
class FullSingleton implements Serializable {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
return InnerInstance.INSTANCE;
}
// 定义静态内部类
private static class InnerInstance {
public static final FullSingleton INSTANCE = new FullSingleton();
}
private Object readResolve() {
return getInstance();
}
}
public class Test {
public static void main(String[] args) throws Exception {
FullSingleton fullSingleton = FullSingleton.getInstance();
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test"));
oos.writeObject(fullSingleton);
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test"));
FullSingleton fullSingleton2 = (FullSingleton) ois.readObject();
System.out.println(fullSingleton == fullSingleton2);
}
}
(5)防止克隆破坏
重写 clone() 方法,返回实例对象。
class FullSingleton implements Cloneable {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
}
// 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
return InnerInstance.INSTANCE;
}
// 定义静态内部类
private static class InnerInstance {
public static final FullSingleton INSTANCE = new FullSingleton();
}
@Override
public Object clone() throws CloneNotSupportedException {
return getInstance();
}
}
public class Test {
public static void main(String[] args) throws Exception {
FullSingleton fullSingleton = FullSingleton.getInstance();
FullSingleton fullSingleton2 = (FullSingleton) fullSingleton.clone();
System.out.println(fullSingleton == fullSingleton2);
}
}
(6)防止反射破坏
在构造方法中,新增一个判断。
import java.lang.reflect.Constructor;
class FullSingleton {
// 私有化变量,不可以通过 "类名.变量名" 的形式访问
private static FullSingleton fullSingleton;
// 构造器私有化(防止通过new创建实例对象)
private FullSingleton () {
if (getInstance() != null) {
throw new RuntimeException("实例已存在,创建失败");
}
}
// 提供一个全局访问点,当调用该方法时,才去调用静态内部类。通过 "类名.方法名" 访问
public static FullSingleton getInstance() {
return InnerInstance.INSTANCE;
}
// 定义静态内部类
private static class InnerInstance {
public static final FullSingleton INSTANCE = new FullSingleton();
}
}
public class Test {
public static void main(String[] args) throws Exception {
Class<FullSingleton> fullSingletonClass = FullSingleton.class;
Constructor<FullSingleton> fullSingletonConstructor = fullSingletonClass.getDeclaredConstructor();
fullSingletonConstructor.setAccessible(true);
FullSingleton fullSingleton = fullSingletonConstructor.newInstance();
FullSingleton fullSingleton2 = FullSingleton.getInstance();
System.out.println(fullSingleton == fullSingleton2);
}
}
(1)部分源码
如下代码所示,就是 饿汉模式的 实现。
public class Runtime {
private static Runtime currentRuntime = new Runtime();
public static Runtime getRuntime() {
return currentRuntime;
}
private Runtime() {}
}
我有一个模型:classItem项目有一个属性“商店”基于存储的值,我希望Item对象对特定方法具有不同的行为。Rails中是否有针对此的通用设计模式?如果方法中没有大的if-else语句,这是如何干净利落地完成的? 最佳答案 通常通过Single-TableInheritance. 关于ruby-on-rails-Rails-子类化模型的设计模式是什么?,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.co
我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i
鉴于我有以下迁移:Sequel.migrationdoupdoalter_table:usersdoadd_column:is_admin,:default=>falseend#SequelrunsaDESCRIBEtablestatement,whenthemodelisloaded.#Atthispoint,itdoesnotknowthatusershaveais_adminflag.#Soitfails.@user=User.find(:email=>"admin@fancy-startup.example")@user.is_admin=true@user.save!ende
给定一个复杂的对象层次结构,幸运的是它不包含循环引用,我如何实现支持各种格式的序列化?我不是来讨论实际实现的。相反,我正在寻找可能会派上用场的设计模式提示。更准确地说:我正在使用Ruby,我想解析XML和JSON数据以构建复杂的对象层次结构。此外,应该可以将该层次结构序列化为JSON、XML和可能的HTML。我可以为此使用Builder模式吗?在任何提到的情况下,我都有某种结构化数据-无论是在内存中还是文本中-我想用它来构建其他东西。我认为将序列化逻辑与实际业务逻辑分开会很好,这样我以后就可以轻松支持多种XML格式。 最佳答案 我最
了解Rails缓存如何工作的人可以真正帮助我。这是嵌套在Rails::Initializer.runblock中的代码:config.after_initializedoSomeClass.const_set'SOME_CONST','SOME_VAL'end现在,如果我运行script/server并发出请求,一切都很好。然而,在我的Rails应用程序的第二个请求中,一切都因单元化常量错误而变得糟糕。在生产模式下,我可以成功发出第二个请求,这意味着常量仍然存在。我已通过将以上内容更改为以下内容来解决问题:config.after_initializedorequire'some_cl
我经常迷上ruby的一件事是递归模式。例如,假设我有一个数组,它可能包含无限深度的数组作为元素。所以,例如:my_array=[1,[2,3,[4,5,[6,7]]]]我想创建一个方法,可以将数组展平为[1,2,3,4,5,6,7]。我知道.flatten可以完成这项工作,但这个问题是作为我经常遇到的递归问题的一个例子-因此我试图找到一个更可重用的解决方案。简而言之-我猜这种事情有一个标准模式,但我想不出任何特别优雅的东西。任何想法表示赞赏 最佳答案 递归是一种方法,它不依赖于语言。您在编写算法时要考虑两种情况:再次调用函数的情
这应该是一个简单的问题,但我找不到任何相关信息。给定一个Ruby中的正则表达式,对于每个匹配项,我需要检索匹配的模式$1、$2,但我还需要匹配位置。我知道=~运算符为我提供了第一个匹配项的位置,而string.scan(/regex/)为我提供了所有匹配模式。如果可能,我需要在同一步骤中获得两个结果。 最佳答案 MatchDatastring.scan(regex)do$1#Patternatfirstposition$2#Patternatsecondposition$~.offset(1)#Startingandendingpo
我想开始使用“Sinatra”框架进行编码,但我找不到该框架的“MVC”模式。是“MVC-Sinatra”模式或框架吗? 最佳答案 您可能想查看Padrino这是一个围绕Sinatra构建的框架,可为您的项目提供更“类似Rails”的感觉,但没有那么多隐藏的魔法。这是使用Sinatra可以做什么的一个很好的例子。虽然如果您需要开始使用这很好,但我个人建议您将它用作学习工具,以对您来说最有意义的方式使用Sinatra构建您自己的应用程序。写一些测试/期望,写一些代码,通过测试-重复:)至于ORM,你还应该结帐Sequel其中(imho
有没有一种方法可以自动生成种子数据文件并创建种子数据,就像您在下面链接中的Laravel中看到的那样?LaravelDatabaseMigrations&Seed我在另一个应用程序上看到在Rails的db文件夹下创建了一些带有时间戳的文件,其中包含种子数据。创建它的好方法是什么? 最佳答案 我建议你使用Fabrication的组合gem和Faker.Fabrication允许您编写一个模式来构建您的对象,而Faker为您提供虚假数据,如姓名、电子邮件、电话号码等。这是制造商的样子:Fabricator(:user)dousernam
我有一个交互式RubyonRails应用程序,我想在特定时间将其置于“只读模式”。这将允许用户读取他们需要的数据,但阻止他们执行写入数据库的操作。执行此操作的一种方法是在数据库中放置一个true/false变量,该变量在进行任何写入之前进行检查。我的问题。有没有更优雅的解决方案来解决这个问题? 最佳答案 如果你真的想阻止任何数据库写入,我能想到的最简单的方法是覆盖readonly?始终返回true的模型方法,无论是在选定模型中还是对于所有ActiveRecord模型。如果模型设置为只读(通常通过调用#readonly!来完成),任何