设计模式之单例模式
这种方式虽然实现了懒加载的功能,但是线程不安全,体现在哪里?在多线程的场景中,不可避免的会有多个线程同时去调用`getExample`方法,并且在第一个线程并没有执行完`new Example01()`,后面的线程进来判断`if (example01 == null) ---> true`,这样就导致了`example01 = new Example01()`这行代码被多个线程多次执行。...
简介
单例(singleton)模式是我们比较常见设计模式,是指在一个系统中确保某对象的实例有且只有一个。
使用场景:
- 在系统中需要频繁的创建、销毁的对象;
- 创建实例过程耗时过多、消耗资源过大的对象(重量级对象),并且这些对象又需要频繁创建使用。
实现单例模式的几种方式
饿汉式(静态变量)
public class Eager01 {
public static void main(String[] args) {
Example01 example01 = Example01.getExample();
Example01 example02 = Example01.getExample();
System.out.println(example01 == example02); // true
}
}
class Example01 {
private static final Example01 example = new Example01();
// 私有构造器方法,防止外部new
Example01() {
}
public static Example01 getExample() {
return example;
}
}
缺点:这种方式并不是懒加载(Lazy Loading) 的方式,也就是不管我们需不需要使用该对象,只要一旦该类被加载,该对象就会被创建在方法区内存中。
如果确定在系统中一定会使用到该实例对象时才推荐使用这种方式。
懒加载:是指只有在使用到该对象时,才去创建该对象的实例,这样就避免了有可能在系统中不需要用到此对象,但一直占用者内存空间。
饿汉式(静态代码块)
public class Eager02 {
public static void main(String[] args) {
Example02 example01 = Example02.getExample();
Example02 example02 = Example02.getExample();
System.out.println(example01 == example02); // true
}
}
class Example02 {
private static Example02 example;
static {
example = new Example02();
}
// 私有构造器方法,防止外部new
private Example02() {
}
public static Example02 getExample() {
return example;
}
}
这种方式和上述饿汉式(静态变量)方式是同一种方式,只是用不同的方法实现了而已,其缺点都是一样的。
懒汉式(线程不安全)
public class Lazy01 {
public static void main(String[] args) {
Example01 example01 = Example01.getExample();
Example01 example02 = Example01.getExample();
System.out.println(example01 == example02); // true
}
}
class Example01 {
private static Example01 example01;
private Example01() {
}
public static Example01 getExample() {
if (example01 == null) {
example01 = new Example01();
}
return example01;
}
}
这种方式虽然实现了懒加载的功能,但是线程不安全,体现在哪里?
在多线程的场景中,不可避免的会有多个线程同时去调用getExample
方法,并且在第一个线程并没有执行完new Example01()
,后面的线程进来判断if (example01 == null) ---> true
,这样就导致了example01 = new Example01()
这行代码被多个线程多次执行。
在实际中不会使用这种方式。
懒汉式(线程安全)
public class Lazy02 {
public static void main(String[] args) {
Example02 example01 = Example02.getExample();
Example02 example02 = Example02.getExample();
System.out.println(example01 == example02); // true
}
}
class Example02 {
private static Example02 example02;
private Example02() {
}
public synchronized static Example02 getExample() {
if (example02 == null) {
example02 = new Example02();
}
return example02;
}
}
为了解决上一段方法中线程不安全的问题,这里在方法上添加了synchronized,通过锁机制来解决线程不安全的问题。
缺点: 这种方式确实保存的线程安全,但是降低了性能。在多线程调用该方式时,每个线程都需要等待上一个线程释放锁,才能执行该方法(上锁放锁的操作影响性能)。其实我们只需要保证example02 = new Example02()
只被一个线程调用,那么后面的线程就可以直接通过getExample
方法拿到该实例对象了,而不需要再去进行上锁放锁的操作,相信看完下段代码就会理解。
不推荐使用。
synchronized作用在静态方法上表示对类对象进行上锁,作用在非静态方法上表示对当前对象进行上锁。
双重检查(线程安全+懒加载)
public class DoubleCheck {
public static void main(String[] args) {
Example03 example01 = Example03.getExample();
Example03 example02 = Example03.getExample();
System.out.println(example01 == example02); // true
}
}
class Example03 {
private static volatile Example03 example03;
private Example03() {
}
public static Example03 getExample() {
if (example03 == null) {
synchronized (Example03.class) {
if (example03 == null) {
example03 = new Example03();
}
}
}
return example03;
}
}
该方式弥补了上一种方式的缺点,使用双重检查。
在同步代码块内添加if语句,确保多个线程进入该方法内,example03 = new Example03()
代码也只会被执行一次!从而后面再进来的线程,根本就不会进入到同步代码块中了,因为在第一层if语句判断完,直接就可以return拿到实例对象了。这样既保证了线程安全,又弥补了损失性能的缺点,这种方式推荐使用。
为什么在创建变量时添加volatile,以及volatile的作用,具体可以参考这篇文章:https://blog.csdn.net/weixin_30342639/article/details/91356608
静态内部类(线程安全+懒加载)
public class InnerClass {
public static void main(String[] args) {
Example04 example01 = Example04.getExample04();
Example04 example02 = Example04.getExample04();
System.out.println(example01 == example02); // true
}
}
class Example04 {
private Example04() {
}
// 静态内部类
private static final class InnerExample04 {
private static final Example04 example04 = new Example04();
}
public static Example04 getExample04() {
return InnerExample04.example04;
}
}
这种方式利用了静态内部类的特点:本类被加载时,其静态内部类不会被加载。只有在调用
InnerExample04.example04
时,该静态内部类InnerExample04
才会被加载。
即满足线程安全,又实现了懒加载,这种方式推荐使用。
枚举
public class SingletonTest {
public static void main(String[] args) {
Example05 example01 = Example05.EXAMPLE;
Example05 example02 = Example05.EXAMPLE;
System.out.println(example01 == example02); // true
}
}
enum Example05 {
EXAMPLE;
Example05() {
}
}
这种方式除了能实现懒加载+线程安全,还能防止使用反序列化重新创建该对象。推荐使用。
更多推荐
所有评论(0)