单例模式,即单个实例.进程中的某个类有且只有一个对象(不会new多个对象出来),这样的对象就称为单例.如何做才能保证某个类只有一个实例呢?可以通过一些编码上的技巧,使在创建多个对象的时候直接编译出错.下面主要介绍两种写法:饿汉模式与懒汉模式
1.饿汉模式,代码如下
package Demo6;
//单例模式,即单个实例
//单例模式中的饿汉模式
class Singleton{
public static Singleton instance=new Singleton();
public static Singleton getinstance(){
return instance;
}
private Singleton(){}
}
public class Demo1{
public static void main(String[] args){
Singleton t1=Singleton.getinstance();
//Singleton t2=new Singleton();
}
}
首先
public static Singleton instance=new Singleton();
在Singleton类中直接new一个Singleton对象,并用static 修饰,static 成员初始化时机是在类加载的时候.可以认为当JVM启动的时候,就立即加载.static 修饰的是类属性,就是在"类对象"上的,每个类的类对象在JVM中只有一个,里面的静态方法只有一份.
接着
public static Singleton getinstance(){
return instance;
}
后续需要使用这个类的实例的时候,就可以通过getinstance这个方法来获取了,而不是重新创建实例.
最后
private Singleton(){}
将构造方法设置为private类型,后续想再次new新的实例的时候,就会报错.
总结一下,饿汉模式就是在类加载的时候就已经初始化好了单个实例,无论后续是否会调用,这个单例已经存在了.
2.懒汉模式,代码如下
package Demo6;
class Singletonlazy{
//啥时候调用就啥时候创建,如果不调用,就不创建了.
public static Singletonlazy instance=null;
public static Singletonlazy getInstance(){
if(instance==null){
instance=new Singletonlazy();
}
return instance;
}
private Singletonlazy(){
}
}
public class Demo2 {
public static void main(String[] args){
Singletonlazy t1=Singletonlazy.getInstance();
System.out.println(t1);
}
}
首先
public static Singletonlazy instance=null;
将instance置为null,即在未调用的时候不会创建实例.
接着
public static Singletonlazy getInstance(){
if(instance==null){
instance=new Singletonlazy();
}
return instance;
}
调用getInstance函数,首先会判断是否已经实例化了对象,如果已经实例化了就返回,如果没有就实例化对象.
最后
private Singletonlazy(){}
将构造方法设置为私有的,通过new一个对象这样的方法来创建实例会报错.
对比这两种写法,如果代码中存在多个单例类,使用饿汉模式,就会导致这些实例都是在程序启动的时候扎堆创建的,可能把程序启动时间拖慢.如果是懒汉模式,啥时候调用,啥时候启动,调用时机是分散的,化整为零,不太容易卡顿.
接着思考一下,这两种写法哪一种是线程安全的,哪一种是线程不安全的.
首先是饿汉模式
class Singleton{
public static Singleton instance=new Singleton();
public static Singleton getInstance(){
return instance;
}
private Singleton(){}
}
单例的创建是在java进程启动的时候(比main线程还早),那么后续线程中调用getInstance的时候,意味着上述示例早就已经有了.每个线程的getInstance只做了一件事,就是读取静态变量的值.前面介绍的线程不安全的几个方面1.线程抢占式执行 2.多个线程修改同一个变量 3.修改操作不是原子的.饿汉模式只是涉及到线程 多个线程读取同一个变量,是线程安全的.
接着是懒汉模式
class Singletonlazy{
public static Singletonlazy instance=null;
public static Singletonlazy getInstance(){
if(instance==null){
instance=new Singletonlazy();
}
return instance;
}
private Singletonlazy(){
}
}
当多个线程同时调用读操作的时候,如果此时示例还未被创建instance为空,这些线程就会都进入new() Singletonlazy的操作,前面介绍过了当多个线程同时修改一个变量的时候,容易引发线程安全问题.通过一个图理解一下此处的线程安全问题
如果按照上述的顺序执行,当t1执行完之后,因为instance只有一份,t1创建的对象的地址会被第二个创建的地址覆盖.虽然覆盖了但是客观上还是创建过的,是客观存在的,有资源开销的.
尝试一下给创建实例操作加锁.如下:
class Singletonlazy{
public static Singletonlazy instance=null;
public static Singletonlazy getInstance(){
Object object=new Object();
if(instance==null){
synchronized(object) {
instance = new Singletonlazy();
}
}
return instance;
}
private Singletonlazy(){
}
}
在这里加锁依然会出现上图中的问题,可以尝试将锁加载if的外面将判断和创建操作原子化,就可以避免上图中出现的问题了.观察上述代码可以发现,每一次调用getInstance()方法都需要加锁操作,但懒汉模式只是在最开始调getInstance()的时候会存在线程安全问题,一旦把实例创建好了,后续在调用,就不会出现线程安全问题了.每次调用时都需要加锁的操作,会有时间开销.使代码性能降低.可以进行改进如下:
class Singletonnlazy{
public volatile static Singletonnlazy instance=new Singletonnlazy();
static Object object=new Object();
public static Singletonnlazy getInstance() {
if (instance == null) {
synchronized (object) {
if (instance == null) {
instance = new Singletonnlazy();
}
return instance;
}
}
return instance;
}
}
在外面加一层判断,如果此时实例已经创建就直接返回不需要再进行加锁操作了. 这就是最后的线程安全的懒汉模式的写法.