单例设计模式学习

1.掌握两种懒汉式的单利设计模式
2.理解兵法编程中的可见性和有序性
3.理解volatile关键字的作用
单例设计模式:
解决的问题:一个应用范围内,一个class类,只应该存在一个对象
单例设计模式的特点:
1.构造私有(不能通过正常手段进行new对象—因为通过反射还可以私有的方法进行调用)
2.静态成员变量(存储单单例对象的引用)
3.提供一个公开静态的访问方法,获取单例对象。
两种方式:
(1)懒汉式
会存在线程安全问题(面试最常问的问题)
(2)饿汉式
不存在线程安全问题
当单例对象所对应的类被初始化的时候才会创建该单例对象
类的初始化时机有哪些:
在JVM加载类的时候,由JVM对加载类的线程加锁,也就是说类加载的时候是线程安全的,不会存在多线程安全问题。
饿汉式和懒汉式的区别:
(1)饿汉式不管你用不用该对象都创建。
---------------会造成资源的浪费
---------------在创建对象的时候不会出现多线程情况。
(2)懒汉式只有第一次访问的时候才会创建。
--------也叫延迟加载,由于被加载的时候可能存在多线程访问

单例模式常见的规范写法有多种,只记录两种:
1.双重检查锁(DCL) + volatile
2.静态内部类

代码实现:
/****
单例模式
同时在内存中,只有一个对象存在
如何保证一个类在内存中只能实现一个实例那?1:构造私有化2.使用私有静态成员变量初始化本身对象3:对外提供静态公共方法获取本对象

单例模式有两种方式:1:懒汉式(延迟加载)2.饿汉式
**/
饿汉式:
public clss Student1{
//2.成员变量初始化本身对象
private static Student1 student = new Student1();//饿汉式
//构造私有
private Student1(){
}

//3.对外提供公共方法获取本对象
Public static Student1 getSingletonInstance(){
return student;
}

public void sayHello(String name){
System.out.println(“hello,” + name);
}
}
懒汉式0.0版
/***

懒汉式单例模式步骤:
1.构造方法私有化
2.定义私有静态成员变量,先不初始化
3.定义公开静态方法,获取本身对象(有对象就返回已有对象,没有对象再去创建)

线程安全问题,判断依据
1.是否存在多线程
2.是否有共享数据
3.是否存在非原子性操作(是针对CPU(字节码)指令来说,不是高级语言的代码)
例子:
线程安全:
UserService {
@Autowired
private UserDao userDao;
public void method1(){

}
}

**/
public clss Student2{
//2.成员变量初始化本身对象
private static Student2 student = null;//懒汉式
//构造私有
private Student2(){
}

//3.对外提供公共方法获取本对象
public static Student2 getSingletonInstance(){
if(student == null){
student = new Student2();
}
reutrn student;
}
public void sayHello(String name){
System.out.println(“hello,” + name);
}
}
分析:10个线程并发访问getSingletonInstance方法,10个线程都会new Student1(),此时就会产生10个对象,违反了单例模式原则。
懒汉式0.1版
/****
synchronized关键字锁住的这个对象,这样的用户在性能上会有所下降,因为每次调用getSingletonInstance()都要对对象上锁,事实上,只要在第一次创建对象的时候加锁之后就不需要了。所以这个地方需要改进
*/
public clss Student3{
private static Student3 student = null;//懒汉式
private Student3(){
}

//此处考验对synchronized知识点的掌握情况
public static synchronized Student3 getSingletonInstance(){
if(student == null){
student = new Student1();
}
reutrn student;
}
public void sayHello(String name){
System.out.println(“hello,” + name);
}
}
懒汉式0.2版
/**
似乎解决了之前提到的问题,将synchronized 关键字加载了内部,也就是说当调用的时候是不需要加锁的。只有在instance为null并创建对象的时候才需要加锁,性能有一定的提升

但是,这样的情况还是有一定的问题,情况如下:
在java指令中创建对象和赋值操作是分开进行的,也就是说instance = new Singleton();语句是分两步执行的。
但是JVM并不保证这两个操作的先后顺序,也就是说有可能JVM会为新的Singleton实例分片空间,然后直接赋值给instance成员,然后再去初始化这个Singleton实例。这样就可能出错了。

以A、B两个线程威力:
1.A、B线程同时进入了第一个if判断
2.A首先进入synchronized块,由于instance为null,所以他执行instance = new Singleton();
3.由于JVM内部的优化机制(指令重排序),JVM先画出了一些分配给Singleton实例的空白内存并赋值给instance成员(注意此时JVM并没有开始初始化这个实例),然后A离开了synchronized块。
4.B进入synchronized块,由于instance此时不是null,因此它马上离开了synchronized块并将结果返回给调用该方法的程序。
5.此时B线程打算使用Singleton实例,却发现它没有被初始化,于是错误发生了。

**/
/DCL 双重检查锁/
public clss Student4{
private static Student4 student = null;//懒汉式
private Student3(){
}

//此处考验对synchronized知识点的掌握情况
public static Student4 getSingletonInstance(){
if(student == null){
//采用这种方式,对于对象的选择会有问题
//JVM优化机制(JIT即时编译器):先分配内存空间,在初始化
synchronized (Student4 .class){
If(student == null){
student= new Student4();//加上同步锁保证了原子性操作
//student.setName(“SS”);
//1.开辟内存空间
//2.student初始化成员变量
//3.student赋值,对象引用赋值
//以上的顺序可能是:1-3-2,此时发生了线程时间分片问题,

}
}
}
return student;
}
public void sayHello(String name){
System.out.println(“hello,” + name);
}
}
注意:两个线程同时进入synchronized锁,A进入块中进行执行代码,B需要等A执行完毕释放锁后才会进入块中,不会直接放弃锁返回对象。
//student.getName();

懒汉式0.3版
/**
synchronized 保证原子性
volatile 保证可见性和有序性
双重检查锁+volatile,最终版
**/
public class Student6{
private volatile static Student6 student;
private Student6(){}

public static Student6 getInstance(){
if(student == null){
//B线程检测到student不为空
synchronized(Student6.class){
If(student == null){
Student = new Student6();
//A线程被指令重排了,刚好先赋值了,但还没执行完构造函数
}
return student;//后面B线程执行时引发:对象尚未初始化错误
}
}
}

懒汉式0.4版
/***
静态内部类
/
Public class Student5{
/
**
此处使用一个内部类来维护单例,JVM在类加载的时候互斥的,所以可以由此保证线程安全问题
*/
private static class SingletonFactory{
private static Student5 studnet = new Student5();
}
/获取实例/
public static Student5 getSingletonInstance(){
return SingletonFactory.student;
}
}

/*****************************************************************************/
原子性的理解:
(1)new Student();//是否是一个原子性操作?不是
(2)i++;//是否是一个原子操作?不是
①先取出i变量的值
②进行加1操作
③将加1之后的值赋值给i
如何保证原子性那?加锁

可见性的理解:
可见性解决方案:volatile关键字
volatile第一个作用:
可以禁用cpu缓存或者说工作内存
在这里插入图片描述
线程间变量不可见性图例

有序性的理解:
(1)例子
int x;//step1
boolean a;//step2
x=20;//step3
a=false;//step4
x和a之间时没有依赖性的(有没有hanppened-before关系),所以说JVM会考虑型那问题,对step3和step4进行指令(字节码指令)重排序。

(2)
int x = 10;
int y;
y = x + 10;
x和y存在依赖性,所以说此处不会发生指令重排序

总结:指令重排序不会影响最终的结果
有序性的解决:通过关键字volatile。
/*****************************************************************************/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值