java设计模式-单例模式及其几种实现方式
2020-08-25 本文已影响0人
methon
1.定义
确保一个类只有一个实例,并提供一个全局访问点。
2.实现方式
2.1 声明即创建对象方式
package com.methon.singleton;
public class SigDemo01 {
private static SigDemo01 INSTANCE=new SigDemo01 ();
private SigDemo01 (){};
public static SigDemo01 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
SigDemo01 sig01=SigDemo01 .getInstance();
SigDemo01 sig02=SigDemo01 .getInstance();
/* 判断是否是同一个对象 */
System.out.println(sig01==sig02);
}
}
其中static声明的静态成员INSTANCE只会在类加载的时候创建一次,new一个对象。
私有的构造函数保证只能在此类中调用,其他类中想要获取实例只能调用getInstance()方法。
返回static对象。保证对象的唯一性。上面的代码也可以写成:
package com.methon.singleton;
public class SigDemo02{
private static SigDemo02 INSTANCE;
static {
INSTANCE=new SigDemo02();
}
private SigDemo02(){};
public static SigDemo02 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
SigDemo02 sig01=SigDemo02.getInstance();
SigDemo02 sig02=SigDemo02.getInstance();
/* 判断是否是同一个对象 */
System.out.println(sig01==sig02);
}
}
2.2 懒加载方式
以下为懒加载方式的演变,方法逐渐变得趋于完美
2.2.1 错误的懒加载写法(不支持多线程)
package com.methon.singleton;
public class SigDemo03{
private static SigDemo03 INSTANCE;
private SigDemo03(){
}
public static SigDemo03 getInstance(){
if(INSTANCE==null){
INSTANCE=new SigDemo03();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()-> System.out.println(SigDemo03.getInstance().hashCode())).start();
}
}
}
如果代码中没有多线程,则程序没有任何问题。但存在多线程的情况下,执行到if(INSTANCE==null)后, INSTANCE=new SigDemo03()语句前,可能有多个线程通过了if(INSTANCE==null)的判断,从而不能保证创建出的对象有且仅有1个。
2.2.2 懒加载的正确写法(加锁)
package com.methon.singleton;
public class SigDemo04{
private static SigDemo04 INSTANCE;
private SigDemo04() {
}
public static synchronized SigDemo04 getInstance() {
if (INSTANCE == null) {
INSTANCE = new SigDemo04();
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(SigDemo04.getInstance().hashCode())).start();
}
}
}
对比之前的方法,只是在getInstance()方法上添加了synchronized ,保证了线程安全。但这样做的话降低了程序的执行效率。
2.2.3 为了解决加锁后程序效率变低而采取的错误处理
package com.methon.singleton;
public class SigDemo05{
private static SigDemo05 INSTANCE;
private SigDemo05() {
}
public static SigDemo05 getInstance() {
if (INSTANCE == null) {
synchronized (SigDemo05.class) {
INSTANCE = new SigDemo05 ();
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(SigDemo05 .getInstance().hashCode())).start();
}
}
}
为了提高效率,在判断INSTANCE == null之后在进行加锁的判断。但是请注意,虽然进行了加锁操作,但是最后线程们都会创建新的对象。
2.2.4 为了解决加锁后程序效率变低而采取的正确处理
package com.methon.singleton;
public class SigDemo06{
private static SigDemo06 INSTANCE;
private SigDemo06 () {
}
public static SigDemo06 getInstance() {
if (INSTANCE == null) {
synchronized (SigDemo06 .class) {
if (INSTANCE == null) {
INSTANCE = new SigDemo06 ();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(SigDemo06 .getInstance().hashCode())).start();
}
}
}
此方式虽然有些繁琐,但是正确的做法。在判断INSTANCE == null后进行加锁,之后在进行判空操作。仍为空后在进行new新的对象。避免了只要为空就会创建新的对象。
2.2.5 较完美的静态内部类方式
package com.methon.singleton;
public class SigDemo07{
private SigDemo07() {
}
public static SigDemo07 getInstance() {
return SigDemo07Holder.INSTANCE;
}
private static class SigDemo07Holder {
private final static SigDemo07 INSTANCE = new SigDemo07();
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(SigDemo07.getInstance().hashCode())).start();
}
}
}
此种方式,避免了声明即创建对象的方式,采用静态内部类的方式,需要时才创建对象。比较完美且代码不冗余。
2.2.6 effictive java给出的方式
package com.methon.singleton;
public enum SigDemo08{
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> System.out.println(SigDemo08.INSTANCE.hashCode())).start();
}
}
}
直接设置成了枚举类型,且仅有一个枚举量INSTANCE。
3.总结
正常需要单例模式的情况一般都需要创建对象。所以是否进行懒加载区别相差不大。故方法2.1满足平常使用的要求。除此之外,方法2.2.4, 2.2.5, 2.2.6更为完美,推荐使用。