当前位置:网站首页>【操作系统】线程安全保护机制
【操作系统】线程安全保护机制
2022-08-02 03:33:00 【Money、坤】
1.单例模式下的线程安全
- 单例模式的应用场景
某个类,不应该有多个实例,此时就可以使用单例模式(DataSource就是一个典型的案例,一一个程序中只有一个实例,不应该实例化多个DataSource对象)。如果尝试创建多个实例,编译期就会报错
- 两种典型的单例模式
- 饿汉模式-线程安全
static修饰,在类加载的过程执行实例化,JVM保证了类加载的过程是线程安全的。
/** * 饿汉模式 */
public class singlePattern {
private static singlePattern instance =new singlePattern();
//获取对象实例
public static singlePattern getInstance(){
return instance;
}
//私有构造方法
private singlePattern(){
}
}
- 懒汉模式-线程不安全
类被加载的时候,没有立刻被实例化,第一次调用getInstance的时候,才真正的实例化。
改进后的线程安全版本:
/** * 懒汉模式 * 在需要对象的时候实例化 */
public class lazyPattern {
//当指令重排序的时候会出现问题,就需要对变量使用volatile修饰,保证可见性
private volatile static lazyPattern instance =null;
//synchronized修饰,线程安全
public static lazyPattern getInstance(){
//第一次调用的时候,实例化
if(instance ==null){
//instance还没有初始化时,才会走到这个分支
synchronized (lazyPattern.class){
//加锁之后才能执行
//第一个抢到锁的线程,看到instance 是null,进行初始化
//保证 instance只会被实例化一次
if (instance==null){
instance =new lazyPattern();
}
}
}
return instance;
}
private lazyPattern(){
}
}
如果要是代码一整场都没有调用getInstance,此时实例化的过程也就被省略掉了,又称“延时加载”,一般认为“懒汉模式” 比 “饿汉模式”效率更高。懒汉模式有很大的可能是“实例用不到”,此时就节省了实例化的开销。
- volatile关键字
import java.util.concurrent.TimeUnit;
/** * volatile 演示示例 * volatile 保证内存可见性 */
public class volatileCase {
volatile static boolean quit =false;
static class MyThread extends Thread{
@Override
public void run() {
long r =0;
while (quit ==false){
r++;
}
System.out.println(r);
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread =new MyThread();
thread.start();
TimeUnit.SECONDS.sleep(5);
//在未使用volatile修饰quit变量时,此处的quit的修改,对于子线程来说是不可见的
//所以,即使将quit改为true,对于thread线程来说仍是不可见的
quit=true;
}
}
主线程中对静态变量quit进行了修改,但是对于子线程Mythread来说,quit 的修改是不可见的,此时就需要使用关键字volatile修饰静态变量,保证内存的可见性,子线程中当quit被修改时就会输出变量r的值。
2.线程通知wait和notify
wait操作使线程进入阻塞,notify操作会唤醒阻塞中的线程,两者在使用的过程中都需要搭配synchronized使用。
public static void main(String[] args) throws InterruptedException {
//锁
Object o =new Object();
synchronized (o){
//1.wait()时,会释放锁 2.等待唤醒 3.唤醒后继续加锁
o.wait();
//阻塞状态
System.out.println("除非唤醒,否则永远不会到达!");
}
o.wait()操作会使主线程进入阻塞,同时释放当前锁o,阻塞后的主线程会一直等待被唤醒,唤醒后会继续加锁。
创建一个子线程,唤醒主线程
static class MyThread extends Thread{
private Object o;
public MyThread(Object o){
this.o=o;
}
@Override
public void run() {
synchronized (o){
System.out.println("唤醒阻塞队列!!!");
o.notify();
}
}
}
在主线程中启动子线程:
public class Demo1 {
static class MyThread extends Thread{
private Object o;
public MyThread(Object o){
this.o=o;
}
@Override
public void run() {
synchronized (o){
System.out.println("唤醒阻塞队列!!!");
o.notify();
}
}
}
public static void main(String[] args) throws InterruptedException {
//锁
Object o =new Object();
synchronized (o){
//子线程唤醒
MyThread myThread =new MyThread(o);
myThread.start();
//1.wait()时,会释放锁 2.等待唤醒 3.唤醒后继续加锁
o.wait();
//阻塞状态
System.out.println("除非唤醒,否则永远不会到达!");
}
}
}
此时,主线程被唤醒,打印语句就会执行:
3.生产-消费者模型-阻塞队列
阻塞队列(BlockingQueue) 是一个支持两个附加操作的队列。这两个附加的操作是:在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
基于一对一生产-消费者模型下的阻塞队列:
**
* 自定义阻塞队列
* 生产-消费者模式
* 一个生产者,一个消费者
*/
public class MyArrayBlockingQueue {
//存储元素的数组
private long[] array;
//永远指向队列的第一个元素的索引位置
private int frontIndex;
//永远在队列的最后一个位置的下一个位置
private int rearIndex;
//队列元素的个数
private int size;
public MyArrayBlockingQueue(int capacity){
array =new long[capacity];
frontIndex=0;
rearIndex=0;
size=0;
}
/** * 入队操作 * @param e * @throws InterruptedException */
public synchronized void put(long e) throws InterruptedException {
//先判断数组是否已满,避免假唤醒的情况,使用while
while (array.length==size){
//TODO:
this.wait();
}
//预期队列一定不是满的
array[rearIndex]=e;
rearIndex++;
//数组越界
if (rearIndex ==array.length){
rearIndex=0;
}
size++;
//若有线程阻塞,一定是消费者等待
notifyAll();
}
/** * 出队操作 * @return * @throws InterruptedException */
public synchronized long take() throws InterruptedException {
while (size==0){
//队列为空
wait();
}
long e=array[frontIndex];
frontIndex++;
if (frontIndex==array.length){
frontIndex=0;
}
size--;
//若有线程阻塞,一定是生产者等待
notifyAll();
return e;
}
}
一对一生产-消费者模式演示:
public class Test {
static MyArrayBlockingQueue queue =new MyArrayBlockingQueue(3);
static class MyThread extends Thread {
@Override
public void run() {
Scanner sc =new Scanner(System.in);
long e=sc.nextLong();
//将元素入队
try {
queue.put(e);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread =new MyThread();
thread.start();
//队列为空,主线程会阻塞,等待生产者生产
long e=queue.take();
System.out.println(e);
}
}
主线程中,队列元素为空,消费者(主线程)发生阻塞,此时,主线程(消费者)唤醒生产者,即往队列中添加元素。子线程中,读取键盘输入往队列中添加元素,当队列中添加一个元素后,生产者唤醒消费者(主线程),即可取出队列元素。以上操作,就实现了一个简单的一对一的生产者-消费者模型。
边栏推荐
猜你喜欢
随机推荐
GM8775C MIPI转LVDS调试资料分享
HDMI转MIPI CSI东芝转换芯片-TC358743XBG/TC358749XBG
GM7150,振芯科技,视频解码器,CVBS转BT656/601,QFN32,替换TVP5150/CJC5150
振芯科技GM8285C:功能TTL转LVDS芯片简介
如何用 Lightly 进行 Debug 断点调试?
MC1496乘法器
Personal image bed construction based on Alibaba Cloud OSS+PicGo
为什么D类音频功放可以免输出滤波器
读取FBX文件踩坑清单
2020 - AAAI - Image Inpainting论文导读《Learning to Incorporate Structure Knowledge for Image Inpainting》
TC358860XBG BGA65 东芝桥接芯片 HDMI转MIPI
IDEA2021.2安装与配置(持续更新)
回溯法 & 分支限界 - 2
Modify hosts file using batch script
【土壤湿度传感器与 Arduino 测量土壤湿度】
【心率传感器与Arduino连接读取心率数据】
判断子序列 —— LeetCode-392
【NTC 热敏电阻与 Arduino 读取温度】
基础IO(下):软硬链接和动静态库
出差电子流应用实战