饿汉式,安全的
public class SingletonExample2 {
//私有化构造函数
private SingletonExample2(){ }
//单例对象
private static SingletonExample2 instance = new SingletonExample2();
//静态工厂方法
public static SingletonExample2 getInstance(){
return instance;
}
}
懒汉式 不安全的
public class SingletonExample1 {
//私有化构造函数
private SingletonExample1(){
}
//单例对象
private static SingletonExample1 instance = null;
//静态工厂方法
public static SingletonExample1 getInstance(){
if (instance == null){
instance = new SingletonExample1();
}
return instance;
}
从写法上我们可以看出,饿汉模式是线程安全的,但它的性能上会大大折扣。那么我们能否也让懒汉模式也变得线程安全呢?答案是可以的
方法一.在方法上加上同步锁
直接在获取实例的方法上加上synchronized关键字
public class SingletonExample3 {
//私有化构造函数
private SingletonExample3(){
}
//单例对象
private static SingletonExample3 instance = null;
//静态工厂方法
public synchronized static SingletonExample3 getInstance(){
if (instance == null){
instance = new SingletonExample3();
}
return instance;
}
}
虽说该方法是线程安全的,但其性能也和饿汉模式差不多,在性能上会大大折扣,别急我们接着看
方法二.使用双重校验加同步锁机制
public class SingletonExample4 {
//私有化构造函数
private SingletonExample4(){
}
//指令重排问题:
//1.分配内存空间
//2.初始化对象
//3.instance = memory设置instance指向刚分配的内存
//单例对象
private static SingletonExample4 instance = null;
//静态工厂方法
public static SingletonExample4 getInstance(){
if (instance == null){ //双重检测机制
synchronized (SingletonExample4.class) { //同步锁
if (instance == null){
instance = new SingletonExample4();
}
}
}
return instance;
}
}
通过两次判断,确保创建的对象只能有一个,但这种方法还是存在线程安全的问题的。在单线程的情况下,以上的代码没有丝毫问题,但在多线程的情况下,就会存在指令重排问题
image.png
当A、B线程达到以上位置时,发生指令重排,在A线程执行到指令2(将对象的引用指向新分配的空间)时,刚好CPU被B占用,这样B的对象指向了一个内存空间,但其对象并没有被实例化
我们可以给代码加上一个volatile关键字来防止指令重排
//单例对象
private static volatile SingletonExample4 instance = null;
方法三.使用枚举模式来创建对象
有了以上方法为何还会需要第三种方法呢?那是因为java中还有一种暴力的创建方法,反射,虽然不能通过关键字new来创建对象,但通过反射创建的对象,就不会是单例的了,那么有什么办法可以解决吗?答案是有的,就是使用枚举。
public class SingletonExample7 {
private SingletonExample7(){}
public static SingletonExample7 getInstance(){
return Singleton.INSTANCE.getInstance();
}
private enum Singleton{
INSTANCE;
private SingletonExample7 singleton;
//JVM保证这个方法绝对只调用一次
Singleton(){
singleton = new SingletonExample7();
}
public SingletonExample7 getInstance() {
return singleton;
}
}
}
4、使用静态内部类实现单例模式
DCL解决了多线程并发下的线程安全问题,其实使用其他方式也可以达到同样的效果,代码实现如下:
package org.mlinge.s06;
public class MySingleton {
//内部类
private static class MySingletonHandler{
private static MySingleton instance = new MySingleton();
}
private MySingleton(){}
public static MySingleton getInstance() {
return MySingletonHandler.instance;
}
}
测试1:继承线程
public class Singleton {
//防止指令重排
private static volatile Singleton singleton =null;
private Singleton(){
}
public static Singleton getSingleton() {
if (singleton == null) {//双重检测
synchronized (Singleton.class) {
if (singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
public class SingletonTest extends Thread{
public static void main(String ss[]){
Thread[] tms = new Thread[10];
for (int i = 0;i<tms.length;i++){
SingletonTest singletonTest = new SingletonTest();
singletonTest.setName("线程"+i);
tms[i] = singletonTest;
}
for (int j = 0;j<tms.length;j++){
tms[j].start();
}
}
@Override
public void run(){
System.out.println(Thread.currentThread().getName()+"_"+Singleton.getSingleton().hashCode());
}
测试2:实现runable
public class Singleton {
//防止指令重排
private static volatile Singleton singleton =null;
private Singleton(){
}
public static Singleton getSingleton() {
if (singleton == null) {//双重检测
synchronized (Singleton.class) {
if (singleton==null) {
singleton = new Singleton();
}
}
}
return singleton;
}
}
public class SingletonRunableTest implements Runnable {
public static void main(String ss[]){
Thread[] tms = new Thread[10];
for (int i = 0;i<tms.length;i++){
SingletonRunableTest test = new SingletonRunableTest();
tms[i] = new Thread(test,"线程"+i);
}
for (int j = 0;j<tms.length;j++){
tms[j].start();
}
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"_"+Singleton.getSingleton().hashCode());
}
}
网友评论