23种设计模式之单例模式
程序员文章站
2022-06-04 23:17:13
...
单例模式
Singleton
单例模式就是只有一个实例存在
有时候完全只需要一个实例存在就够了,不用那么多实例存在
饿汉式(常用)
//饿汉式
public class SingLeton01 {
//类加载的时候就实例化这个单例
private static final SingLeton01 INSTANCE = new SingLeton01();
//私有化无参构造,封装好这个类 防止使用者再new而产生多个实例
private SingLeton01(){
}
//拿到这个单例的方法
public static SingLeton01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
//测试多线程是否安全
for (int i = 0; i < 100; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()-> System.out.println(SingLeton01.getInstance())).start();
}
}
}
-
优点
-
简单常用
-
私有化无参构造,让使用者不能再产生多的实例化
-
类加载到内存时,实例化这个单例
-
因为类只加载一次,所以只有这个单例,线程安全(JVM只加载一次这个类)
-
-
缺点
- 无论使用与不使用,这个单例在类加载时都被实例化了
懒汉式
//懒汉式
public class SingLeton02 {
private SingLeton02(){
}
//类加载时 不初始化 所以不加final
private static SingLeton02 INSTANCE;
public static SingLeton02 getInstance(){
//第一次调用这个方法时,INSTANCE为空实例化对象,之后不为空了就直接返回这个单例
if (INSTANCE == null){
/*try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
INSTANCE = new SingLeton02();
}
return INSTANCE;
}
public static void main(String[] args) {
//多线程测试 发现线程不安全(测不出来可以在getInstance方法中线程睡眠)
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SingLeton02.getInstance())).start();
}
}
}
-
线程不安全原因:
- 当线程A进入getInstance方法判断INSTANCE为空,此时线程B也进入getInstance方法并判断INSTANCE为空,然后就造成了实例多个对象,并非单例
-
虽然解决了按需要来进行初始化的问题,但是引来了线程不安全
懒汉式加锁优化
既然懒汉式线程不安全,那就加锁
//懒汉式加锁优化
public class SingLeton03 {
private SingLeton03(){
}
private static SingLeton03 INSTANCE;
//因为这里是同步方法,所以锁的是SingLeton03这个类
public synchronized static SingLeton03 getInstance(){
if (INSTANCE == null){
INSTANCE = new SingLeton03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SingLeton03.getInstance())).start();
}
}
}
虽然线程安全了,但是因为加了synchronized效率变低了(每次操作都要看是否加了锁等…)
懒汉式同步块再优化
将同步方法变为同步块再优化
//懒汉式同步块优化
public class SingLeton04 {
private SingLeton04(){
}
private static SingLeton04 INSTANCE;
public static SingLeton04 getInstance(){
if (INSTANCE == null){
synchronized (SingLeton04.class){
if (INSTANCE == null){
INSTANCE = new SingLeton04();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SingLeton04.getInstance())).start();
}
}
}
静态内部类方式
//静态内部类 实现懒加载,线程安全
public class SingLeton05 {
private SingLeton05(){
}
//静态内部类
public static class Inner{
private static SingLeton05 INSTANCE = new SingLeton05();
}
public static SingLeton05 getInstance(){
return Inner.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SingLeton05.getInstance())).start();
}
}
}
Inner类在SingLeton05类内部,外部访问不了
实现了懒加载和线程安全(JVM保证单例)
枚举单例(完美)
effecfive java 作者提出
public enum SingLeton06 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SingLeton06.INSTANCE.hashCode())).start();
}
}
}
解决线程同步
防止反序列化
上面的写法:Java通过反射反序列化 Class对象在内存中然后再把实例new出来
枚举的反序列化不是通过反射
枚举默认无参构造私有化
记录学习单例模式,如有错误,请指出,感谢