多线程学习-线程安全

news2024/10/9 2:28:08

目录

1.多线程可能会造成的安全问题

2. static共享变量

3.同步代码块

4.同步方法 

5.使用Lock手动加锁和解锁

6.死锁


1.多线程可能会造成的安全问题

        场景:三个窗口同时售卖100张电影票,使用线程模拟。

public class MyThread extends Thread{

    //ticketnum表示当前正在售卖第几张票
    public int ticketnum=0;

    @Override
    public void run() {

        //模拟卖票过程,总共100张票,售完为止
        while (ticketnum<100){
            ticketnum++;
            System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticketnum+"张票");

            //通常卖票是有时间间隔的,用sleep来模拟
            //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

//模拟三个窗口同时售票
public class Main {
    public static void main(String[] args) {

        //创建三个线程模拟三个窗口
        Thread t1=new MyThread();
        Thread t2=new MyThread();
        Thread t3=new MyThread();
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //模拟三个窗口售票
        t1.start();
        t2.start();
        t3.start();
    }
}

        运行结果:

        从运行结果可以看到,如果这样设计多线程卖票,会出现三个窗口出售同一张票,在实际情况下肯定是不能够出现这种问题的。那么为什么会出现重复售票的情况呢?

        这是因为ticketnum是普通成员变量,也就意味着每个线程中ticketnum是独立的,即相当于每个线程各有100张票进行售卖,但实际上三个窗口应当共同售卖这100张票。

2. static共享变量

        我们可以通过将ticketnum变成静态成员变量来解决这个问题。静态成员变量是共享的,这样可以保证这三个线程共同售卖这100张票:

public class MyThread extends Thread{

    //ticketnum表示当前正在售卖第几张票
    //使用static将其变为共享的
    public static int ticketnum=0;

    @Override
    public void run() {

        //模拟卖票过程,总共100张票,售完为止
        while (ticketnum<100){
            ticketnum++;
            System.out.println(Thread.currentThread().getName()+"正在售卖第"+ticketnum+"张票");

            //通常卖票是有时间间隔的,用sleep来模拟
            //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

        此时再次运行代码,运行的结果为:

        此时运行结果还是有问题,不仅还是会重复,而且在卖完第100张票后又出现了还在卖第99张票的情况,并且第96、98张票并没有显示被卖出。这又是为什么呢?

        这是由于线程执行的随机性导致的,在java中,多线程的执行是抢占式调度的,哪个线程能竞争到CPU完全是随机的,并且线程在执行过程中CPU也是有可能被其他线程抢走的。

        下面在解释时线程1、2、3并不对应上面例子中的窗口1、2、3,只是为了解释原因

        先来解释为什么将ticketnum变成共享的还是会出现重复售卖的现象:

        虽然在运行结果中窗口3、1、2重复出售了第97张票,但实际上ticketnum并不会重复,因为已经设置为共享资源了,输出结果中出现重复是因为在输出结果时出现了问题。假设目前都是从第一张票进行售卖,即ticketnum还是初始值0。线程1先抢到了CPU,执行ticketnum++,此时ticketnum的值变为1,然后线程1还没有来得及执行输出的操作分配给线程1的CPU占用时间就耗尽了;此时线程2成功抢到CPU,也是执行ticketnum++,由于是共享资源,所以这里的自增是在1的基础上自增的,所以此时ticketnum的值就变为了2,同样线程2还没有来得及执行输出的操作CPU就被线程3抢走了;线程3也执行ticketnum++,此时ticketnum就变成了3,然后CPU占用时间到。

        此时线程1再次抢到CPU,执行输出操作,但由于ticketnum变成了3,所以线程1会输出“正在售卖第3张票”,然后执行sleep方法休眠100ms,CPU被强制释放;此时线程2抢到了CPU执行输出操作,ticketnum的值还是3,所以线程2也会输出“正在售卖第3张票”,随后也进入100ms的休眠;由于线程1和线程2都进入了休眠,只有线程3在竞争CPU,所以线程3会抢到CPU,也会输出“正在售卖第3张票”。

        由此可见,虽然输出结果显示三个线程收买了同一张票,但实际上售卖了三张不同的票,只是输出结果时出现了问题。

        再来解释为什么输出结果中有的票没有售出显示:

        其实从上面的过程我们可以看到,ticketnum是会一直自增1的,但由于打印结果时出现了CPU抢占问题,导致ticketnum在自增后不能及时输出结果,本来应该是自增之后就输出,变成了三个线程滞后输出了相同的结果。所以重复输出相同的结果已经覆盖了那些没有显示的输出。

        最后解释为什么已经售完了第100张票,还会出售第99张票:

        假设此时正在出售第97张票,线程1执行完ticketnum++后ticketnum变成了98。然后线程2抢占了CPU并执行ticketnum++,此时ticketnum变成了99,接着开始执行输出操作;在执行输出操作时,需要先获取要输出的内容,此时读到的ticketnum是99,但还没有来得及执行println时间就耗尽了,此时线程1抢到了CPU,准备执行输出操作,获取到的ticketnum自然也是99,还没有来得及执行println,CPU就被线程3抢走了,线程3不仅执行了ticketnum++使其变为了100,还完整执行了输出操作,随后线程3进入100ms的休眠状态。此时线程1抢到了CPU,继续执行println,但由于之前读到的值是99,所以线程1会输出“正在售卖第99张票”,随后也进入100ms的休眠状态。最后是线程2抢到CPU,继续执行println,也输出“正在售卖第99张票”。所以输出结果时会先出现线程3输出“正在售卖第100张票”,然后是线程1输出“正在售卖第99张票”,最后是线程2输出“正在售卖第99张票”。

        那怎样解决这种问题呢?

3.同步代码块

        上面这三种情况归根结底还是因为程序在运行过程中其他线程能够修改共享变量的值,导致前后内容不一致的情况,也就是不能保证代码执行的原子性。如果我们在执行的过程中进行加锁,当一个线程未使用完共享资源之前不允许其他线程访问这个共享资源,那不就可以解决这个问题了,这就需要同步代码块来帮忙。

        同步代码块的格式:

synchronized (锁对象) {

    //要执行的代码

}

        其中锁对象可以是任意一个对象,因为锁对象就相当于这把锁是什么样的,是什么品牌的锁,什么形状的锁不重要,关键在于要锁住所有可能用到共享资源的代码,保证在这些代码执行完之前不会有其他线程干扰代码的执行。同时锁对象又必须是唯一的或者共享的,虽然锁的样式可以不同,但是锁必须是唯一的或者共享的,就比如一个房间有一把黑锁和一把白锁,有两个人要进入这个房间,一个人只认黑锁,如果没有黑锁就认为房间没有上锁可以进入,而另一个人只认白锁;此时第一个人进入到房间并给房间加了黑锁,第二个人由于只认白锁,所以会认为房间没有上锁也进入到这个房间,此时锁就失去了作用。

        同步代码块在执行时会自动加锁,当其中的代码块都执行完之后才会自动解锁。

        接着之前卖电影票的案例,可以将代码修改为:

public class MyThread extends Thread{

    //ticketnum表示当前正在售卖第几张票
    //使用static将其变为共享的
    public static int ticketnum=0;

    //锁对象,用于加锁操作
    public static Object object=new Object();
    
    @Override
    public void run() {

        //模拟卖票过程,总共100张票,售完为止
        //对卖票过程进行加锁
        synchronized (object){
            while (ticketnum<100){
                ticketnum++;
                System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");

                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }
}

        这里要注意要把整个while循环放到同步代码块中,因为while的判定条件也用到了共享变量ticketnum,凡是可能用到共享资源的代码都要放到同步代码块中。

        运行结果为:

        如果仅把ticketnum++和输出操作放入同步代码块也会出现线程安全问题,就是下面这样:

            //模拟卖票过程,总共100张票,售完为止
            while (ticketnum<100){
                
                //对卖票过程进行加锁
                synchronized (object){
                ticketnum++;
                System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                }
                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }

        运行结果为: 

        这是因为while中的判定条件并没有在同步代码块中,所以在执行判定条件时就无法保证线程安全。假设已经执行到卖出第99张票,随后线程1执行while的判定条件,99<100,所以线程1再次进入循环,但此时还没有来得及执行同步代码块线程2就抢走了CPU。此时线程2也执行while的判定条件,由于同步代码块还没有执行,就意味着还没有加锁,所以线程2也可以拿到ticketnum的值为99,所以线程2也可以再次进入循环,然后又和线程1一样,还没有来得及执行同步代码块线程3就抢走了CPU,同理线程3也拿到了ticketnum的值为99,也再次进入了循环。

        然后假设CPU又被线程1抢走了,此时线程1执行同步代码块,虽然加锁了,其他线程在同步代码块执行期间也都不能再次访问共享变量ticketnum,但是在这之前线程2和线程3都因为while没有保证线程安全都再次进入了循环,所以线程1、2、3都会执行ticketnum++和输出操作。

        只不过不同的是,线程1执行同步代码块之后ticketnum变为了100,然后输出“正在售卖第100张票”,执行结束后进入100ms的休眠状态,然后线程2抢到CPU并执行同步代码块,此时ticketnum变成了101,然后输出“正在售卖第101张票”,随后线程2也进入休眠状态,线程3抢占CPU并执行同步代码块,ticketnum变成了102,然后输出“正在售卖第102张票”。

        但是如果把整个while都放到同步代码块中还是会有问题,因为sleep也在循环体中,这么做虽然线程也会进入阻塞状态,但这期间由于还没有解锁,其他线程并不能对这个线程所持有的共享资源进行操作,所以其他线程抢到CPU也没有用,最后的运行结果会显示为这个线程售完了所有的票,相当于这个窗口垄断了整个售票部。

        那怎样才能既保证所有可能用到共享资源的代码都在代码块中,也能保证sleep不在同步代码块中呢?

        sleep在while循环体中,要想不让sleep在同步代码块中就不能让整个while在同步代码块中,这样做就不能让ticketnum这个共享变量出现在while的判定条件里,所以我们可以在while中使用if-else判断语句,将ticketnum<100这个判定条件转移到if中,这样就不需要对整个while进行加锁了:

@Override
    public void run() {

            //模拟卖票过程,总共100张票,售完为止
            while (true){

                //对卖票过程进行加锁
                synchronized (object){

                    //加入if-else
                    if(ticketnum<100) {
                        ticketnum++;
                        System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                    }else{
                        break;
                    }
                }

                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }

    }

        运行结果为:

4.同步方法 

        也可以使用同步方法,将同步代码块中的代码放到同步方法中,同步方法可以不用指定锁对象,在运行时会自动指定锁对象,如果使用的变量是非静态的,那么锁对象就是this,如果使用的变量是静态的,那么锁对象就是这个类的Class对象。下面是同步方法的格式:

public synchronized void 方法名(){
        
    //要执行的代码
        
    }

        不过要注意的是,如果使用了循环并且判定条件中使用了共享变量,或者在循环中使用了break或continue,那么要把整个循环都要放在同步方法中。前者上面讲过了,整个循环体都要放在同步代码块中,同步方法中肯定也是要把整个循环体都放进去;后者虽然不用将整个循环体放到同步代码块中,但必须都要放在同步方法中,因为break和continue只能在循环体中使用,如果在循环外使用会报错。

        那如果使用实现Runnable接口的方式实现电影院售票呢?下面是代码:

public class MyRun implements Runnable{

    int ticketnum=0;

    //对卖票过程进行加锁
    public synchronized void lock(){
        //模拟卖票过程,总共100张票,售完为止
        while (true){

                if(ticketnum<100) {
                    ticketnum++;
                    System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                }else{
                    break;
                }
            }

            //通常卖票是有时间间隔的,用sleep来模拟
            //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
    }

    @Override
    public void run() {
        lock();
    }
}

//实现模拟卖票
public class Main {
    public static void main(String[] args) {

        //创建MyRun任务类的对象
        Runnable r=new MyRun();

        //创建三个线程模拟三个窗口
        Thread t1=new Thread(r);
        Thread t2=new Thread(r);
        Thread t3=new Thread(r);
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //模拟三个窗口售票
        t1.start();
        t2.start();
        t3.start();
    }
}

        运行结果为:

        如果使用Runnable接口,那么ticketnum就没必要使用静态的了,普通成员变量就可以了,因为Runnable的实现类MyRun仅创建了一个实例,这就谈不到独立和共享变量的问题了,前面的MyThread创建了三个实例,所以必须让ticketnum变成静态的使其成为共享变量。所以对于MyRun的同步方法而言锁住的是非静态变量,那么锁对象就是this,也就是MyRun的实例;如果在MyThread中使用同步方法,那么锁住的就是静态变量,锁对象就是MyThread.class。

        由上面的运行结果会发现,由于把整个循环体都放进了同步方法中,sleep也放被迫进去了,导致线程1卖完了所有的票,所以遇到循环需要考虑使用同步方法会不会产生其他影响。

5.使用Lock手动加锁和解锁

        上面使用同步代码块会自动加锁和解锁,那有没有办法可以自己控制加锁和解锁的时机呢?

        java在JDK1.5之后提供了一个新的锁对象Lock,他有两个方法lock和unlock,一个是手动加锁,另一个是手动解锁。但是Lock本身是一个接口不能实例化对象,所以我们在使用时需要用到它的一个实现类ReentrantLock来实例化对象。那如果用Lock来实现同步代码块是不是仅需要在同步代码块中要执行的代码的前后分别加上lock和unlock就可以了呢?我们可以试一试:

        这是使用同步代码块的run方法: 

    @Override
    public void run() {

            //模拟卖票过程,总共100张票,售完为止
            while (true){

                //对卖票过程进行加锁
                synchronized (object){
                    if(ticketnum<100) {
                        ticketnum++;
                        System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                    }else{
                        break;
                    }
                }

                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }

    }

        这是在前后加上lock和unclock的方法:

        注意创建的lock对象也必须是静态的,这个就相当于锁对象,必须唯一,当然如果使用Runnable接口实现的那就可以是普通变量。

public class MyThread extends Thread{

    //ticketnum表示当前正在售卖第几张票
    //使用static将其变为共享的
    public static int ticketnum=0;

    //锁对象,用于加锁操作
    public static Lock lock=new ReentrantLock();;

    @Override
    public void run() {

            //模拟卖票过程,总共100张票,售完为止
            while (true){

                //对卖票过程进行加锁
                lock.lock();
                    if(ticketnum<100) {
                        ticketnum++;
                        System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                    }else{
                        break;
                    }
                lock.unlock();

                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }

    }
}

        运行结果:

        乍一看没有什么问题,但是仔细看就会发现程序并没有停止运行,这是哪里出现了问题呢?

        在上面的代码中,如果判断ticketnum>=100就会break退出循环,但在判断之前执行了加锁操作,break之后循环跳出,unlock解锁操作自然就没有执行,所以此时还处在上锁状态,需要将锁释放程序才能停止运行。

        所以不只是在要执行的代码前后加上lock和unlock那么简单。在上面的例子中我们可以在break前加上一个unlock操作就能解决问题,但这样做unlock就会出现两次,如果有多个break那就需要重复加上多个unlock。那怎样只需要写一个unlock就能保证加的锁最后都能被释放呢?

        保证加的锁最后都能被释放换句话讲就是无论中间执行什么代码,最后解锁的操作都会执行,这和try-catch-finally中的finally部分很契合,finally部分的代码无论任何情况最后都会被执行,所以我们可以使用finally来解决这个问题:

public class MyThread extends Thread{

    //ticketnum表示当前正在售卖第几张票
    //使用static将其变为共享的
    public static int ticketnum=0;

    //锁对象,用于加锁操作
    public static Lock lock=new ReentrantLock();;

    @Override
    public void run() {

            //模拟卖票过程,总共100张票,售完为止
            while (true){

                //对卖票过程进行加锁
                lock.lock();
                
                //使用finally实现
                try {
                    if(ticketnum<100) {
                        ticketnum++;
                        System.out.println(Thread.currentThread().getName() + "正在售卖第" + ticketnum + "张票");
                    }else{
                        break;
                    }
                }finally {
                    lock.unlock();
                }

                //通常卖票是有时间间隔的,用sleep来模拟
                //由于父类Thread的run方法没有抛异常,所以子类的run方法也不能抛异常,只能使用try-catch
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
        }
    }
}

        运行结果为: 

        总结一下,使用Lock的好处是我们可以手动控制在哪里进行加锁和解锁操作,也能清楚的看到什么时候加锁什么时候解锁,而同步代码块是自动加锁和解锁的,这个过程对我们是不可见的,我们也不能控制加锁和解锁的时机。但使用Lock我们需要去考虑是否在任何情况下加上的锁都能够在最后释放掉,同步代码块则不需要,会自动解锁。

6.死锁

        死锁就是指,线程1加了A锁又准备加B锁,线程2加了B锁又准备加A锁,此时由于B锁已经被添加,线程1只能等待B锁释放,而线程2由于A锁也已经被添加,也在等待A锁释放,这样线程1、2因为要等待对方先释放锁导致一直无法解除自身已经添加的锁,造成死锁。所以在使用多线程时尽量不要嵌套加锁。

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

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

相关文章

windows安装OpenUSD

一、下载OpenUSD git clone https://github.com/PixarAnimationStudios/OpenUSDOpenUSD&#xff0c;原名USD&#xff08;Universal Scene Description&#xff0c;通用场景描述&#xff09;&#xff0c;是由皮克斯动画工作室开发的一种开放数据格式。OpenUSD主要用于在虚拟世界…

Linux-程序地址空间

目录 1. 程序地址空间分布 2. 两个问题 3. 虚拟地址和物理地址 4. 页表 5. 解决问题 6. 为什么要有地址空间 1. 程序地址空间分布 测试一下&#xff1a; #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h>int ga…

java中Date类,SimpleDateFormat类和Calendar类

Date类 public Date() 创建一个Date对象&#xff0c;代表的是系统当前此刻的日期时间 public Date(long date) Constructs a Date object using the given milliseconds time value. 把时间毫秒值转变成Date日期对象 public void setTime(long date) Sets an existing Date ob…

【Linux学习】Linux 的虚拟化和容器化技术

˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN 如…

进阶线段树之乘法线段树

1.乘法线段树 顾名思义&#xff0c;就是其中的区间修改为乘法&#xff0c;但是呢&#xff0c;如果只是一个乘法&#xff0c;把之前的加号变成*号&#xff0c;然后开long long即可&#xff08;因为乘法的数据超大&#xff0c;如果不在中间mod点儿东西还能直接超出64位&#xff…

【Java EE】Maven jar 包下载失败问题的解决方法

文章目录 1. 配置好国内的Maven源1.1配置当前项⽬setting1.2设置新项⽬的setting 2.重新下载jar包3.其他问题⭕总结 1. 配置好国内的Maven源 因为中央仓库在国外, 所以下载起来会⽐较慢, 所以咱们选择借助国内⼀些公开的远程仓库来下载资源 接下来介绍, 如何设置国内源 1.1配…

Java集合(个人整理笔记)

目录 1. 常见的集合有哪些&#xff1f; 2. 线程安全的集合有哪些&#xff1f;线程不安全的呢&#xff1f; 3. Arraylist与 LinkedList 异同点&#xff1f; 4. ArrayList 与 Vector 区别&#xff1f; 5. Array 和 ArrayList 有什么区别&#xff1f;什么时候该应 Array而不是…

Spring Boot Mockito (二)

Spring Boot Mockito (二) 基于第一篇Spring Boot Mockito (一) 这篇文章主要是讲解Spring boot 与 Mockito 集成持久层接口层单元测试。 1. 引入数据库 h2及其依赖包 <dependency><groupId>com.h2database</groupId><artifactId>h2</artifactId…

C语言----数据在内存中的存储

文章目录 前言1.整数在内存中的存储2.大小端字节序和字节序判断2.1 什么是大小端&#xff1f;2.2 练习 3.浮点数在内存中的存储3.1.引子3.2.浮点数的存储3.2.2 浮点数取的过程 前言 下面给大家介绍一下数据在内存中的存储&#xff0c;这个是一个了解c语言内部的知识点&#xf…

动规训练3

一、按摩师 1、题目解析 简而言之就是&#xff0c;找到一个按摩师的预约总是长的最长方案&#xff0c;还有一个限制条件&#xff0c;选取的预约两两不相邻。 2、算法原理 a状态表示方程 小技巧&#xff1a;经验题目要求 dp[i]表示以这个节点为结尾&#xff0c;最长的预约时…

【C语言】翻译环境与运行环境

一、前言 在我们学习C语言的时候&#xff0c;第一个接触的程序就是&#xff1a;在屏幕上打印” hello word! “&#xff0c;可当时的我们却未去深入的理解与感悟&#xff0c;一个程序代码是如何运行的&#xff1b;而这一期的博客&#xff0c;则是带着我们&#xff0c;通过C代码…

Python | SLP | EOF | 去除季节趋势

EOF & PC 前言 在计算EOF&#xff08;经验正交函数&#xff09;之前去除季节循环是为了消除数据中的季节变化的影响&#xff0c;使得EOF能够更好地捕捉数据中的空间变化模式。如果不去除季节循环&#xff0c;季节性信号可能会在EOF中占据较大的比例&#xff0c;从而影响对其…

材料物理 笔记-4

原内容请参考哈尔滨工业大学何飞教授&#xff1a;https://www.bilibili.com/video/BV18b4y1Y7wd/?p12&spm_id_frompageDriver&vd_source61654d4a6e8d7941436149dd99026962 或《材料物理性能及其在材料研究中的应用》&#xff08;哈尔滨工业大学出版社&#xff09; 离…

浙大恩特客户资源管理系统 RegulatePriceAction SQL注入漏洞复现

0x01 产品简介 浙大恩特客户资源管理系统是一款针对企业客户资源管理的软件产品。该系统旨在帮助企业高效地管理和利用客户资源,提升销售和市场营销的效果。 0x02 漏洞概述 浙大恩特客户资源管理系统 RegulatePriceAction接口存在 SQL 注入漏洞,攻击者可通过输入恶意 SQL …

信息传播的AI时代:机器学习赋能新闻出版业的数字化之旅

&#x1f9d1; 作者简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟,欢迎关注。提供嵌入式方向的学习指导…

layui框架实战案例(26):layui-carousel轮播组件添加多个Echarts图标的效果

在Layui中&#xff0c;使用layui-carousel轮播组件嵌套Echarts图表来实现多个图表的展示。 css层叠样式表 调整轮播图背景色为白色&#xff1b;调整当个Echarts图表显示loading…状态&#xff1b;同一个DIV轮播项目添加多个Echarts的 .layui-carousel {background-color: #f…

黄锈水过滤器 卫生热水工业循环水色度水处理器厂家工作原理动画

​ 1&#xff1a;黄锈水处理器介绍 黄锈水处理器是一种专门用于处理“黄锈水”的设备&#xff0c;它采用机电一体化设计&#xff0c;安装方便&#xff0c;操作简单&#xff0c;且运行费用极低。这种处理器主要由数码射频发生器、射频换能器、活性过滤体三部分组成&#xff0c;…

GPT-3.5开放免费使用,这次OpenAI做到了真的open

本周一&#xff0c;OpenAI宣布&#xff0c;部分地区的ChatGPT网站访问者现在无需登录即可使用人工智能助手。 此前&#xff0c;该公司要求用户创建账户才能使用&#xff0c;即使是目前由GPT-3.5AI语言模型支持的免费版ChatGPT也是如此。 01.GPT-3.5开放免登录使用 众所周知&…

mysql+keepalive+lvs搭建的数据库集群实验

前提条件&#xff1a;准备5台计算机&#xff0c;且网络互通 1、客户端 yum groups -y install mariadb-client ip 192.168.0.5 2、lvs1 yum-y install ipvsadm keepalived ip 192.168.0.1 keepalivedvip 192.168.0.215 /etc/hosts 解析192.168.0.1 主机名 3、lvs2 yum-y i…

生成式人工智能与 LangChain(预览)(下)

原文&#xff1a;Generative AI with LangChain 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 六、开发软件 虽然这本书是关于将生成式人工智能&#xff0c;特别是大型语言模型&#xff08;LLMs&#xff09;集成到软件应用程序中&#xff0c;但在本章中&#xff0c;…