多线程学习-线程安全

目录

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因为要等待对方先释放锁导致一直无法解除自身已经添加的锁,造成死锁。所以在使用多线程时尽量不要嵌套加锁。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

AI结合机器人的入门级仿真环境有哪些?

由于使用真实的机器人开发和测试应用程序既昂贵又费时&#xff0c;因此仿真已成为机器人应用程序开发中越来越重要的部分。在部署到机器人之前在仿真中验证应用程序可以通过尽早发现潜在问题来缩短迭代时间。通过模拟&#xff0c;还可以更轻松地测试在现实世界中可能过于危险的…

Linux文件搜索工具(gnome-search-tool)

opensuse下安装: sudo zypper install gnome-search-tool 操作界面:

【剑指offr--C/C++】JZ59 滑动窗口的最大值

一、题目 二、思路及代码 暴力解法是依次往后滑动一位&#xff0c;然后比较窗口内的值。 我这里考虑&#xff1a;窗口每次往后移动一位&#xff0c;那么如果当前窗口的最大值max在窗口内部&#xff0c;那么再滑动到下一个窗口的时候&#xff0c;窗口内只有最新进来的一个元素没…

解决:CloudCompare中display选择Full screen后无法恢复且无法关闭

问题 在CloudCompare中display选择Full screen进行全屏显示时&#xff0c;软件各按钮失效且软件无法关闭 解决 按下F9键退出全屏模式&#xff0c;笔记本电脑可能需要FnF9同时按下。

算法 - 符号表-下

&#x1f600;前言 推荐从上看到下 算法 - 符号表-上 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 算法 - 符号表查找树1. 插入操作2. 性质 红黑树1. 左旋转2. 右旋转3. 颜色转换4. 插入5. 分析 散列表1. 散列函数2. 拉链法3. 线性探测法3.1 查找3.2 插入3.3 删除3.5 …

中兴天机A31 A31PRO 5G zte A2122H te A2022H 解锁BootLoader root权限 教程magisk,原厂刷机包

zte A2122H P768A02 zte A2022H P875A02 中兴天机A31 A31PRO 5G zte A2122H te A2022H 解锁BootLoader root教程magisk&#xff0c;原厂刷机包 感谢 某大神支持&#xff0c;已经解锁root 刷了面具&#xff1b; 中兴天机A31 A31PRO 5G zte A2122H te A2022H 解锁BootLoad…

Oracle 数据库中的全文搜索

Oracle 数据库中的全文搜索 0. 引言1. 整体流程2. 创建索引2-1. 创建一个简单的表2-2. 创建文本索引2-3. 查看创建的基础表 3. 运行查询3-1. 运行文本查询3-2. CONTAINS 运算符3-3. 混合查询3-4. OR 查询3-5. 通配符3-6. 短语搜索3-7. 模糊搜索&#xff08;Fuzzy searches&…

Francek Chen 的128天创作纪念日

目录 Francek Chen 的128天创作纪念日机缘收获日常成就憧憬 Francek Chen 的128天创作纪念日 Francek Chen 的个人主页 机缘 不知不觉的加入CSDN已有两年时间了&#xff0c;最初我第一次接触CSDN技术社区是在2022年4月的时候&#xff0c;通过学长给我们推荐了几个IT社区平台&a…

代码随想录-力扣刷题-总结笔记02

代码随想录&#xff1a;代码随想录力扣&#xff1a;力扣 (LeetCode) 全球极客挚爱的技术成长平台 代码随想录-力扣刷题-总结笔记01代码随想录-力扣刷题-总结笔记02 目录 01、代码随想录 00、其他 ArrayList转数组 07、二叉树 7.0、递归法 7.1、二叉树的层序遍历模板 7.2…

Lan仿朋友圈系统开源源码 表白墙 打造朋友圈工具 仿朋友圈界面 朋友圈模拟器在线

(购买本专栏可免费下载栏目内所有资源不受限制,持续发布中,需要注意的是,本专栏为批量下载专用,并无法保证某款源码或者插件绝对可用,介意不要购买!购买本专栏住如有什么源码需要,可向博主私信,第二天即可发布!博主有几万资源) Lan仿朋友圈系统开源,可用于表白墙等…

STL中各类容器详细介绍

STL介绍 STL&#xff08;Standard Template Library&#xff09;&#xff0c;即标准模板库&#xff0c;是一个具有工业强度的&#xff0c;高效的C程序库。它被容纳于C标准程序库&#xff08;C Standard Library&#xff09;中&#xff0c;是ANSI/ISO C标准中最新的也是极具革命…

tsv、csv、xls等文件类型区别及处理(python版)

目录 前言 介绍 tsv、csv、txt的区别 读取/生成 不同格式数据文件&#xff08;python&#xff09; 一、读取/生成csv数据文件 二、读取/生成txt数据文件 三、读取/生成tsv数据文件 四、读取/生成xls数据文件 不同文件格式转化 总结 前言 考虑到进行机器学习、深度学习…

代码随想录day35--动态规划的应用2||01背包理论基础、携带研究材料

01背包理论基础 有n件物品和一个最多能背重量为w的背包。第i件物品的重量是weight[i],得到的价值为 value[i]。每件物品只能用一次&#xff0c;将这些物品装入背包里物品价值总和最大。 这是很标准的背包问题&#xff0c;很多同学看到后很自然的就想到了背包&#xff0c;我们…

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

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

少儿编程 2024年3月电子学会图形化编程等级考试Scratch一级真题解析(选择题)

2024年3月scratch编程等级考试一级真题 选择题&#xff08;共25题&#xff0c;每题2分&#xff0c;共50分&#xff09; 1、单击下列哪个按钮&#xff0c;能够让舞台变为“全屏模式” A、 B、 C、 D、 答案&#xff1a;C 考点分析&#xff1a;考查scratch平台的使用&…

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…

【爬虫开发】爬虫从0到1全知识md笔记第3篇:数据提取概要,知识点【附代码文档】

爬虫开发从0到1全知识教程完整教程&#xff08;附代码资料&#xff09;主要内容讲述&#xff1a;爬虫课程概要&#xff0c;爬虫基础爬虫概述,,http协议复习。requests模块&#xff0c;requests模块1. requests模块介绍,2. response响应对象,3. requests模块发送请求,4. request…

接口练习题目

练习一 1、声明接口Eatable&#xff0c;包含抽象方法public abstract void eat(); 2、声明实现类中国人Chinese&#xff0c;重写抽象方法&#xff0c;打印用筷子吃饭 3、声明实现类美国人American&#xff0c;重写抽象方法&#xff0c;打印用刀叉吃饭 4、声明实现类印度人Indi…

深入Tauri开发——从环境搭建到项目构建

深入Tauri开发——从环境搭建到项目构建 开启你的Tauri桌面应用开发之旅&#xff08;续&#xff09; 经过上一篇文章的基础介绍&#xff0c;现在让我们更进一步&#xff0c;详细阐述如何在Windows和macOS平台上顺利搭建Tauri应用所需的开发环境&#xff0c;并指导您从创建项目…

Python搭建编程环境-安装Python3解释器

✅作者简介&#xff1a;CSDN内容合伙人、新星计划第三季Python赛道Top1&#x1f3c5; &#x1f525;本文已收录于Python系列专栏&#xff1a;零基础学Python &#x1f4ac;订阅专栏后可私信博主进入Python学习交流群&#xff0c;进群可领取Python视频教程以及Python相关电子书…