单例类实例化出来的对象是堆里的一个对象,且是同一个对象
使用场景:
当对象需要频繁的创建和销毁的时候,单例可以提高性能
优点:
.只有一个实例对象,可以节省很多空间。
.对象不会被重复的 new ,降低了对系统资源的重复占用,提高了性能。
缺点:
.只有一个对象的话,无疑扩展性差了很多。(想扩展你就得来改这个 单例类)
.场景比较多的时候,单例就显得很无力了,根本无法应对,这时候还是不要使用单例了
.配合连接池时,不推荐使用,单例又不会死,一直来,池子就满了。
/**
* @ClassName Hungary 饿汉式单例
* @Description 最基本的单例模式
* @Author SkySong
* @Date 2020-09-15 22:09
*/
public class Hungary {
//构造器私有
private Hungary(){
}
//先把对象 new 出来
/**
* 一上来就创建对象的话 容易造成浪费空间的问题
* class 被加载时 new 就分配了空间,但我们不一定用
* 这也奠定了 懒汉式 横空出世 的必要性
*/
public final static Hungary HUNGARY = new Hungary();
public static Hungary getInstance(){
return HUNGARY;
}
}
基本实现:
/**
* @ClassName LazyMan 懒汉式 单例
* @Description
* @Author SkySong
* @Date 2020-09-15 22:24
*/
public class LazyMan {
//还是构造器私有
private LazyMan(){
}
//先不实例化对象
public static LazyMan lazyMan;
//当调用时,再创建
public static LazyMan getInstance(){
if (lazyMan == null){
lazyMan = new LazyMan();
}
return lazyMan;
}
}
分析问题:
如果系统是单线程执行的话,这个写法没有问题。 但多线程就会出现问题……
我们用 10 个线程并发 测试一下
public class LazyMan {
//还是构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
public static LazyMan lazyMan;
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(){
@Override
public void run() {
lazyMan.getInstance();
}
}.start();
}
}
}
结果不一,甚至出现多个 实例
Thread-1
Thread-2
Thread-0
Process finished with exit code 0
这时需要引出我们的 DCL懒汉式
/**
* @ClassName LazyMan DCL懒汉式 单例
* @Description 加入锁,解决多线程并发 单例变多例 问题
* @Author SkySong
* @Date 2020-09-15 22:24
*/
public class LazyMan {
//还是构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
public 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(){
@Override
public void run() {
lazyMan.getInstance();
}
}.start();
}
}
}
分析问题:
new 对象的过程 不是一个原子性操作!
过程如下:
1.分配空间
2.初始化(执行构造方法)
3.让 引用 指向这个空间
当发生指令重排的时候,会出现问题:
我们希望的执行顺序时 1->2->3
但 CPU 为了提高效率,有可能会 执行为 1->3->2
此时,引用已经指向了空间(lazyMan 在这一时刻后就不在是 null 了),但这个空间还没有完成初始化
为了解决这个问题,刚才的代码需要改进:(给这个对象加上volatile ,防止它指令重排 )
package single;
/**
* @ClassName LazyMan 懒汉式 单例
* @Description
* @Author SkySong
* @Date 2020-09-15 22:24
*/
public class LazyMan {
//还是构造器私有
private LazyMan(){
System.out.println(Thread.currentThread().getName());
}
public volatile 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(){
@Override
public void run() {
lazyMan.getInstance();
}
}.start();
}
}
}
在内部类里调用,这样别人再怎么调用外部类都不会影响到单例。
/**
* @ClassName Outer
* @Description
* @Author SkySong
* @Date 2020-09-15 23:53
*/
public class Outer {
private Outer(){
}
public static Outer getInstance(){
return Inner.outer;
}
public static class Inner{
public final static Outer outer = new Outer();
}
}