This article continues to introduce the singleton pattern in the series of 23 design patterns.
Concept:
The singleton pattern is a common design pattern in Java. There are several ways to write the singleton pattern. Here are three main types: lazy-style singleton, hungry-style singleton, and registration-style singleton. .
The singleton pattern has the following characteristics:
1. A singleton class can only have one instance.
2. The singleton class must create its own unique instance.
3. The singleton class must provide this instance to all other objects.
The singleton pattern ensures that a class has only one instance, instantiates itself and provides this instance to the entire system. In computer systems, thread pools, caches, log objects, dialog boxes, printers, and graphics card driver objects are often designed as singletons. These applications all have more or less the functionality of resource managers. Each computer can have several printers, but there can only be one Printer Spooler to prevent two print jobs from being output to the printer at the same time. Each computer can have several communication ports, and the system should centrally manage these communication ports to prevent one communication port from being called by two requests at the same time. In short, the purpose of choosing the singleton mode is to avoid inconsistent states and avoid long-term policies.
1. Lazy Singleton
//懒汉式单例类.在第一次调用的时候实例化自己 public class Singleton { private Singleton() {} private static Singleton single=null; //静态工厂方法 public static Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; } }
Singleton avoids the class from being instantiated externally by limiting the construction method to private. Within the scope of the same virtual machine, the only instance of Singleton can only be accessed through the getInstance() method.
(In fact, it is possible to instantiate a class with a private constructor through the Java reflection mechanism, which will basically invalidate all Java singleton implementations. This issue will not be discussed here. Let’s just ignore the reflection mechanism. Does not exist.)
But the above lazy-style singleton implementation does not consider thread safety issues. It is thread-unsafe. Multiple Singleton instances are likely to appear in a concurrent environment. To achieve thread safety, there are three ways: It is a transformation of the getInstance method to ensure the thread safety of lazy-style singletons. If you are new to singleton mode and don’t know much about thread safety, you can skip the following three small items and go to hungry-style singletons. For example, wait until you finish reading before thinking about thread safety issues:
1. Add synchronization to the getInstance method
public static synchronized Singleton getInstance() { if (single == null) { single = new Singleton(); } return single; }
2. Double check locking
public static Singleton getInstance() { if (singleton == null) { synchronized (Singleton.class) { if (singleton == null) { singleton = new Singleton(); } } } return singleton; }
3. Static inner class
public class Singleton { private static class LazyHolder { private static final Singleton INSTANCE = new Singleton(); } private Singleton (){} public static final Singleton getInstance() { return LazyHolder.INSTANCE; } }
This is better than the above 1 and 2 are better, not only achieving thread safety, but also avoiding the performance impact caused by synchronization.
2. Hungry-style singleton
//饿汉式单例类.在类初始化时,已经自行实例化 public class Singleton1 { private Singleton1() {} private static final Singleton1 single = new Singleton1(); //静态工厂方法 public static Singleton1 getInstance() { return single; } }
Hungry-Han style has already created a static object for system use when the class is created, and will not change it in the future, so it is inherently thread-safe. of.
3. Registered singleton (can be ignored)
//类似Spring里面的方法,将类名注册,下次从里面直接获取。 public class Singleton3 { private static Map<String,Singleton3> map = new HashMap<String,Singleton3>(); static{ Singleton3 single = new Singleton3(); map.put(single.getClass().getName(), single); } //保护的默认构造子 protected Singleton3(){} //静态工厂方法,返还此类惟一的实例 public static Singleton3 getInstance(String name) { if(name == null) { name = Singleton3.class.getName(); System.out.println("name == null"+"--->name="+name); } if(map.get(name) == null) { try { map.put(name, (Singleton3) Class.forName(name).newInstance()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } return map.get(name); } //一个示意性的商业方法 public String about() { return "Hello, I am RegSingleton."; } public static void main(String[] args) { Singleton3 single3 = Singleton3.getInstance(null); System.out.println(single3.about()); } }
The registered singleton actually maintains a set of instances of the singleton class and stores these instances in a Map (registration book). For instances that have been registered, they are returned directly from the Map. For those that have not been registered, they are registered first and then returned.
Here I have marked the registration-style singleton as negligible. From my understanding, first of all, it is used less often. In addition, the internal implementation still uses the Chinese-style singleton because of the static method block and its singleton. Examples are instantiated when the class is loaded.
The difference between Hungry Man style and Lazy Man style
From the name, Hungry Man and Lazy Man,
Hungry Man means that once the class is loaded, the singleton is initialized to ensure that when getInstance, the singleton already exists Yes,
The lazy man is lazy and only goes back to initialize the singleton when getInstance is called.
In addition, the following two methods are distinguished from the following two points:
1. Thread safety:
Hungry Chinese style is inherently thread-safe and can be directly used for multi-threading without problems,
The lazy style itself is not thread-safe. There are several ways to achieve thread safety, namely 1, 2, and 3 above. These three implementations have some differences in resource loading and performance.
2. Resource loading and performance:
Hungry Chinese style instantiates a static object when the class is created. Regardless of whether this singleton will be used later, it will occupy a certain amount of memory, but accordingly , the speed will be faster when it is called for the first time, because its resources have been initialized,
and the lazy style, as the name suggests, will delay loading, and the object will not be instantiated until the first time the singleton is used. The first Initialization needs to be done during the first call. If there is a lot of work to be done, there will be some delay in performance, and then it will be the same as the hungry Chinese style.
As for the three implementations 1, 2, and 3, there are some differences.
The first one adds synchronization to the method call. Although it is thread safe, synchronization is required every time, which will affect performance. After all, 99% of In this case, there is no need for synchronization.
In the second type, two null checks are done in getInstance to ensure that synchronization is only performed when the singleton is called for the first time. This is also thread-safe and avoids the need to Performance loss of simultaneous synchronization
The third method uses the classloader mechanism to ensure that there is only one thread when initializing the instance, so it is thread-safe and has no performance loss, so generally I tend to use this one.
什么是线程安全?
如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
或者说:一个类或者程序所提供的接口对于线程来说是原子操作,或者多个线程之间的切换不会导致该接口的执行结果存在二义性,也就是说我们不用考虑同步的问题,那就是线程安全的。
应用
以下是一个单例类使用的例子,以懒汉式为例,这里为了保证线程安全,使用了双重检查锁定的方式:
public class TestSingleton { String name = null; private TestSingleton() { } private static volatile TestSingleton instance = null; public static TestSingleton getInstance() { if (instance == null) { synchronized (TestSingleton.class) { if (instance == null) { instance = new TestSingleton(); } } } return instance; } public String getName() { return name; } public void setName(String name) { this.name = name; } public void printInfo() { System.out.println("the name is " + name); } }
可以看到里面加了volatile关键字来声明单例对象,既然synchronized已经起到了多线程下原子性、有序性、可见性的作用,为什么还要加volatile呢,原因已经在下面评论中提到,
还有疑问可参考http://www.iteye.com/topic/652440
和http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
public class TMain { public static void main(String[] args){ TestStream ts1 = TestSingleton.getInstance(); ts1.setName("jason"); TestStream ts2 = TestSingleton.getInstance(); ts2.setName("0539"); ts1.printInfo(); ts2.printInfo(); if(ts1 == ts2){ System.out.println("创建的是同一个实例"); }else{ System.out.println("创建的不是同一个实例"); } } }
运行结果:
结论:由结果可以得知单例模式为一个面向对象的应用程序提供了对象惟一的访问点,不管它实现何种功能,整个应用程序都会同享一个实例对象。
对于单例模式的几种实现方式,知道饿汉式和懒汉式的区别,线程安全,资源加载的时机,还有懒汉式为了实现线程安全的3种方式的细微差别。