一:
单例模式:单例模式就是只能产生一个实例化对象的一种模式。这一模式的目的是为了类中产生的一个对象成为整个系统的唯一一个对象。
二:
单例模式的特点:
①这个类只能有一个实例
②这个类必须自己创建自己的唯一实例
③这个类必须给所有其他对象提供这一对象实例。
三:
单例模式的实现:
1.提供一个私有的构造方法,确保在类的外部无法产生实例化对象,无法通过new关键字产生实例化对象.
2.提供一个静态的方法,使得只能通过该类的静态方法来取得对象的实例。
(1):饿汉式单例模式:
饿汉式单例模式的特点:
.在类加载的过程中已经产生实例化对象,不会涉及多个线程访问的问题,虚拟机只会装载一次这个类,不会发生并发访问的问题
.如果只是加载这个类,而没有要调用getInstance(),甚至永远不调用getInstance(),会造成资源空间浪费。
实例:
class HungrySingleton {
//实例化这个类,并且当类被加载的时候就已经产生了实例化对象,并且保存在内存中
private static HungrySingleton singleton = new HungrySingleton();
//提供一个私有的构造方法,确保在类的外部无法产生实例化对象,无法通过new关键字产生实例化对象
private HungrySingleton() {
}
public static HungrySingleton getInstance() {
return singleton;
}
}
public static void main(String[] args) {
HungrySingleton hungrySingleton1 = HungrySingleton.getInstance();
HungrySingleton hungrySingleton2 = HungrySingleton.getInstance();
System.out.println(hungrySingleton1);
System.out.println(hungrySingleton2);
System.out.println(hungrySingleton1 == hungrySingleton2);
}
运行结果:
创建对象。。。
HungrySingleton@1540e19d
HungrySingleton@1540e19d
true
可以看出,在此处该类的构造方法只调用了一次,并且产生的对象是同一个对象。
总结:饿汉式单例模式线程安全,在该类加载的时候就会产生一个实例化对象,但是如果没有要调用getInstance()方法,就会造成资源空间浪费,因为此时在堆内存和栈内存已经开辟了空间 用来储存该类的对象。同时,饿汉式单例模式不能实现按延迟加载。
(2):懒汉式单例模式
class LazySingleton{
//声明这个类的变量,但是没有实例化
//将类的实例化放在getInstance()中
private static LazySingleton lazySingleton;
//提供一个私有的构造方法,确保在类的外部无法产生实例化对象,无法通过new关键字产生实例化对象
private LazySingleton(){
System.out.println("创建对象。。。");
}
public static LazySingleton getInstance(){
if(lazySingleton == null){//当lazySingleton为空时在进行新对象的创建
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
以上是基本的懒汉式单例模式,懒汉式单例模式存在在多线程情况下线程不安全问题。
当一个线程A执行到lazySingleton = new LazySingleton();时,创建对象,但是对象的创建需要经过1.分配内存空间,2.调用构造函数,3返回地址给引用。对象不会立马创建出来.如果此时线程B执行到 if(lazySingleton == null),经过判断,发现lazySingleton为空,接着创建新的对象,就会产生线程A和线程B分别创建了一个对象。系统中就存在了两个对象,违背了单例模式的最初目的。
测试:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton lazySingleton1 = LazySingleton.getInstance();
System.out.println(lazySingleton1);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton lazySingleton2 = LazySingleton.getInstance();
System.out.println(lazySingleton2);
}
}).start();
运行结果:
创建对象。。。
LazySingleton@2858c11c
LazySingleton@2858c11c
一种情况是创建了一个对象,构造方法只调用了一次。
创建对象。。。
创建对象。。。
LazySingleton@2770f418
LazySingleton@29580e2d
第二种情况是创建了两个对象,调用了两次构造方法。
总结:懒汉式单例模式在多线程情况下存在线程不安全问题。
优化:使用synchronized关键字(1.同步方法2.同步代码块)
1.同步方法
在getInstance()方法前面加上synchronized:
class LazySingleton{
//声明这个类的变量,但是没有实例化
//将类的实例化放在getInstance()中
private static LazySingleton lazySingleton;
//提供一个私有的构造方法,确保在类的外部无法产生实例化对象,无法通过new关键字产生实例化对象
private LazySingleton(){
System.out.println("创建对象。。。");
}
//为了数据安全添加synchronized关键字
public static synchronized LazySingleton getInstance(){
if(lazySingleton == null){//当lazySingleton为空时在进行新对象的创建
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
测试:
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton lazySingleton1 = LazySingleton.getInstance();
System.out.println(lazySingleton1);
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton lazySingleton2 = LazySingleton.getInstance();
System.out.println(lazySingleton2);
}
}).start();
运行结果:
创建对象。。。
LazySingleton@29580e2d
LazySingleton@29580e2d
经过多次实验,发现此时在getInstance()前面加上关键字synchronized后只会产生一个实例化对象。
2.同步代码块:
//同步代码块解决懒汉式线程不安全问题
class LazySingleton{
private static LazySingleton lazySingleton;
private LazySingleton(){//私有的构造方法
System.out.println("创建对象。。。");
}
public static LazySingleton getInstance(){
if(lazySingleton == null){
synchronized (LazySingleton.class){//此处了解同步代码块的使用
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
测试代码与上述同步方法的测试代码相同
运行结果:
创建对象。。。
LazySingleton@6d5af378
LazySingleton@6d5af378
总结:同样,经过多次测试,使用同步代码块的方法也只会产生一个实例。
由于在Java的JVM中在编译时存在指令重排的优化机制,因此还有可能出现错误。
JVM中的线程的三个特性:原子性,可见性,有序性,
任意一个不满足,都存在线程安全问题。
由于在JVM中存在指令重排机制,cpu为了优化程序,可能会打乱原子操作执行顺序,lazySingleton = new LazySingleton()此句代码不是原子操作,在JVM中原子操作就是不可分割的操作,也就是指不会因为线程调度被打断的操作。所以在执行这一句代码时会出现以下几步:
生成一个对象需要进行三步:
1.分配内存空间
2.调用构造方法
3.返回地址给引用
有可能会打乱2,3步,导致对象内存还没有分配,就会被使用。
解释:
线程A和线程B同时进入,线程A执行到lazySingleton = new LazySingleton();进行对象的创建,由于存在指令重排,可能会导致先把引用进行赋值,而并没有执行构造函数,此时cpu时间片结束,线程执行BlazySingleton = new LazySingleton(),此时线程B判断lazySingleton的引用不为空,直接返回引用地址,然后导致线程B使用了没有被初始化的变量。可能会导致错误出现。
由此引出关键字volatile:
class LazySingleton{
//volatile保证变量对所有线程的可见性
//使用volatile变量的语义是禁止指令重排
private static volatile LazySingleton lazySingleton;
private LazySingleton(){//私有的构造方法
System.out.println("创建对象。。。");
}
//synchronized保证操作的原子性
public static LazySingleton getInstance(){
if(lazySingleton == null){
synchronized (LazySingleton.class){//此处了解同步代码块的使用
if(lazySingleton == null){
lazySingleton = new LazySingleton();
}
}
}
return lazySingleton;
}
}
这是我对单例模式的简单理解,如有错误,欢迎指出。