单例模式分为懒汉式和饿汉式两种
在有些系统中,为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式. 例如,Windows 中只能打开一个
任务管理器,这样可以避免因打开多个任务管理器窗口而造成内存资源的浪费,
或出现各个窗口显示内容的不一致等错误。
单例模式有 3 个特点:
1. 单例类只有一个实例对象;
2. 该单例对象必须由单例类自行创建;
3. 单例类对外提供一个访问该单例的全局访问点;
懒汉式单例模式:
在类加载时便进行单例对象的创建,不会因为线程产生不安全原因
public class Wonder {
public static Wonder wonder=new Wonder();
private Wonder(){}
public static Wonder getWonder(){
return wonder;
}
}
饿汉式单例模式:
在使用访问方法时才进行单例对象进行生成操作,在多线程的情况下有问题
public class Wonder {
public static Wonder wonder;
private Wonder(){}
public static Wonder getWonder(){
if(wonder==null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
wonder=new Wonder();
}
return wonder;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Wonder.getWonder());
}).start();
}
}
}
所以需要处理线程安全问题,可以给访问方法添加锁
public class Wonder {
public static Wonder wonder;
private Wonder(){}
public static synchronized Wonder getWonder(){
if(wonder==null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
wonder=new Wonder();
}
return wonder;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Wonder.getWonder());
}).start();
}
}
}
但是虽然给方法加锁以后,线程安全解决了,但是效率下去了 ,所以我们可以给代码块加锁,并使用双重判断,再给单例对象使用volatile关键字,防止指令重排生成半成品对象
//懒汉模式
public class Wonder1 {
public static volatile Wonder1 wonder1; //防止指令重排出现半成品对象
private Wonder1(){};
public static Wonder1 getWonder1(){
if(wonder1==null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//防止多个线程进入到此等待锁释放
synchronized (Wonder1.class){
//当上一层的线程获得锁后再次判断是否已经初始化过
if(wonder1==null){
wonder1=new Wonder1();
}
}
}
return wonder1;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
System.out.println(Wonder1.getWonder1());
}).start();
}
}
}
JAVA 中的例子:
Runtime 类
Jdk 中的源码 Runtime 类就是一个单例类,利用 Runtime 类可以启动新的进程或进行相关运行时环境的操作。比如,取得内存空间以及释放垃圾空间。
Runtime 类属于典型的单例设计。