单例模式(Singleton Pattern)是23种设计模式中,最简单的形式之一。这一模式的目的是使得类的一个对象成为系统中的唯一实例。通过单例模式可以保证系统中,应用该模式的一个类只有一个实例。即一个类只有一个对象实例。
Java中单例模式的定义:一个类有且仅有一个实例,并且自行实例化向整个系统提供。
单例模式的特点:
1.单例类只能有一个实例。
2.单例类必须自己创建自己的唯一实例。
3.单例类必须给所以其他对象提供这一实例
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。
单例模式的应用场景:
1.数据库连接池,为了避免资源浪费,一般都是采用单例模式
2.多线程的线程池的设计一般也是采用单例模式
3.Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
4.Application
5.网站的计数器,一般也是采用单例模式实现,否则难以同步。
单例模式的构建方式:
-
饿汉式:在程序启动或单例模式类被加载的时候,实例就已经被创建。
-
懒汉式:当程序第一次访问实例时才进行创建,就是说在用的时候才会去创建
饿汉式:(常用)
1.代码实现
public class Singleton{
private Singleton(){
//无参构造方法
}
private static Singleton instance = new Singleton();
public static Singleton getInstance(){
return instance;
}
}
2.测试类
public class Test {
public static void main(String[] args) {
for (int i = 0; i <10000 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
}
}
}
3.测试结果:10000个都是@5746ac53,说明是饿汉式是线程安全的(饿汉式单例在类加载初始化时就创建好一个静态的对象供外部使用,除非系统重启,这个对象不会改变)
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
SingletonPattern.Singleton@5746ac53
......共10000个
4.饿汉式比较常用!!!
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存;容易产生垃圾对象
懒汉式:
1.代码实现(这么定义的话,懒汉式是不安全的,看测试结果即可知道)
public class Singleton {
private Singleton(){
//无参构造方法
}
private static Singleton instance = null;
public static Singleton getInstance(){
if(instance == null){
return new Singleton();
}
return instance;
}
}
2.测试类
public class Test {
public static void main(String[] args) {
for (int i = 0; i <10000 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Singleton.getInstance());
}
}).start();
}
}
}
3.测试结果:每一个都不一样,说明是懒汉式这样定义线程不安全的。
SingletonPattern.lanHanShi.Singleton@568acee4
SingletonPattern.lanHanShi.Singleton@406fd94d
SingletonPattern.lanHanShi.Singleton@10810
SingletonPattern.lanHanShi.Singleton@4f9d1ec9
SingletonPattern.lanHanShi.Singleton@3f580216
SingletonPattern.lanHanShi.Singleton@5e99dd12
SingletonPattern.lanHanShi.Singleton@141cded9
SingletonPattern.lanHanShi.Singleton@2e193816
SingletonPattern.lanHanShi.Singleton@1985d77d
SingletonPattern.lanHanShi.Singleton@3ca00ad1
SingletonPattern.lanHanShi.Singleton@31ba7625
SingletonPattern.lanHanShi.Singleton@59ceb749
SingletonPattern.lanHanShi.Singleton@4add1757
......10000个
4.那么如何才能让懒汉式变成线程安全的呢?那就只能对getInstance()方法加synchronized同步锁咯
public class Singleton {
public Singleton(){
//无参构造方法
}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){//此处加了synchronized锁
if(instance == null){
instance = new Singleton();
}
return instance;
}
}
加synchronized之后,这样就绝对没有任何问题了,绝对是线程安全的了。但是假如有100个线程同时执行,那么,每次去执行getInstance方法时都要先获得锁再去执行方法体,如果没有锁,就要等待,这样子,耗时会明显太长
5.那么还有别的方法吗可以降低耗时吗?
那就是降低synchronized锁的粒度,即synchronized同步锁锁住的代码越少,效率相对更高点。优化后代码如下:
public class Singleton {
public Singleton(){
//无参构造方法
}
private static Singleton instance = null;
public static Singleton getInstance(){
synchronized (Singleton.class){//降低锁的粒度,直接锁住Singleton类
if(instance == null){
instance = new Singleton();
}
}
return instance;
}
}
你以为这样子就是线程安全的了吗?看看测试结果你就知道了。
测试结果:
//测试结果:明显看到有一个是@2f75cfb0,说明还是线程不安全
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@2f75cfb0
SingletonPattern.lanHanShi.Singleton@41ad0c3c
SingletonPattern.lanHanShi.Singleton@41ad0c3c
......10000个
6.那这是为什么呢?降低了锁粒度,怎么还成了线程不安全的了?
现在有两个线程,线程A和线程B,线程A读取instance值为null,此时CPU被线程B抢去了,线程B再来判断instance值为null,于是,它开始执行同步代码块中的代码,对instance进行实例化。此时,线程A获得CPU,由于线程A之前已经判断instance值为null,于是开始执行它后面的同步代码块代码。它也会去对instance进行实例化。这样就导致了还是会创建两个不一样的实例。
7.那这该怎么解决呢?很简单,在同步代码块中instance实例化之前进行判断,如果instance为null,才对其进行实例化。这样,就能保证instance只会实例化一次了。也就是所谓的双重检查加锁机制。
public class Singleton {
public Singleton(){
//无参构造方法
}
private static Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
但是,双重检查加锁机制也并不代码百分百一定没有线程安全问题了。
8.因为,这里会涉及到一个指令重排序问题
可以先参考一下:https://blog.csdn.net/pzxwhc/article/details/48984209
volatile关键字的特性:①无法保证原子性 ②能保证可见性 ③禁止指令重排序
所以此处添加volatile修饰,即可解决该问题。
public class Singleton {
public Singleton(){
//无参构造方法
}
private static volatile Singleton instance = null;
public static synchronized Singleton getInstance(){
if(instance == null){
synchronized (Singleton.class){
if(instance == null){
instance = new Singleton();
}
}
}
return instance;
}
}
9.单例模式,经常见的也就饿汉式和懒汉式两种,还有其他编写方式,在这里就不一一描述了。设计模式其实主要是为了感受它的思想,而不是用过多的代码来展现。
---->如有疑问,请发表评论,或者联系博主:lzb348110175@163.com,欢迎^_^