单例模式(Java)

单例模式分为饿汉式和懒汉式

饿汉式实现比较简单

//饿汉式
class SingleTon1{
private static SingleTon1 instance=new SingleTon1();
private SingleTon1(){}
public static SingleTon1 getInstance(){
return instance;
}
}
饿汉式存在的优缺点:
优点是不用考虑多线程问题,因为在类加载时已初始化。
缺点:1.长期占用一块内存,存在资源浪费;2.程序启动时需创建对象,会影响启动速度。

懒汉式
class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
public static SingleTon2 getInstance(){
if(instance == null){
instance=new SingleTon2();
}
return instance;
}
}
测试
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance1);
SingleTon2 instance2 = SingleTon2.getInstance();
System.out.println(instance1);
返回相同的地址,实现了单例

多线程测试
class myrunable implements Runnable{
@Override
public void run(){
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance1);
}
}
//多线程测试
for (int i=0;i<10;i++){
Thread th=new Thread(new myrunable());
th.start();
}
测试结果发现存在不同的地址,这是因为多个线程同时访问getInstance时,new了多次。需增加多线程锁控制多线程创建对象
class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
synchronized public static SingleTon2 getInstance(){
if(instance == null){
instance=new SingleTon2();
}
return instance;
}
}
class myrunable implements Runnable{
@Override
public void run(){
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance1);
}
}
public class MySingleTon {
public static void main(String[] args) {
//多线程测试
for (int i=0;i<10;i++){
Thread th=new Thread(new myrunable());
th.start();
}
}
}
测试发现返回地址变成了1个,说明满足多线程时,创建同一对象的要求。但是直接在该方法中增加锁,会影响多线程访问效率。需优化
class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
public static SingleTon2 getInstance(){
synchronized(SingleTon2.class){
if(instance == null){
instance=new SingleTon2();
}
}
return instance;
}
}
class myrunable implements Runnable{
@Override
public void run(){
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance1);
}
}
public class MySingleTon {
public static void main(String[] args) {


// SingleTon2 instance1 = SingleTon2.getInstance();
// System.out.println(instance1);
// SingleTon2 instance2 = SingleTon2.getInstance();
// System.out.println(instance1);

//多线程测试
for (int i=0;i<10;i++){
Thread th=new Thread(new myrunable());
th.start();
}
}
}

在这个 getInstance 方法中,synchronized 关键字是用来锁定 SingleTon2.class 这个类的 Class 对象。这意味着在同一时间,只有一个线程能够进入 synchronized 代码块,其他尝试进入该代码块的线程将被阻塞,直到锁被释放。

这里的 synchronized(SingleTon2.class) 确保了在多线程环境下,instance 变量的初始化是线程安全的。也就是说,如果多个线程同时调用 getInstance 方法,它们将串行地执行 synchronized 代码块内的代码,从而确保 instance 只会被初始化一次。

具体来说,synchronized(SingleTon2.class) 的作用是:

  1. 当一个线程进入 synchronized 代码块时,它会获取 SingleTon2.class 的锁。
  2. 如果此时有其他线程试图进入 synchronized 代码块,它们将因为无法获取锁而被阻塞。
  3. 当第一个线程执行完 synchronized 代码块内的代码并退出时,它会释放锁,此时其他线程中的一个将有机会获取锁并继续执行。

这种实现方式被称为“双重检查锁定”(double-checked locking),是一种常用的单例模式实现方法,用于在延迟初始化时提高性能。但是,这种实现方式在 Java 5 及更早版本中可能由于指令重排而出现问题。从 Java 5 开始,通过引入 volatile 关键字可以确保双重检查锁定的正确性。因此,为了完全确保线程安全,你可能会看到 instance 变量被声明为 volatile

class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
public static SingleTon2 getInstance(){
synchronized(SingleTon2.class){
if(instance == null){
instance=new SingleTon2();
}
}
return instance;
}
}
class myrunable implements Runnable{
@Override
public void run(){
SingleTon2 instance1 = SingleTon2.getInstance();
System.out.println(instance1);
}
}
public class MySingleTon {
public static void main(String[] args) {
//多线程测试
for (int i=0;i<10;i++){
Thread th=new Thread(new myrunable());
th.start();
}
}
}
但是这样处理,会造成多线程访问的时候每次都被锁定,需优化
class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
public static SingleTon2 getInstance(){
if(instance == null){
synchronized(SingleTon2.class){
instance=new SingleTon2();
}
}
return instance;
}
}
此时又会遇到新的问题,如果有2个线程走到了
if(instance == null){
synchronized(SingleTon2.class){
instance=new SingleTon2();
}
}
其中A线程阻塞,B线程锁定,那么B执行完之后,A进入还是会new,所以需要再增加一层判断
class SingleTon2{
public static SingleTon2 instance;
private SingleTon2(){}
public static SingleTon2 getInstance(){
if(instance == null){
synchronized(SingleTon2.class){
if(null == instance){
instance=new SingleTon2();
}
}
}
return instance;
}
}
这就实现了双重检测锁

此方式优缺点分析:优点:实现延迟初始化和线程安全;缺点:多线程竞争资源时,仍然面临性能问题。有没有既能解决延迟初始化又能解决线程安全的单例,有:
class SingleTon2{
private static class SingleInner{
public static SingleTon2 instance=new SingleTon2();
}
public static SingleTon2 getInstance(){
return SingleInner.instance;
}
}
需要的时候才会初始化
posted @ 2024-03-25 18:50  dmfsimle  阅读(3)  评论(0编辑  收藏  举报