定义:
1.顾名思义单例模式就是一个类只提供一个实例对象给外部访问。确保全局使用该类所对应的对象永远都是一个对象。
应用场景:
1.各种对象工厂
2.各种管理器
实现方案:
1.(推荐使用)饿汉式
通过提前创建对象,将对象唯一化,提供给外部访问的只有已创建的对象的返回的方法,同时屏蔽掉外部构建新对象的手段。
他的线程安全性是由jvm保证的。(jvm 会保证类只会被load到内存一次,而静态对象的初始化也是在load的过程中的.
所以jvm初始化静态对象时只会初始化一次,保证了线程的安全性)。
但是她的对象会直接创建不管有没有使用(缺点)
public class Factory {
private static final Factory factory = new Factory();
private Factory() {
}
public static Factory getInstance(){
return factory;
}
public static void main(String[] args) {
Factory instance = Factory.getInstance();
Factory instance1 = Factory.getInstance();
System.out.println(instance == instance1);//true
}
}
2.懒汉式
与饿汉式相比,懒汉式是当外部有调用的时候,才会去构建对象,也就是我们说的懒加载。
public class Factory2 {
private static Factory2 factory;
private Factory2() {
}
public static Factory2 getInstance(){
if(factory == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
factory = new Factory2();
}
return factory;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory2.getInstance())).start();
}
}
}
此种方案有安全性问题,当多线程访问时,该实例对象可能不是同一个对象,睡眠时间是为了模拟对象的构建逻辑,更明显的观察出安全性问题。
安全性问题可以通过加锁的方式进行解决,比如在获取对象的方法上加锁
public class Factory3 {
private static Factory3 factory;
private Factory3() {
}
public static synchronized Factory3 getInstance(){
if(factory == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
factory = new Factory3();
}
return factory;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory3.getInstance())).start();
}
}
}
但是,该方法也会面临锁的粒度过大,导致性能降低,为了更好的使用,通过降低锁粒度的方式,提升效率。
public class Factory4 {
private static Factory4 factory;
private Factory4() {
}
public static Factory4 getInstance(){
if(factory == null){
synchronized (Factory4.class) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
factory = new Factory4();
}
}
return factory;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory4.getInstance())).start();
}
}
}
该方案虽然降低了锁的力度,但是没法保证单例的问题,因为当线程持有锁的时候,可能有多个线程竞争,当进程的竞争随着前面锁的释放,会继续执行,导致对象被多次创建。因此引出了创建单的dcl方法(双重检查),解决上一个步骤中竞争锁的线程会多次创建对象,同时为了解决指令重排问题,加上volatile,解决对象没有初始化的时候就已经将内存地址指向了该引用
public class Factory5 {
private volatile static Factory5 factory;
private Factory5() {
}
public static Factory5 getInstance(){
if(factory == null){
synchronized (Factory5.class) {
if(factory != null){
return factory;
}
try {
Thread.sleep(1);
} catch (InterruptedException e) {
}
factory = new Factory5();
}
}
return factory;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory5.getInstance())).start();
}
}
}
至此关于单例模式的一些讲解到这已经结束了,在实际开发中还是建议使用饿汉式,因为它简单明了。对于内存而言,现在不是早期的时候,代价没那么高,也没有那么紧张。
延伸另外两种其他写法1:通过静态内部类,2:使用枚举
public class Factory6 {
private Factory6() {
}
public static class FactMa{
private static final Factory6 factory = new Factory6();
}
public static Factory6 getInstance(){
return FactMa.factory;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory5.getInstance())).start();
}
}
}
public enum Factory7 {
INSTANCE;
public void m(){};
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(Factory7.INSTANCE.hashCode())).start();
}
}
}