Skip to content

Commit f0087f3

Browse files
committed
The Singleton Pattern
1 parent c5932fe commit f0087f3

File tree

6 files changed

+225
-0
lines changed

6 files changed

+225
-0
lines changed

pom.xml

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@
1111
<properties>
1212
<maven.compiler.source>21</maven.compiler.source>
1313
<maven.compiler.target>21</maven.compiler.target>
14+
<maven.compiler.release>21</maven.compiler.release>
1415
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16+
<junit.version>5.8.2</junit.version>
1517
</properties>
1618

1719
<dependencies>
@@ -31,6 +33,92 @@
3133
<artifactId>guava</artifactId>
3234
<version>33.4.0-jre</version>
3335
</dependency>
36+
<!-- JUnit 5 -->
37+
<dependency>
38+
<groupId>org.junit.jupiter</groupId>
39+
<artifactId>junit-jupiter-api</artifactId>
40+
<version>${junit.version}</version>
41+
<scope>test</scope>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.junit.jupiter</groupId>
45+
<artifactId>junit-jupiter-engine</artifactId>
46+
<version>${junit.version}</version>
47+
<scope>test</scope>
48+
</dependency>
49+
<dependency>
50+
<groupId>org.apache.commons</groupId>
51+
<artifactId>commons-lang3</artifactId>
52+
<version>3.12.0</version>
53+
<scope>test</scope>
54+
</dependency>
3455
</dependencies>
3556

57+
<build>
58+
<plugins>
59+
<!-- sse the updated Maven compiler plugin to support Java 21 -->
60+
<plugin>
61+
<groupId>org.apache.maven.plugins</groupId>
62+
<artifactId>maven-compiler-plugin</artifactId>
63+
<version>3.11.0</version>
64+
<configuration>
65+
<release>${maven.compiler.release}</release>
66+
<showWarnings>true</showWarnings>
67+
<showDeprecation>true</showDeprecation>
68+
</configuration>
69+
</plugin>
70+
71+
<!-- plugin for test -->
72+
<plugin>
73+
<groupId>org.apache.maven.plugins</groupId>
74+
<artifactId>maven-surefire-plugin</artifactId>
75+
<version>3.1.2</version>
76+
</plugin>
77+
78+
<!-- if you need to use the preview feature -->
79+
<plugin>
80+
<groupId>org.apache.maven.plugins</groupId>
81+
<artifactId>maven-compiler-plugin</artifactId>
82+
<version>3.11.0</version>
83+
<configuration>
84+
<compilerArgs>
85+
<arg>--enable-preview</arg>
86+
</compilerArgs>
87+
</configuration>
88+
</plugin>
89+
</plugins>
90+
</build>
91+
92+
<!-- if you need to use Java 21 preview features -->
93+
<profiles>
94+
<profile>
95+
<id>enable-preview</id>
96+
<activation>
97+
<property>
98+
<name>enablePreview</name>
99+
</property>
100+
</activation>
101+
<build>
102+
<plugins>
103+
<plugin>
104+
<groupId>org.apache.maven.plugins</groupId>
105+
<artifactId>maven-compiler-plugin</artifactId>
106+
<configuration>
107+
<compilerArgs>
108+
<arg>--enable-preview</arg>
109+
</compilerArgs>
110+
</configuration>
111+
</plugin>
112+
<plugin>
113+
<groupId>org.apache.maven.plugins</groupId>
114+
<artifactId>maven-surefire-plugin</artifactId>
115+
<configuration>
116+
<argLine>--enable-preview</argLine>
117+
</configuration>
118+
</plugin>
119+
</plugins>
120+
</build>
121+
</profile>
122+
</profiles>
123+
36124
</project>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package Singleton;
2+
3+
import java.io.Serial;
4+
import java.io.Serializable;
5+
6+
public final class DoubleCheckedLockingSingleton implements Serializable {
7+
private static volatile DoubleCheckedLockingSingleton instance; //use volatile to prevent instruction reordering
8+
private static boolean initialized = false; //defense signs
9+
10+
private DoubleCheckedLockingSingleton() {
11+
synchronized (DoubleCheckedLockingSingleton.class) {
12+
if (initialized) { //prevent reflection calls
13+
throw new RuntimeException("Prohibit creating instances through reflection");
14+
}
15+
initialized = true;
16+
}
17+
}
18+
19+
public static DoubleCheckedLockingSingleton getInstance() {
20+
if (instance == null) { //first check
21+
synchronized (DoubleCheckedLockingSingleton.class) {
22+
if (instance == null) { //second check
23+
instance = new DoubleCheckedLockingSingleton();
24+
}
25+
}
26+
}
27+
return instance;
28+
}
29+
30+
//return existing instances during deserialization
31+
@Serial
32+
private Object readResolve() {
33+
return getInstance();
34+
}
35+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package Singleton;
2+
3+
public enum EnumSingleton {
4+
INSTANCE
5+
;
6+
7+
public void doSomething() {
8+
// do sth...
9+
}
10+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package Singleton;
2+
3+
public final class StaticInnerClassSingleton {
4+
private StaticInnerClassSingleton() {}
5+
6+
private static class SingletonHolder {
7+
private static final StaticInnerClassSingleton INSTANCE = new StaticInnerClassSingleton();
8+
}
9+
10+
public static StaticInnerClassSingleton getInstance() {
11+
return SingletonHolder.INSTANCE;
12+
}
13+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package common.utils;
2+
3+
import java.io.*;
4+
5+
public class SerializationUtils {
6+
public static byte[] serialize(Object obj) throws IOException {
7+
ByteArrayOutputStream baos = new ByteArrayOutputStream();
8+
try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
9+
oos.writeObject(obj);
10+
}
11+
return baos.toByteArray();
12+
}
13+
14+
@SuppressWarnings("unchecked")
15+
public static <T> T deserialize(byte[] bytes) throws IOException, ClassNotFoundException {
16+
try (ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bytes))) {
17+
return (T) ois.readObject();
18+
}
19+
}
20+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package singleton;
2+
3+
import Singleton.DoubleCheckedLockingSingleton;
4+
import common.utils.SerializationUtils;
5+
import org.junit.jupiter.api.Test;
6+
7+
import java.lang.reflect.Constructor;
8+
9+
import static org.junit.jupiter.api.Assertions.*;
10+
11+
public class DoubleCheckedLockingSingletonTest {
12+
13+
@Test
14+
public void testSingletonInstance() {
15+
//Test ordinary obtaining instances
16+
DoubleCheckedLockingSingleton instance1 = DoubleCheckedLockingSingleton.getInstance();
17+
DoubleCheckedLockingSingleton instance2 = DoubleCheckedLockingSingleton.getInstance();
18+
19+
assertNotNull(instance1);
20+
assertSame(instance1, instance2, "Two instances should be the same object");
21+
}
22+
23+
@Test
24+
public void testReflectionAttack() {
25+
try {
26+
//Attempt to create an instance through reflection
27+
Constructor<DoubleCheckedLockingSingleton> constructor =
28+
DoubleCheckedLockingSingleton.class.getDeclaredConstructor();
29+
constructor.setAccessible(true);
30+
31+
//First instance acquisition (normal method)
32+
DoubleCheckedLockingSingleton instance1 = DoubleCheckedLockingSingleton.getInstance();
33+
34+
//Attempt to create a second instance through reflection
35+
assertThrows(RuntimeException.class, () -> {
36+
DoubleCheckedLockingSingleton instance2 = constructor.newInstance();
37+
}, "An exception should be thrown to prevent reflection from creating an instance");
38+
39+
} catch (Exception e) {
40+
fail(STR."Reflection call failed: \{e.getMessage()}");
41+
}
42+
}
43+
44+
@Test
45+
public void testSerialization() throws Exception {
46+
//Obtain a singleton instance
47+
DoubleCheckedLockingSingleton original = DoubleCheckedLockingSingleton.getInstance();
48+
49+
//Serialize
50+
byte[] serialized = SerializationUtils.serialize(original);
51+
52+
//Deserialize
53+
DoubleCheckedLockingSingleton deserialized =
54+
SerializationUtils.deserialize(serialized);
55+
56+
//Verify that even after deserialization, it still remains the same instance
57+
assertSame(original, deserialized, "After deserialization, it should be the same instance");
58+
}
59+
}

0 commit comments

Comments
 (0)