Singleton单例模式
23种经典设计模式之一。后续我会继续更新设计模式(看心情)。设计模式,是前人也就是之前的大佬一步一步一个坑踩出来得到的经验。这里不仅仅是java 也是很多种语言可以共同使用的一种思想。好了废话不多说这就上笔记。
只需要一个实例,严格意义上有八种写法
首先构造方法设成
私有的private ,其他类调用的时候new不出来。
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01();
private Singleton01(){}
public static Singleton01 getInstance(){
return INSTANCE;
}
public void m(){ System.out.println("m"); }
public static void main(String[] args) {
Singleton01 m1 = Singleton01.getInstance();
Singleton01 m2 = Singleton01.getInstance();
System.out.println(m1 == m2);
}
}
饿汉式
类加载到内存后,就实例化一个单例,JVM保证线程安全。非常简单实用,推荐使用
唯一缺点:不管是否用到,都会加载的时候实例话。
实际上你要加载类的话是这样写的
Class.forname("类名称");
就是只会加载到内存并不会实例化。类加载时候,只要是被static关键字修饰的都会实例化。(不懂为啥的可以再看一遍static关键字作用)
public class Singleton02 {
private static final Singleton02 INSTANCE;
static {
INSTANCE = new Singleton02();
}
private Singleton02(){}
public static Singleton02 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton02 m1 = Singleton02.getInstance();
Singleton02 m2 = Singleton02.getInstance();
System.out.println(m1 == m2);
}
}
本质上和第一种没有区别 只是改成了静态块的一种写法
import java.util.Objects;
public class Singleton03 {
private static Singleton03 INSTANCE;
private Singleton03(){}
public static Singleton03 getInstance(){
if (Objects.isNull(INSTANCE)){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
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().hashCode());
}).start();
}
}
}
lazy loading
懒汉式(什么时候我用 什么时候我初始化)
虽然达到了按需初始化的目的,但也带来了线程不安全的问题。
比如当第一个线程走到try-catch的时候 还没有被初始化 不知名的原因 这里用睡眠代替。
第二个线程也进来了,因为第一个线程的睡眠导致还是这个对象还是空。所以也进去了睡眠。
后面的线程也是。直到第一个线程把对象new出来过后。后面的进程才会读取到。
线程写法为 jdk1.8新增的 Lambda(那木大)表达式写法 不懂都可以去看看jdk每个版本新增的不同内容
顺便提一句Lambda函数式编程也是趋势所为。很多学习过面向函数式编程的小伙伴也知道。有很多语言是面向函数式的,比如python,golang等等,所以很多语言是学通一门学习学其他的语言的时候都会比较轻松。语言是相通的。
运行结果为
因为线程太快的原因 所以睡眠期间进入过多少他就会改变多少次 表现为hashcode值不同 (但是hashcode值相同 对象也可能不一样)但是最后一个睡眠进程过了new出来过后,后面就是全部一样的。怎么样解决呢。
public static synchronized Singleton03 getInstance(){
if (Objects.isNull(INSTANCE)){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton03();
}
return INSTANCE;
}
很简单,加一个synchronized锁也是第四种写法,你会发现hashcode就全部都一样了,但这样效率也会降低。相信大部分人都不知道这个单词怎么读,只知道是什么意思,有什么用。如果你会读那么你是牛逼的。
第五种写法也是懒汉式
import java.util.Objects;
public class Singleton04 {
private static Singleton04 INSTANCE;
private Singleton04(){}
public static Singleton04 getInstance(){
if (Objects.isNull(INSTANCE)){
synchronized(Singleton04.class){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
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().hashCode());
}).start();
}
}
}
synchronized加在方法内 企图通过减小同步代码块的方式提高效率,然后发现不可行。同第三种一样
所以也就衍生出第六种写法
import java.util.Objects;
public class Singleton06 {
private static Singleton06 INSTANCE;
private Singleton06(){}
public static Singleton06 getInstance(){
if (Objects.isNull(INSTANCE)){
synchronized(Singleton06.class){
if (INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton06();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(()->{
System.out.println(Singleton06.getInstance().hashCode());
}).start();
}
}
}
以前的双重检查,写法也越来越复杂。但这种也可以解决问题。所以 以前的单例模式写法里面这种也是称为最完美的单例模式之一。有些人可能会觉得第一个判断为空没有必要,但实际可以减少synchronized加锁的次数
第七种也是完美的单例模式之一
public class Singleton07 {
private Singleton07(){}
private static class Singleton07Holder{
private final static Singleton07 INSTANCE = new Singleton07();
}
public static Singleton07 getInstance(){
return Singleton07Holder.INSTANCE;
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(()->{
System.out.println(Singleton06.getInstance().hashCode());
}).start();
}
}
}
但是最后一种绝对会惊呼一些常人理解的完美单例之一。
public enum Singleton08 {
INSTANCE;
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(()->{
System.out.println(Singleton06.getInstance().hashCode());
}).start();
}
}
}
很简单。
这种写法既可以防止反序列化,也可以解决线程同步
是一本《effective java 》上来的方法,有时间读书的小伙伴可以去看看,作者是Joshua,是java设计师也是google的首席java架构师
以上也是我的学习笔记,顺便提一句 笔记使用有道云笔记 特别方便 手机也可以看。换台电脑也可以继续写笔记 。好像还有网页版吧,也不清楚。我一般都下的软件使用。感兴趣小伙伴也可以看看,我们下次再见,拜拜~~