单例模式
单例模式
什么是单例模式
比如一个团队多个开发者都需要操作日志文件,如果每个人都实例化一个日志对象,会导致不必要的开销。为了解决这类问题,单例模式产生了。单例模式保证一个类仅有一个实例,并提供一个访问它的全局访问点。Windows系统中的回收站就是典型的一种单例模式应用,多次打开回收站不会创建新的窗口。
单例模式的优缺点
优点:
- 节省内存空间。
- 避免了频繁的创建销毁对象,可以提高性能。
- 避免对共享资源的多重占用,简化访问。
- 为整个系统提供一个全局访问点。
缺点:
- 不适于变化频繁的独享。
- 为了节省资源将数据连接池对象设计为单例类,可能会导致共享连接池对象太多,出现连接池溢出。
- 如果实例化的对象长时间不被使用,系统会认为该对象是垃圾而被回收,这可能会导致对象状态的丢失。
单例模式的实现
1、饿汉式:类加载到内存后,就实例化一个单例,JVM保证线程安全
package SingletonPattern;
public class Singleton01 {
private static final Singleton01 INSTANCE = new Singleton01();
private Singleton01(){};
public static Singleton01 getInstance(){return INSTANCE;}
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
Singleton01 m1 = Singleton01.getInstance();
Singleton01 m2 = Singleton01.getInstance();
System.out.println(m1 == m2);
}
}
2、懒汉式:不在类加载的时候初始化,按需初始化
package SingletonPattern;
import org.omg.PortableServer.THREAD_POLICY_ID;
public class Singleton02 {
//不能定义为final型,因为常量需要初始化
private static Singleton02 INSTANCE;
private Singleton02(){
}
public static Singleton02 getInstance(){
if(INSTANCE == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton02();
}
return INSTANCE;
}
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++){
//同一类的不同对象hashcode是不同的
new Thread(()->{
System.out.println(Singleton02.getInstance().hashCode());
}).start();
}
}
}
结果
1184893857
219573733
449584288
1924898670
103512977
585605895
967740059
1802506717
499531361
1416599099
当多线程操作时,懒汉模式会出现,线程不安全的问题,比如A,B两个线程同时首次创建单例对象,A线程进入判空条件,创建对象之前,B线程此时INSTANCE依然为空,此时A,B线程会创建两个对象。
3、在懒汉式上添加同步方法:解决懒汉模式线程不安全的问题
package SingletonPattern;
import org.omg.PortableServer.THREAD_POLICY_ID;
public class Singleton03 {
//不能定义为final型,因为常量需要初始化
private static Singleton03 INSTANCE;
private Singleton03(){
}
public static synchronized Singleton03 getInstance(){
if(INSTANCE == null){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Singleton03();
}
return INSTANCE;
}
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++){
new Thread(()->{
System.out.println(Singleton03.getInstance().hashCode());
}).start();
}
}
}
结果
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
1416599099
4、双重检查法:虽然通过synchronized解决了线程不安全的问题,但是也带了效率的下降。在同步块中添加判空条件,否则A,B线程拿到锁后都会创建一个对象。
package SingletonPattern;
import org.omg.PortableServer.THREAD_POLICY_ID;
public class Singleton04 {
//不能定义为final型,因为常量需要初始化
private static volatile Singleton04 INSTANCE;
private Singleton04(){
}
public static synchronized Singleton04 getInstance(){
if(INSTANCE == null){
synchronized (Singleton04.class) {
if(INSTANCE == null) {
INSTANCE = new Singleton04();
}
}
}
return INSTANCE;
}
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++){
new Thread(()->{
System.out.println(Singleton04.getInstance().hashCode());
}).start();
}
}
}
5、静态内部类方式:静态内部类在类加载的时候不会加载,在调用getInstance时加载,实现了懒加载。
package SingletonPattern;
import sun.applet.AppletResourceLoader;
/**
* 静态内部类方式
*/
public class Singleton05 {
private Singleton05(){};
private static class Singleton05Holder{
private final static Singleton05 INSTANCE = new Singleton05();
}
public static Singleton05 getInstance(){
return Singleton05Holder.INSTANCE;
}
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++){
new Thread(()->{
System.out.println(Singleton05.getInstance().hashCode());
}).start();
}
}
}
6、枚举实现:枚举类是一个抽象类,没有构造方法,只能有一个实例,不仅可以解决线程同步,还可以防止反序列化。
package SingletonPattern;
public enum Singleton06 {
INSTANCE;
public void method(){
System.out.println("Happy Everyday");
}
public static void main(String[] args) {
for(int i = 0; i < 10; i++){
new Thread(()->{
System.out.println(Singleton05.getInstance().hashCode());
}).start();
}
}
}