volatile,wait和notify,懒汉模式和饿汉模式,阻塞式队列,定时器

news2024/11/23 8:07:41

目录

可见性

volatile

volatile保证内存可见性

volatile不保证原子性

synchronized也可以保证内存可见性 

wait和notify

wait ()

notify()

notifyAll()

wait和sleep对比

顺序执行ABC三个线程

单例模式

饿汉模式

懒汉模式

懒汉模式和饿汉模式在多线程环境下调用getInstance,是否线程安全?

如何让懒汉模式线程安全?

模拟阻塞队列中的put和take方法

 生产者消费者模型

定时器

定时器会使用在哪些场景

标准库中的定时器


可见性

一个线程对共享变量的修改,可以及时的被其他线程看到。

从JMM角度表述内存可见性问题:

1.线程之间的共享变量存在 主内存 (Main Memory).
2.每一个线程都有自己的 " 工作内存 " (Working Memory) .
3.当线程要读取一个共享变量的时候 , 会先把变量从主内存拷贝到工作内存 , 再从工作内存读取数据 .
4.当线程要修改一个共享变量的时候 , 也会先修改工作内存中的副本 , 再同步回主内存 .
5.当t1线程进行读取,t2线程进行修改的时候,先修改工作内存的值,然后再把工作内存的内容同步到主内存中,但是由于编译器的优化,导致t1没有重新从工作内存同步到主内存,读到的结果就是修改之前的结果,
工作内存=CPU寄存器+CPU的缓存cache;(CPU读取寄存器比读取内存快得多,因此会在CPU内部引入缓存cache,有的CPU可能没有cache,有的有1个,有的是多个,现在普遍是三级)

volatile

volatile保证内存可见性

代码在写入 volatile 修饰的变量的时候,
改变线程工作内存中 volatile 变量副本的值, 将改变后的副本的值从工作内存刷新到主内存;
代码在读取 volatile 修饰的变量的时候,
从主内存中读取 volatile 变量的最新值到线程的工作内存中, 从工作内存中读取 volatile 变量的副本;

前面我们讨论内存可见性时说了, 直接访问工作内存(实际是 CPU 的寄存器或者 CPU 的缓存), 速度非常快, 但是可能出现数据不一致的情况. 
加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了.

 这种情况就称为内存可见性问题;也是一种线程安全问题。

 

✅由此可见:

内存可见性问题,一个线程针对一个变量进行读取操作,同时另一个线程针对变量进行修改,此时读到的值不一定是修改之后的值。这个读线程没有感知到变量的变化

归根结底就是:jvm或者编译器在多线程优化环境下产生了误判

此时就需要我们手动给flag变量添加volatile关键字(告诉编译器这个变量是易变的,每一次一定要重新读取这个变量的内存内容,指不定啥时候就变了,不能再进行激进的优化了

编译器是否会优化是个玄学问题,我们最好加上volatile.

✅上述内存可见性 编译器优化 的问题,也并不是始终都会出现的,编译器只是可能会误判

✅其他知识:方法中的变量是存放在栈中的,每一个线程都有自己的内存空间,即使是同一个方法,被不同的线程掉用,这里的局部变量还是会处在不同的栈空间上,本质上还是不通的变量。 

volatile不保证原子性

volatile synchronized 有着本质的区别 . synchronized 能够保证原子性 , volatile 保证的是内存可见 性.
volatile不能处理并发++的情况;

synchronized也可以保证内存可见性 

”synchronized 既能保证原子性 , 也能保证内存可见性“,这个观点存疑,没有办法用代码验证。线程的最大问题就是抢占式执行,随机性调度,我们需要控制线程之间的执行顺序,虽然线程在内核的调度都是随机的,但是可以通过一些api让线程主动阻塞,主动放弃cpu。

wait和notify

eg:t1,t2,两个线程,希望t1先干活,干的差不多了,再让t2干,就可以让t2先wait(阻塞,主动放弃cpu),等t1干的差不多了,再通过notify通知t2,唤醒t2,让t2继续干。

上述场景使用join()的话,只能是t1 100%执行完,t2才开始执行

wait ()

调用wait方法,就会进入阻塞状态,进入waiting状态,这个可以通过interrupt异常唤醒;

wait()方法中不添加任何的参数,就是死等,除非有别的线程唤醒她。

wait 结束等待的条件 :
其他线程调用该对象的 notify 方法 .
wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本 , 来指定等待时间 ).
其他线程调用该等待线程的 interrupted 方法 , 导致 wait 抛出 InterruptedException 异常 .

为什么有这个异常 ?

要理解wait操作是干啥:

1.先释放锁; 

2.进行阻塞等待;

3.收到通知后,重新尝试获取锁,并且在获取锁之后,继续向下执行。

就好比是单身(没被加锁)还想着分手(就想释放锁)的事情,

所以wait要搭配synchronized来使用

public class demo44 {
    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();
        synchronized(object){
            System.out.println("wait之前");
            object.wait();
            System.out.println("wait之后");
        }
    }
}

此时就只会打印出“wait之前” 

这里虽然wait是阻塞了,阻塞在synchronized代码块里,实际上,这里的阻塞是释放了锁的,此时其他线程是可以获取到object这个对象的锁的~此时这里的阻塞,就处于waiting状态

wait()无参数版本就是死等的;

wait()带参数版本,是指定了等待的最大时间;

notify()

public class demo99 {
    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();
        Thread t1=new Thread(()->{
            //这个线程负责等待;
            System.out.println("t1:wait之前");
            synchronized(object){
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("t2:wait之后");

        });
        Thread t2=new Thread(()->{
            System.out.println("t2notify之前");
            synchronized(object){
//notify必须获取到锁才能进行通知;
                object.notify();
            }
            System.out.println("t2:notify之后");

        });
        t1.start();
        Thread.sleep(500);
        t2.start();
    }

}

在代码的最后

写t1.start();t2.start();由于线程调度的不确定性,此时不能保证先执行wait,后执行notify,如果先调用notify,此时没有人wait,此处的wait没法被唤醒的,但是也没啥副作用

notifyAll()

notify 方法只是唤醒某一个等待线程 . 使用 notifyAll 方法可以一次唤醒所有的等待线程 .

wait和sleep对比

wait的待有时间参数的版本看起来和sleep有点像,其实有本质区别的,虽然都是指定等待时间,虽然也都能指定等待时间,虽然也能被提前唤醒,(wait是使用notify唤醒,sleep使用interrupted唤醒)

notify唤醒wait,不会有任何异常;

interrupt唤醒sleep则是出异常了。

其实理论上 wait sleep 完全是没有可比性的,因为一个是用于线程之间的通信的,一个是让线程阻 塞一段时间,
唯一的相同点就是都可以让线程放弃执行一段时间 .
1. wait 需要搭配 synchronized 使用 . sleep 不需要 .
2. wait Object 的方法  ,sleep Thread 的静态方法 .

顺序执行ABC三个线程

public class demo999 {
    public static void main(String[] args) {
        Object locker1=new Object();
        Object locker2=new Object();
        Thread t1=new Thread(()->{
            System.out.println("A");
            synchronized(locker1){
                locker1.notify();
            }
        });
        Thread t2=new Thread(()->{
            synchronized(locker1){
                try {
                    locker1.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
            System.out.println("B");
            synchronized(locker2){
                locker2.notify();
            }
        });
        Thread t3=new Thread(()->{
            synchronized(locker2){
                try {
                    locker2.wait();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

            System.out.println("C");
        });
        t2.start();
        t3.start();
        t1.start();
        /***
         * 如果程序先执行t1的notify,后执行t2的wait
         *就僵住了
         *
         * **/
    }

}

单例模式

⛅单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.
⛅单例模式有很多种,这里介绍 懒汉模式 和 饿汉模式 (饿汉模式;类加载阶段就把实例创建出来了,类加载时比较靠前的  )。

饿汉模式

class Singleton{
    private static Singleton instanse=new Singleton();
    /**
     * Singleton这个属性与实例无关,而是与类有关,java代码中的每一个类,都会在编译完成后得到.class文件,、
     * JVM运行时就会加载这个.class文件读取其中的二进制文件,并且在内存中构造出对应的类对象
     * 由于类对象在Java进程中只有唯一一份,因为类对象内部的类属性也是唯一一份;
     * **/
    //如果需要获取这个instanse,就通过这个方法
    public static Singleton getInstance(){
        return instanse;
    }
    //为了避免Singleton被复制出来多份
    //把构造方法设为private,在类外就无法通过new 的方式获取Singleton对象了;
    private Singleton(){

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

}
1.此时打印结果就是true;
2.在这里第二行的static是必要的
(1)static保证这个实例唯一;
(2)static保证这个实例在一定时间被创建出来
3.如何保证类对象唯一?
(1)static 这个操作是让当前的instanse对象变成 类属性,
类属性是长在类对象上的,类对象又是唯一实例的,只是在类加载的时候被创建一个实例。
(2)构造方法设为private,在类外是无法new的。                                                                 
4.类加载模式是啥?

  要执行Java程序的前提就是让类先加载出来。 

 类对象本身与类属性无关,仅仅是因为类里面使用static修饰变量,会作为类属性,也就相当于这个属性对应的内存空间在类对象里面。static与类有关,与实例无关

懒汉模式

class SingletonLazy{
    private static SingletonLazy instance=null;
    public static SingletonLazy getInstance(){
        if(instance==null){
            instance=new SingletonLazy();
            //这个实例并不是类加载的时候就创建,而是在第一次使用的时候才去创建,
            //如果不用,就不创建了
        }
         return instance;

    }
    private SingletonLazy(){};

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

懒汉模式和饿汉模式在多线程环境下调用getInstance,是否线程安全?

饿汉模式里的getInstance里的操作只涉及到了读的操作;懒汉既有读也有写 ;懒汉并不是线程安全的,可能会进行多次new操作

如何让懒汉模式线程安全?

加锁

 但是此时还是有问题,内存可见性问题:

加入有很多线程,同时进行getInstance操作,这个时候,是否还会有被优化的风险(只有第一次读使读内存,之后都是读寄存器/cache)。

new操作可能涉及指令重排序问题;

instance=new SingletonLazy()操作可以分成三个步骤:

1.申请内存空间;

2.调用构造方法,把这个内存空初始化成一个合理的对象;

3.把内存空间的地址赋值给instance引用;

正常情况下就是根据123执行,但是编译器还有一手,指令重排序~为了提高程序效率,调整代码执行顺序,顺序就有可能被调整。如123就可能变成132,如果是单线程,123和132就没有本质区别。

但是在多线程情况下,t1线程是按照132的顺序执行,t1执行完13后,执行2的时候被切出cpu由t2来执行,t2拿到的就是一个空对象非法的对象,还没构造完的不完整对象。

解决办法就是加volatile

1.解决内存可见性;

2.解决指令重排序问题;

class SingletonLazy1{
    public volatile static SingletonLazy1 instance = null;
    public static SingletonLazy1 getInstance(){
        if (instance == null) {
            synchronized(SingletonLazy1.class){
                if(instance==null){
                    instance=new SingletonLazy1();
                }
            }
        }
        return instance;
    }
    private SingletonLazy1(){};

}

 加锁不是说线程就赖在CPU上不走了,而是切换调度正常,但是其他线程尝试枷锁就阻塞;

这里加锁的作用是让原本不是原子的操作变成原子性操作;并且加锁是解决线程安全问题的关键,加锁必须是两个线程同时对同一个线程加锁才会产生线程阻塞;

模拟阻塞队列中的put和take方法

这里是普通队列

 public void put(int value){
        if(size==items.length){
            //队列已经满了
            return;
        }
        items[tail]=value;
        tail++;
        //记得对tail的处理
        //第(1)种写法
        tail=tail%items.length;
        //第(2)中写法
        if(tail>=items.length){
            tail=0;
        }
    }
    //出队列
    public Integer take(){
        if(size==0){
            return null;
        }
        int result=items[head];
        head++;
        if(head>=items.length){
            head=0;
        }
        size--;
        return result;
    }

/**自己写阻塞队列;
 * 此处不考虑泛型,直接使用int代替
 * */
/***
 * 普通队列的实现加上阻塞功能就变成了阻塞队列,是在多线程环境下的阻塞;
 * 加上synchronized包裹整个方法体
 * */
/**
 * 但是有一个问题:
 * 如果他们两个线程的wait同时触发了;
 * 那显然就不能在正确的唤醒了;
 *
 *
 * **/
class MyBlockingQueue{
    private int[] items=new int[200];
    private int head=0;
    private int tail=0;
    private int size=0;
    //入队列
    public void put(int value) throws InterruptedException {
        synchronized(this){
            while(size==items.length){
                //队列已经满了,再放元素,就会产生阻塞
                this.wait();
            }
            items[tail]=value;
            tail++;
            //记得对tail的处理
            //第(1)种写法
            tail=tail%items.length;
            //第(2)中写法
            if(tail>=items.length){
                tail=0;
            }
            this.notify();//唤醒take()里面的wait;
        }

    }
    //出队列
    public Integer take() throws InterruptedException {
        int result=0;
        synchronized (this){
            if(size==0){//队列为空,还要求出队列,出现阻塞;
              this.wait();
            }
            result=items[head];
            head++;
            if(head>=items.length){
                head=0;
            }
            size--;
            this.notify();//唤醒take()里面的阻塞等待;
        }
        return result;

    }

}

 生产者消费者模型

public static void main(String[] args) {
        MyBlockingQueue queue=new MyBlockingQueue();
        Thread customer=new Thread(()->{
            while(true){
                try {
                    int result=queue.take();
                    System.out.println("消费: "+result);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }

        });
        Thread producer=new Thread(()->{
            int count=0;
            while(true){
                try {
                    System.out.println("生产 "+ count );
                    queue.put(count);
                    count++;
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
            }
        });




    }

 

定时器

定时器会使用在哪些场景

定时器是一种实际开发中非常常用的组件.
比如网络通信中, 如果对方 500ms 内没有返回数据, 则断开连接尝试重连.
比如一个 Map, 希望里面的某个 key 在 3s 之后过期(自动删除).
类似于这样的场景就需要用到定时器.

标准库中的定时器

 

Timer timer = new Timer();
timer.schedule(new TimerTask() {
    @Override
    public void run() {
        System.out.println("hello");
   }
}, 3000);
/**
*标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
*schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 
*第二个参数指定多长时间之后执行 (单位为毫秒).
*/

定时器的实现 

单独在定时器内部,搞个线程,让线程周期性扫描 ,到时间就执行。   扫描线程也只用扫描优先级队列的第一个元素。 

2.一个定时器可以注册N个任务,N个任务会按照最初约定的任务,按照顺序执行;

优先级队列

此处的优先级队列会在多线程环境下使用,使用schedule是一个队列,扫描线程是另一个的队列;

定时器的构成:

1.一个带优先级的阻塞队列
为啥要带优先级呢 ?
因为阻塞队列中的任务都有各自的执行时刻 (delay). 最先执行的任务一定是 delay 最小的 . 使用带 优先级的队列就可以高效的把这个 delay 最小的任务找出来 .
2.队列中的每个元素是一个 Task 对象 .
3.Task 中带有一个时间属性.
4. 同时有一个 worker 线程一直扫描队首元素 , 看队首元素是否需要执行

Task实现 

static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }
        @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
}
Task 类用于描述一个任务 ( 作为 Timer 的内部类 ). 里面包含一个 Runnable 对象和一个 time( 毫秒时 间戳)
这个对象需要放到 优先队列 中. 因此需要实现 Comparable 接口.

 完整代码

public class Timer {
    static class Task implements Comparable<Task> {
        private Runnable command;
        private long time;
        public Task(Runnable command, long time) {
            this.command = command;
            // time 中存的是绝对时间, 超过这个时间的任务就应该被执行
            this.time = System.currentTimeMillis() + time;
       }
        public void run() {
            command.run();
       }

        @Override
        public int compareTo(Task o) {
            // 谁的时间小谁排前面
            return (int)(time - o.time);
       }
   }
    // 核心结构
    private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue();
    // 存在的意义是避免 worker 线程出现忙等的情况
    private Object mailBox = new Object();
    class Worker extends Thread{
        @Override
        public void run() {
            while (true) {
                try {
                    Task task = queue.take();
                    long curTime = System.currentTimeMillis();
                    if (task.time > curTime) {
                        // 时间还没到, 就把任务再塞回去
                        queue.put(task);
                        synchronized (mailBox) {
                            // 指定等待时间 wait
                            mailBox.wait(task.time - curTime);
                       }
                   } else {
                        // 时间到了, 可以执行任务
                        task.run();
                   }
               } catch (InterruptedException e) {
                    e.printStackTrace();
                    break;
               }
           }
       }
   }
    public Timer() {
        // 启动 worker 线程
        Worker worker = new Worker();
        worker.start();
   }
    // schedule 原意为 "安排"
    public void schedule(Runnable command, long after) {
        Task task = new Task(command, after);
        queue.offer(task);
        synchronized (mailBox) {
            mailBox.notify();
       }
   }
// Timer 实例中, 通过 PriorityBlockingQueue 来组织若干个 Task 对象. 
//通过 schedule 来往队列中插入一个个 Task 对象.
    public static void main(String[] args) {
        Timer timer = new Timer();
        Runnable command = new Runnable() {
            @Override
            public void run() {

                System.out.println("我来了");
                timer.schedule(this, 3000);
           }
       };
        timer.schedule(command, 3000);
   }
}
/***
 * 当时间没到要执行的时候, CPU就把线程拿起来又放下,进行忙等,
 * 所以我们选择“阻塞时等待”,使用wait,更方便唤醒,
 * 使用wait等待,更方便随时唤醒,使用wait等待每次有新任务来了(有人调用schedule)
 * 就重新检查时间,重新计算要等待的时间,并且wait也停工了一个带有:超过时间的版本
 * */
/**
 *堆的take:出堆顶元素 ;底层操作
 * 交换堆顶元素和最后一个元素,进行向下调整
 *堆的put:入,
 * 先放在最后一个元素的位置,然后进行向上调整 
 * */

一个极端情况

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/663777.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

将条码图片批量嵌入到Excel单元格中

项目源码&#xff08;如果有帮助希望可以点一个star&#xff09; 业务场景&#xff1a; 需要将条码图片存到excel之中方便归档。 效果展示&#xff1a; 直接运行org.example.Main.main()就会将file目录中的图片插入到excel中&#xff0c;并且在项目根目录会生成一个.xlsx文件…

CTFshow-pwn入门-前置基础pwn20-pwn22

pwn20-pwn22是关于.got节跟.got.plt节的。这3道题的问题描述全是一样的&#xff0c;全都是问.got跟.got.plt是否可写以及他们的地址是什么&#xff0c;然后根据这些信息来拼成flag。那么关于.got和.got.plt的内容非常复杂&#xff0c;这里呢我也不解释了&#xff0c;推荐一个牛…

【kubernetes】负载均衡器安装部署-Haproxy与keepalived

前言:二进制部署kubernetes集群在企业应用中扮演着非常重要的角色。无论是集群升级,还是证书设置有效期都非常方便,也是从事云原生相关工作从入门到精通不得不迈过的坎。通过本系列文章,你将从虚拟机准备开始,到使用二进制方式从零到一搭建起安全稳定的高可用kubernetes集…

不要再用 count(*) 查询记录数了

来源 | 苏三说技术 &#xff08;ID&#xff1a;susanSayJava&#xff09; 已获得原公众号的授权转载 前言 最近我在公司优化过几个慢查询接口的性能&#xff0c;总结了一些心得体会拿出来跟大家一起分享一下&#xff0c;希望对你会有所帮助。 我们使用的数据库是Mysql8&…

【6.19】用户自己写String类会发生什么(双亲委派机制)

用户自己写一个String类会发生什么&#xff1f; 了解“类加载器” Java是运行在Java的虚拟机&#xff08;JVM&#xff09;中的。我们在IDE里编写的Java源代码先编译成.class的字节码文件&#xff0c;再由ClassLoader将class文件加载到JVM中执行。 JVM中有三层ClassLoader&am…

apple pc install windows 10

苹果笔记本安装window10&#xff0c;做个U盘启动&#xff0c;开机狂摁option&#xff0c;选择U盘&#xff0c;当然你最好去windows官方下个镜像&#xff0c;避免我前面出现提出镜像不行。另外苹果后来机器好像不能安windows了。呼呼…

GPT-3解数学题准确率升至92.5%!无需微调即可打造理科语言模型

原文&#xff1a;百度安全验证 【新智元导读】ChatGPT的文科脑有救了&#xff01; 大型语言模型最为人诟病的缺点&#xff0c;除了一本正经地胡言乱语以外&#xff0c;估计就是「不会算数」了。 比如一个需要多步推理的复杂数学问题&#xff0c;语言模型通常都无法给出正确答…

很有必要更新:LightningChart.NET 10.5.1 Crack

LightningChart.NET v10.5.1版本--这个版本比SciChart 更好&#xff0c;更快&#xff0c;更强 为所有3D、Polar和Smith系列添加DataCursor功能。 2023年6月19日-10:53新版 特点 为所有3D、Polar和Smith系列启用了DataCursor功能。DataCursor允许用户浏览一个系列&#xff0…

【一起啃书】《机器学习》第十章 降维与度量学习

文章目录 第十章 降维与度量学习10.1 k k k近邻学习10.2 低维嵌入10.3 主成分分析10.3.1 定义与步骤10.3.2 最近重构性与最大可分性 10.4 核化线性降维10.5 流形学习10.6 度量学习 第十章 降维与度量学习 10.1 k k k近邻学习 k k k近邻学习是一种常用的监督学习方法&#xf…

数字图像处理实验报告(二)

报告目录 实验四、图像复原 实验五、图像压缩及编码 实验四、图像复原 一、实验目的 了解图像复原的意义和手段&#xff1b;熟悉图像退化成因及处理方法&#xff1b;通过实验了解不同图像退化模型的特点&#xff1b;通过本实验掌握利用MATLAB建立图像退化模型并进行复原的方…

linux CentOS7 keepalived+LVS(DR)搭建部署

目录 一、服务器准备 二、操作步骤 1.部署web服务器 2.部署LVS主备调度器 测试 部署NFS共享服务器 一、服务器准备 .准备6台虚拟机&#xff0c;2台做LVS主备调度器&#xff0c;2台做web服务器&#xff0c;1台做存储&#xff0c;1台客户机验证 1.LVS主调度器 &#xff08…

20个你应该掌握的强大而有用的正则表达式

关注“大前端私房菜”微信公众号&#xff0c;回复暗号【面试宝典】即可免费领取107页前端面试题。 正则表达式是一种很强大的字符串模式匹配工具。掌握常见的正则表达式可以大大提高我们在字符串操作和文本处理上的效率。 1.货币格式化 我经常需要在工作中使用到格式化的货币&…

哈希及其哈希思想的应用

1. unordered 系列关联式容器 在 C98 中&#xff0c; STL 提供了底层为红黑树结构的一系列关联式容器&#xff0c;在查询时效率可达到 $log_2 N$ &#xff0c;即最差情况下需要比较红黑树的高度次&#xff0c;当树中的节点非常多时&#xff0c;查询效率也不理想。最好 的查询…

Ubuntu20.04,samba服务器搭建。

0.前言 推荐个电视剧吧&#xff0c;百看不厌&#xff0c;《雍正王朝》。 这篇博客没什么技术含量&#xff0c;纯粹的表现一下我的勤劳。 1.Ubuntu 安装 终端输入 $ sudo apt install samba samba-common 配置需要共享的目录 # 新建目录&#xff08;自定义&#xff09;&am…

CarFramework打造无缝用户体验:提升汽车信息娱乐和控制

CarFramework框架解析 CarFramework&#xff08;汽车框架&#xff09;是Android Automotive平台上的一个关键框架&#xff0c;它提供了专门针对车辆应用程序开发的功能和工具。CarFramework通过提供一组API和服务&#xff0c;简化了与车辆硬件和车辆特定功能的交互。 CarFram…

被ChatGPT骗了!再用ChatGPT可要小心了。

被ChatGPT戏耍的周末 1. 被ChatGPT戏耍全过程2. 拆穿ChatGPT的把戏3. AIGC与内容安全 1. 被ChatGPT戏耍全过程 电动垂直起降飞行器&#xff08;eVTOL&#xff0c;Electric Vertical Takeoff and Landing&#xff09;技术越来越成熟&#xff0c;为了解下相关产品我周末打开了Cha…

Android Java判断密码强度 强度显示

1&#xff0c;正则表达式 密码强度的正则表达式 public static final String WEAK_PATTERN "(^(?.*[0-9])(?.*[a-z])[0-9a-z]{6,18}$)|(^(?.*[0-9])(?.*[A-Z])[0-9A-Z]{6,18}$)";//全数字字母&#xff08;大/小&#xff09;public static final String MEDIUM_…

61、基于51单片机无线蓝牙音乐喷泉控制系统设计(程序+原理图+PCB源文件+参考论文+参考PPT+元器件清单等)

方案选择 单片机的选择 方案一&#xff1a;AT89C52是美国ATMEL公司生产的低电压&#xff0c;高性能CMOS型8位单片机&#xff0c;器件采用ATMEL公司的高密度、非易失性存储技术生产&#xff0c;兼容标准MCS-51指令系统&#xff0c;片内置通用8位中央处理器(CPU)和Flash存储单元…

MySql基础教程(一):创建删除选择数据库

MySql基础教程(一)&#xff1a;创建删除选择数据库 1、创建数据库 创建一个名为 CSDN 的数据库 CREATE DATABASE CSDN;执行成功&#xff0c;数据库CSDN创建成功。 2、删除数据库 删除名为 CSDN 的数据库 DROP DATABASE CSDN;执行成功&#xff0c;CSDN数据库删除成功。 3…