JavaEE初阶第九期:解锁多线程,从 “单车道” 到 “高速公路” 的编程升级(七)

发布于:2025-07-19 ⋅ 阅读:(10) ⋅ 点赞:(0)

专栏:JavaEE初阶起飞计划

个人主页:手握风云

目录

一、wait和notify

1.1. wait和sleep的对比

二、多线程案例

2.1. 单例模式


一、wait和notify

1.1. wait和sleep的对比

  • wait的设计是提前唤醒,超时的时间属于是“留后手”,而sleep是到时间才唤醒,虽然也可以通过Interrupt()提前唤醒,但会出现异常。
  • wait需要搭配锁来使用,wait执行时需要先释放锁,当sleep放在synchronized内部时,不会释放锁。

二、多线程案例

2.1. 单例模式

        单例模式是一种设计模式,属于校招中最常考的设计模式之一。设计模式可以看作在下棋中的研究出来的一些固定套路。为了使一些代码能力比较差的人下限能够得到提升,大佬们研究出一些固定套路来应对一些场景的题。

        在多线程编程中,单例模式是控制资源访问的核心设计模式之一。它确保一个类仅有一个实例,并提供全局访问点,避免多线程竞争导致的资源重复创建或状态不一致问题。

        单例模式实现的方式有很多,最常见的是饿汉模式和懒汉模式。

  • 饿汉模式
// 饿汉模式
class Singleton {
    // 创建唯一实例
    // 此处的instance属于static成员,创建时机,就是在类加载的时候
    private static Singleton instance = new Singleton();
    public static Singleton getInstance() {
        return instance;
    }

    private Singleton() {
    }
}

public class Demo1 {
    public static void main(String[] args) {
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);// s1和s2指向同一对象

        Singleton s3 = new Singleton();
    }
}

        如果所有的代码都是一个人写的,那么有没有单例无所谓,就能人为保证只有一个实例,这些规则自己都能遵守,但在实际工作中,多人一起分工协作,某人对某个方法进行封装,别人无法调用,强制去使用某种规则。

  • 懒汉模式
// 懒汉模式
class SingletonLazy {
    public static SingletonLazy instance = null;

    // 第一次使用实例的时候才会创建
    public static SingletonLazy getInstance() {
        if (instance == null) {
            instance = new SingletonLazy();
        }
        return instance;
    }
}

public class Demo2 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
        System.out.println(s1 == s2);
    }
}

        如同洗碗一样,吃完饭就洗就是饿汉模式,吃完饭之后等下次吃饭之前再洗就是懒汉模式。如果第一次吃饭用了4个碗,等下次吃饭需要2个碗,按照懒汉模式,下次吃饭前只需洗两个碗,所以懒汉模式效率更高。假设有一个几个G的大文件,当我们想打开文件查看内容时,如果把整个文件加载显示出来显示给用户,就会有明显的卡顿,但按照懒汉模式,只加载一小部分就显示给用户,效率就会大大提高。

        从线程安全的角度来说,饿汉模式只是针对同一变量进行读取,因此线程是安全的。但懒汉模式下,当多个线程同时检测到instance == null,就可能会有多个线程创建出多个实例。为了解决线程安全的问题,还是利用加锁来解决。

class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        // 注意要把哪些操作打包成原子
        synchronized (locker) {
            if (instance == null) {
                instance = new SingletonLazy();
            }
        }
        return instance;
    }
}

public class Demo3 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
    }
}

        刚才的单例模式只是在第一次调用的时候,才会涉及到线程安全问题,只要对象创建完毕,后序都是直接返回的操作,就不涉及修改了。但上述代码每次调用都会加锁,线程已经安全了,却还要进行加锁,显然是不合理的,因为加锁就可能触发阻塞。

class SingletonLazy {
    private static SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if (instance == null) {
            // 注意要把哪些操作打包成原子
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

public class Demo3 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
    }
}

        看到上面的代码,可能会打破某些人的认知。以前都是一个线程,代码执行下来,一次判定和二次判定逻辑是一样的。但在多线程编程中,一次判定和二次判定的结论可能不同。

        代码写到这里,还是会有一些问题,就是之前提到的指令重排序引起的线程安全问题。指令重排序是编译器的一种优化手段,确保逻辑一致的前提下,通过调整代码的顺序,从而提升效率。上面创建对象的过程可以简化成3个步骤:1.申请内存空间;2.在内存空间上通过构造方法进行初始化;3.将内存地址保存到引用变量中。编译器就可能优化成“132”的顺序,就可能出现问题。

        当先执行步骤3时,instance确实非空了,t2就会提前拿到一个未初始化的对象,当去调用s的属性和方法时,就会讲错就错。此时我们就可以通过volatile关键字避免重排序。

class SingletonLazy {
    private static volatile SingletonLazy instance = null;
    private static Object locker = new Object();
    public static SingletonLazy getInstance() {
        if (instance == null) {
            // 注意要把哪些操作打包成原子
            synchronized (locker) {
                if (instance == null) {
                    instance = new SingletonLazy();
                }
            }
        }
        return instance;
    }
}

public class Demo3 {
    public static void main(String[] args) {
        SingletonLazy s1 = SingletonLazy.getInstance();
        SingletonLazy s2 = SingletonLazy.getInstance();
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到