单例模式简单的说就是在整个运行环境下都只存在一个对象。
单例模式实现的方式有如下几种:
立即加载/饿汉模式
立即加载指的是使用类的时候就已经将对象加载完毕
立即加载有”紧急、急迫“的意味,所以也叫做 饿汉模式
代码实现:
*/
public class MyObject {
private static MyObject myObject=new MyObject();
public MyObject() {
}
public static MyObject getInstance(){
return myObject;
}
}
延迟加载/懒汉模式
延迟加载是指调用 get()方法时实例才被创建,常见的实现是在get()方法中进行new实例化
实现代码:
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance(){
//延迟加载
if(myObject!=null){
}else{
myObject=new MyObject();
}
return myObject;
}
}
多线程下的单例模式
上面那样的写法在普通情况下是没有问题的,但是在多线程模式下可能出现非线程安全问题。
饿汉模式的线程安全问题
如果我们在饿汉模式下要给对象设置值:
public class MyObject {
private static MyObject myObject=new MyObject();
private String username;
private String password;
public MyObject() {
}
public static MyObject getInstance(String username,String password){
myObject.username=username;
myObject.password=password;
return myObject;
}
@Override
public String toString() {
return "账号是"+username+" 密码是"+password;
}
}
线程类:
public class MyThread extends Thread {
public String username;
public String password;
@Override
public void run() {
System.out.println(MyObject.getInstance(username,password));
super.run();
}
}
运行类:
public class Run {
public static void main(String[] args) {
MyThread[] threads = new MyThread[10];
for (int i = 0; i < 10; i++) {
threads[i]=new MyThread();
threads[i].username=""+i;
threads[i].password=""+i;
}
for (int i = 0; i < 10; i++) {
threads[i].start();
}
}
}
我们想的是连续打印十行,每一行从0-9这样打印下来,但是这种多线程情况下,有些值会被覆盖:
‘
懒汉模式的线程安全问题:
public class MyObject {
private static MyObject myObject;
public MyObject() {
}
public static MyObject getInstance(){
//延迟加载
if(myObject!=null){
}else{
myObject=new MyObject();
}
return myObject;
}
}
public class MyThread extends Thread {
@Override
public void run() {
System.out.println(MyObject.getInstance());
super.run();
}
}
public class Run {
public static void main(String[] args) {
// MyThread t1 = new MyThread();
// t1.start();
MyThread[] ts=new MyThread[10];
for (int i = 0; i < 10; i++) {
ts[i]=new MyThread();
}
for (int i = 0; i < 10; i++) {
ts[i].start();
}
}
}
我们原意是每个线程获取到的对象都是一样的,所以打印的值都一样,但是实际运行如下:
可以看到,得到的对象并不全部相同
饿汉加载的线程安全解决方案
饿汉模式下的问题是因为每个线程都需要设置值,所以解决方案只能是禁止设置值,值应该在初始化的时候就确定好
懒汉加载的线程安全解决方案
使用synchronized关键字
直接在 getInstance() 方法前面增加一个 synchronized 关键字,这样线程之间就是同步运行的,不会产生同时创建对象的情况
DCL锁(双检查锁)
代码实现:
public class MyObject {
private static volatile MyObject myObject;
public MyObject() {
}
public static MyObject getInstance(){
if(myObject != null){
}else {
synchronized (MyObject.class){
if(myObject==null)
myObject=new MyObject();
}
}
return myObject;
}
}
如上,就是使用了synchronized和volatile这两种锁一起实现。
使用synchronized锁是为了防止同时创建对象的情况,使用volatile则是为了可见性,假设A和B线程都进入了else语句块,此时在它们的线程内存内myobject都是null,当A创建完之后,B进入同步块,此时如果没有可见性的话则B线程又会创建一个新的对象,导致线程安全问题