单例模式及破解方法+枚举类型创建单例模式
单例模式主要分为饿汉式和懒汉式。
例如:
1.1 饿汉式
饿汉式即开始的时候就new一个对象
public class Person() {
private static final Person p = new Person();
private Person() {}
public static Person getInstance(){
return p;
}
}
1.1 饿汉式的特点
饿汉式的特点:
饿汉式是先天的线程安全且写法简单
立即加载,用空间换取时间,会浪费一定内存空间但可以忽略不计,推荐使用
1.2 懒汉式
相对于饿汉式的一开始就创建对象,懒汉式则是在用到时才new对象出来。
例如:
public class Person(){
private static Person p;
private Person() {}
public static Person getInstance(){
if(p == null){
p = new Person();
}
return p;
}
}
1.2.1 懒汉式的特点
懒汉式的特点:
懒汉式是线程不安全的模式,但可以通过后天实现线程安全
延时加载,用时间换取空间,节省空间
1.2.2 懒汉式(低效)线程安全版
对于上面的懒汉式,存在线程不安全的情况,所以我们可以通过加锁的方式让其变为线程安全。
public class PersonSave {
private static PersonSave p;
private PersonSave() {}
public static PersonSave getInstance() {
// 加锁后 性能会低
synchronized (PersonSave.class){
if (p == null) {
p = new PersonSave();
}
}
return p;
}
}
加锁后会使性能有所降低
1.2.3 懒汉式(高效)线程安全版
我们可以发现加锁后在多线程的情况下,所有线程都会停在synchronized前面进行判断,而实际运行中除第一个需要进去之外其余的无需进入该锁,所以我们可以在上面加一个判断从而使程序变得高效起来。
public class PersonSave {
private static PersonSave p;
private PersonSave() {}
public static PersonSave getInstance() {
if (p == null){
synchronized (PersonSave.class){
if (p == null) {
p = new PersonSave();
}
}
}
return p;
}
}
2. 单例模式的破解
虽然单例模式旨在只创建一个对象,但是我们可以通过反射对单例模式进破坏。
2.1 通过构造器破解
2.1.1 通过构造器对单例模式的破解
public class PersonSave {
private static PersonSave p;
private PersonSave() {}
public static PersonSave getInstance() {
if (p == null){
synchronized (PersonSave.class){
if (p == null) {
p = new PersonSave();
}
}
}
return p;
}
}
public class Test {
public static void main(String[] args) throws Exception{
Class<Person> c = Person.class;
Constructor<Person> d = c.getDeclaredConstructor();
d.setAccessible(true);
Person p1 = d.newInstance();
Person p2 = d.newInstance();
System.out.println(p1 == p2);
}
}
其输出结果为false;
2.1.2 改进手段
我们可以在定义一个值标记我们是否已经创建对象
public class Person{
private static boolean bz = false;
private static volatile Person p;
private Person() {
if (!bz) {
bz = true;
} else {
throw new RuntimeException("不要试图用反射破环单例");
}
}
public static Person getInstance() {
if (p == null) {
synchronized (Person.class) {
if (p == null) {
p = new Person();
}
}
}
return p;
}
}
2.2 再破解
我们之前设置了一个布朗值去判断是否已经创建对象,同理我们可以通过反射对值进行修改进一步对单例模式进行破解。
public static void main(String[] args) throws Exception {
Field bz = Person.class.getDeclaredField("bz");
bz.setAccessible(true);
Constructor<Person> dc = Person.class.getDeclaredConstructor(null);
dc.setAccessible(true);
Person p1 = dc.newInstance();
bz.set(p1, false);
Person p2 = dc.newInstance();
System.out.println(p1 == p2);
}
3 枚举类型单例模式
如果我们用反射对枚举类型进行破解,则会在控制台输出Cannot reflectively create enum objects,即无法以反射方式创建枚举对象,所以我们用枚举类型创建单例模式可以防止对单例模式的破解
class Rourse{
// 代码。。。
}
public enum LazyMAN {
INSTANCE;
private static Rourse rourse;
LazyMAN(){}
public static Rourse getInstance(){
if (rourse == null){
synchronized (LazyMAN.class){
if (rourse == null){
rourse = new Rourse();
}
}
}
return rourse;
}
}
class test{
public static void main(String[] args) throws Exception{
Constructor<LazyMAN> dc = LazyMAN.class.getDeclaredConstructor(String.class,int.class);
dc.setAccessible(true);
LazyMAN l1 = dc.newInstance();
LazyMAN l2 = dc.newInstance();
System.out.println(l1);
System.out.println(l2);
}
}

浙公网安备 33010602011771号