【JavaEE】wait 、notify 和单例模式
- 一、引言
- 一、wait()方法
- 二、notify()方法
- 三、notifyAll()方法
- 四、wait()和sleep()对比
- 五、单例模式
- 5.1 饿汉模式
- 5.2 懒汉模式
- 5.2 懒汉模式-线程安全(改进)
博客结尾有此篇博客的全部代码!!!
一、引言
假设这里有一个ATM机,然后有人排队取钱,有人排队存钱
ATM机时进去一个人,门自动关门上锁。
现实生活中,假设第一个人进去取钱,但是恰好此时ATM机里面没有钱,第一个人就出来,刚出来他又觉得ATM机里面有钱了(有押运车来往里放 钱),又进去了,他这样来回反复横跳,一直占着ATM机,不让第二个和第三个人使用!
在操作系统中,这种情况是经常发生的,因为资源的调度是随机的。假设这里有多个线程,系统一直调用某一个线程,让其他线程一直处于阻塞等待,这种情况就叫做线程饿死(线程饥饿)。
那么怎么解决这种问题呢?这里就引出了wait()方法!!!
一、wait()方法
- wait() 方法用于使当前线程等待(挂起)(释放当前锁),让其他线程可以使用这把锁。
public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock) {try {System.out.println("wait 之前");lock.wait();System.out.println("wait 之后");} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();}
由于这段代码在执行wait()方法之后就一直等待下去,这里肯定不可能让这段代码一直等待下去。所以就引入一个新的方法唤醒wait()方法------notify()方法。
wait()方法需要注意的:
- 必须搭配synchronized()使用,脱离synchronized()就会抛出异常
- 其他线程调⽤该等待线程的 interrupted ⽅法, 导致 wait 抛出 InterruptedException 异常
- wait()方法也有等待超时
二、notify()方法
这个方法是为了唤醒wait()方法,防止wait()方法一直等待下去!
• ⽅法notify()也要在同步⽅法或同步块中调⽤,该⽅法是⽤来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
• 如果有多个线程等待,则有线程调度器随机挑选出⼀个呈 wait 状态的线程。(并没有 “先来后到”)
• 在notify()⽅法后,当前线程不会⻢上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁。
public class Demo2 {public static void main(String[] args) {Object lock = new Object();Thread t1 = new Thread(() -> {synchronized (lock) {try {System.out.println("wait 之前");lock.wait();System.out.println("wait 之后");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(() -> {synchronized (lock) {lock.notify();System.out.println("唤醒wait()方法");}});t1.start();t2.start();}
}
如果这里有多个wait()方法,只有一个notify()方法,这个唤醒是随机的!!!
三、notifyAll()方法
notify⽅法只是唤醒某⼀个等待线程. 使⽤notifyAll⽅法可以⼀次唤醒所有的等待线程.
public static void main(String[] args) {Object lock1 = new Object();Object lock2 = new Object();Thread t3 = new Thread(() -> {synchronized (lock1) {try {System.out.println("t3 wait()方法之前");lock1.wait();System.out.println("t3 wait()方法之后");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t1 = new Thread(() -> {synchronized (lock1) {try {System.out.println("t1 wait()方法之前");lock1.wait();System.out.println("t1 wait()方法之后");} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2 = new Thread(() -> {synchronized (lock1) {lock1.notifyAll();System.out.println("唤醒wait()方法");}});t3.start();t1.start();t2.start();}
}
wait()、notify()、notifuAll()三种方法都是Object类中的方法!!!
四、wait()和sleep()对比
相同:
- 都可以让线程放弃一段时间。
不同:
wait:
- 是Object类中的方法
- 需要搭配synchronized使用
- 用于线程间的协作
- 释放锁,允许其他线程运行
- 需要在同步块中调用
- 可以通过 notify() 或超时唤醒
sleep:
- 是Thread的静态方法
- 用于简单的延时操作
- 不释放锁
- 可以在任何地方调用
- 只能通过睡眠时间结束或中断唤醒
五、单例模式
设计模式(Design Patterns)是软件工程中用于解决常见问题的可复用的解决方案。它们是经过验证的最佳实践,能够帮助开发者设计出更灵活、可维护和可扩展的代码。
单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建多个实例。
Thread t1=new Thread();t1就是创建的实例。
单例模式具体的实现方式有很多,最常见的是“饿汉”和“懒汉”两种。
5.1 饿汉模式
类加载的同时,创建实例
class MyDesign{private static MyDesign obj = new MyDesign();private MyDesign(){}public static MyDesign getInstance(){return obj;}
}
5.2 懒汉模式
类加载的时候不创建实例. 第⼀次使⽤的时候才创建实例.
class MyDesignF{private static MyDesignF obj = null;private MyDesignF(){}public static MyDesignF getInstance(){if(obj == null){obj = new MyDesignF();}return obj;}
}
饿汉模式不会出现线程安全问题;懒汉模式会出现线程安全问题!
饿汉模式:没有修改操作,所以不会出现线程安全问题。
懒汉模式:有修改操作,所以线程不安全。
5.2 懒汉模式-线程安全(改进)
- 加synchronized
class MyDesignF{private static MyDesignF obj = null;private MyDesignF(){}public static MyDesignF getInstance(){synchronized (MyDesignF.class){if(obj == null){obj = new MyDesignF();}}return obj;}
}
- 双重if判断,降低锁竞争的频率
class MyDesignF{private static MyDesignF obj = null;private MyDesignF(){}public static MyDesignF getInstance(){if(obj == null){synchronized (MyDesignF.class){if(obj == null){obj = new MyDesignF();}}}return obj;}
}
- 给obj加上volatile
class MyDesignF{private volatile static MyDesignF obj = null;private MyDesignF(){}public static MyDesignF getInstance(){if(obj == null){synchronized (MyDesignF.class){if(obj == null){obj = new MyDesignF();}}}return obj;}
}
volatile关键字作用:(防止指令重排序)
在懒汉模式中,obj = new MyDesignF();也可以分为三步:
- 分配内存空间。----1
- 调用构造函数初始化对象。----2
- 将内存地址赋值给 obj 变量。----3
正常情况下是1-》2-》3,但如果发生指令重排序就会出现1-》3-》2,先将内存地址赋值给obj,但还没有进行初始化对象,此时如果另一个线程访问obj,还未完全初始化的对象,那么此时就出现错误!!!
此篇博客的全部代码!!!