前言
单例最重要的思想:构造器私有。
一、饿汉式单例
- 代码示例:
public class hunger {
private byte[] data1 = new byte[1024];
private byte[] data2 = new byte[1024];
private byte[] data3 = new byte[1024];
private byte[] data4 = new byte[1024];
private hunger(){
}
private final static hunger hunger =new hunger();
public static hunger getInstance(){
return hunger;
}
}
- 缺点:一上来就把所有的东西加载出来,data1不用,就会浪费空间。
二、懒汉式单例
代码如下(示例):
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"OK");
}
private static LazyMan lazyMan;
//直接这个单线程下OK
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
//多线程下有问题
public static void main(String[] args){
for (int i=0;i<10;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
改进代码如下(示例):
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"OK");
}
private static LazyMan lazyMan;
//双重检测锁模式的懒汉式单例 DCL懒汉式
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {//加锁,不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//多线程
public static void main(String[] args){
for (int i=0;i<10;i++){
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
但是这个代码不安全!!再改进!
public class LazyMan {
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"OK");
}
private volatile static LazyMan lazyMan;//volatile 保证不会被指令重排
//双重检测锁模式的懒汉式单例
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {//加锁,不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁
if (lazyMan == null) {
lazyMan = new LazyMan();//极端情况下有问题。他不是原子性操作
/**
* 1.分配内存空间
* 2.执行构造方法,初始化对象
* 3.把这个对象指向这个空间
* 如果132 A
* B 此时lazyMan还没有完成构造
*/
}
}
}
return lazyMan;
}
}
还可以用内部类:
//静态内部类
public class Holder {
private Holder(){}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
public static class InnerClass{
private static final Holder HOLDER =new Holder();
}
}
三、反射破解
但是上面的这些都不安全,都会被反射破解。
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
Thread.currentThread().getName()+"OK");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//反射!
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
解决
import java.lang.reflect.Constructor;
public class LazyMan {
private LazyMan(){
synchronized (LazyMan.class){
if (lazyMan!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static LazyMan lazyMan;
public static LazyMan getInstance() {
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
}
}
return lazyMan;
}
//反射!
public static void main(String[] args) throws Exception {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
假如两个对象都是反射new出来的,也会被破解。
public static void main(String[] args) throws Exception {
// LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
解决
private static boolean qi = false;
private LazyMan(){
synchronized (LazyMan.class){
if (qi==false){
qi=true;
}else {
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
破坏单例
public static void main(String[] args) throws Exception {
// LazyMan instance1 = LazyMan.getInstance();
Field qi = LazyMan.class.getDeclaredField("qi");
qi.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
qi.set(instance1,false);
LazyMan instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
四、枚举解决
import java.lang.reflect.Constructor;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws Exception {
// EnumSingle instance1 = EnumSingle.INSTANCE;
// EnumSingle instance2 = EnumSingle.INSTANCE;
// System.out.println(instance1);
// System.out.println(instance2);
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> de = EnumSingle.class.getDeclaredConstructor(null);
de.setAccessible(true);
EnumSingle instance2 = de.newInstance();
System.out.println(instance2);
System.out.println(instance1);
}
}
cmd >javap -p EnumSingle.class