Java多线程--线程的安全问题与线程的同步机制介绍

文章目录

  • 一、线程安全问题
    • (1)介绍
    • (2)同一个资源问题和线程安全问题
      • 1、方式一:实现Runnable接口
        • 1.1 票数问题
        • 1.2 重票和错票问题
      • 2、方式二:继承Thread类
  • 二、安全问题分类总结
    • (1)局部变量不能共享
    • (2)不同对象的实例变量不共享
    • (3)静态变量是共享的
    • (4)同一个对象的实例变量共享
    • (5)抽取资源类,共享同一个资源对象

一、线程安全问题

(1)介绍

当我们使用多个线程访问同一资源(可以是同一个变量、同一个文件、同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题(因为不会对数据进行修改)。

但是如果多个线程中对资源有读和写的操作,就容易出现线程安全问题

举例:

image.png

“多线程的安全问题”是因为多线程对统一资源进行读和写操作时带来的。

(2)同一个资源问题和线程安全问题

【案例】

火车站要卖票,我们模拟火车站的卖票过程。因为疫情期间,本次列车的座位共100个(即,只能出售100张火车票)。我们来模拟车站的售票窗口,实现多个窗口同时售票的过程。

注意:不能出现错票、重票。

比如现在要开启三个窗口售票,总票数为100张

1、方式一:实现Runnable接口

<1> 卖票的票数

首先写一个卖票,用实现的方式建立一个线程,如下:

class SaleTicket implements Runnable{   //卖票

}

因为Runnable接口里面有抽象方法run,所以需要重写抽象方法,Ctrl+i快捷键调出来,OK即可,如下:

class SaleTicket implements Runnable{   //卖票

    @Override
    public void run() {

    }
}

现在需要卖票,一共是100张票,所以需要有一个变量ticket来表示票的数量。

class SaleTicket implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)

    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        int ticket=100;
    }
}
1.1 票数问题

🗳️这样写可以吗?

其实是不可以的,不能这样来表示100张票。

下一步我们需要做的是创建这个实现类的对象,具体创建线程的步骤如下:

public class WindowTest {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket s=new SaleTicket();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

当整个程序跑起来之后,就会各自调用run方法,三个线程调用run方法,就会有300张票了。

所以下面的写法不靠谱。

image.png

只需要把ticket拿到run()方法外面定义即可,因为在main方法里面,只创建了一个对象s,它被三个线程所共享了。

现在就没有问题了。

🌱代码

public class WindowTest {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket s=new SaleTicket();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
	int ticket=100;
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        
    }
}

<2> 售票

售票的逻辑当然是写在run方法里面的。

一共100张票,3个窗口,每个窗口不一定都拿到100/3张票,所以这里不知道循环了多少次,那就用while循环吧,只要ticket大于0就说明还有票,若是ticket等于0就说明没有票了,退出循环即可。

如下:

class SaleTicket implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

🌱代码

package yuyi02.notsafe;

/**
 * ClassName: WindowTest
 * Package: yuyi02.notsafe
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/27 0027 19:28
 */
public class WindowTest {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket s=new SaleTicket();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

🍺输出结果(部分)

image.png

🔥注意这里显示的票数不是严格递减的,如下:

image.png

卖票肯定是先卖票号大的,后面才卖小的,只不过这里输出要显示到控制台,显示的时候看着像单线程维度(显示的先后问题而已),其实是三个线程并发执行的,如下:

image.png

这里就不用刻意关注这个事情

🗳️但是这里有点问题,如下:

image.png

三个窗口都卖了100号这张票!这个是线程的安全问题,不应该出现这样的问题。

现在加一个sleep(),可能还会看到错票的情况,代码如下:

if(ticket>0){   //如果票数大于0就可以售票
    Thread.sleep(10);
	//...
}else{
    break;
}

记得处理一下异常:

try {
    Thread.sleep(10);
} catch (InterruptedException e) {
    e.printStackTrace();
}

🌱代码

package yuyi02.notsafe;

/**
 * ClassName: WindowTest
 * Package: yuyi02.notsafe
 * Description:
 *
 * @Author 雨翼轻尘
 * @Create 2024/1/27 0027 19:28
 */
public class WindowTest {
    public static void main(String[] args) {
        //3.创建当前实现类的对象
        SaleTicket s=new SaleTicket();

        //4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
        Thread t1 = new Thread(s);
        Thread t2 = new Thread(s);
        Thread t3 = new Thread(s);

        //给三个线程起名字
        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        //5.通过Thread类的实例调用start():1.启动线程 2.调用当前线程的run()。
        t1.start();
        t2.start();
        t3.start();
    }
}

class SaleTicket implements Runnable{   //卖票    1.创建一个实现Runnable接口的类(实现类)
    int ticket=100;
    @Override
    public void run() { //2.实现接口中的抽象方法run()方法
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

🍺输出结果(部分)

image.png

这里怎么还有-1的票?而且还有一样的票号,这也不行。


1.2 重票和错票问题

🍰对于这种重票和错票的问题,是怎么产生的呢?

卖票逻辑代码如下:

public void run() { //2.实现接口中的抽象方法run()方法
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
}

<1> 理想状态

首先有三个窗口卖100张票,就是三个线程,如下:

image.png

然后开始取ticket的票号,在run方法里面,判断票号是否大于0,若大于0就打印票号,如下:

image.png

若此时票号变成了0,这时候就执行else里面的break操作,就结束了,如下:

image.png


<2> 极端状态

上面是理想状态,现在来看一下极端状态,以错票来演示一下。

比如现在只有一张票了,如下:

在这里插入图片描述

票号是1,线程1判断票号大于0,判断是true,就进入到if结构里面,此时又有一个sleep,相当于它进入到阻塞状态,还没有实现“票号–”的操作。如下:

image.png

此时线程1被阻塞了,在被阻塞的10ms中,线程t2又进来了,然后发现ticket大于0,它也进入if里面了,也被阻塞,如下:

image.png

然后线程t3也进来了,也被阻塞了,如下:

image.png

然后它们相继结束阻塞,但是已经在if里面了,这也就意味着它们都会去卖票,所以这里会出现0号票和-1号票,如下:

image.png

注意我们写的程序里面是先打印输出再自减。

sleep相当于放大了线程阻塞的现象。

这里的重票和错票问题,就称为线程的安全问题

画个图看看“重票”吧:

image.png

Java是高并发的,不会出现并行的情况,在CPU层面只有并发执行。


☕那么对于重票和错票问题,是什么原因导致的呢?又如何解决呢?

如何避免重票和错票问题?

①原因

上面的问题在于,一个线程进入了if结构之后,还没有时间执行后续操作,另一个线程又进入了if结构。

即:线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作。

②解决

如何解决?

必须保证一个线程a在操作ticket的过程中,其它线程必须等待,直到线程a操作ticket结束以后,其它线程才可以进来继续操作ticket。(ticket就是所有线程共同操作的数据,就是共享数据

③Java是如何解决线程的安全问题的?

使用线程的同步机制

2、方式二:继承Thread类

卖票

public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }


}

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {

    }
}

run方法里面,就写逻辑代码。

首先是票的问题,不能定义在run方法里面,那放在外面吗?

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    int ticket=100;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {

    }
}

先来看卖票的细节,和上一个案例逻辑一致,如下:

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    int ticket=100;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

🌱代码

package yuyi02.notsafe;

/**
 * ClassName: WindowTest2
 * Package: yuyi02.notsafe
 * Description:
 *      使用继承Thread类的方式,实现卖票
 * @Author 雨翼轻尘
 * @Create 2024/1/27 0027 22:26
 */
public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }


}

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    int ticket=100;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        while (true){
            if(ticket>0){   //如果票数大于0就可以售票
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                //哪个窗口卖票了,票卖了多少
                System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);  //最开始票号为100
                ticket--;
            }else{
                break;
            }
        }
    }
}

🍺输出结果(部分)

image.png

可以发现,它重票的概率非常高,而且好像每一个都重复了,现在是卖了有300张票。

🎲为啥卖了300张票?

这就要用“面向对象”来解释了。

Window类里面声明了实例变量ticket,如下:

class Window extends Thread{    //卖票  1.创建一个继承于Thread类的子类
    //票
    int ticket=100;

    //2.重写Thread类的run() —>将此线程要执行的操作,声明在此方法体中
    @Override
    public void run() {
        //...
    }
}

实例变量是每个对象一份,现在有三个线程,三个对象卖票,意味着各自卖各自的,一共300张票。如下:

public class WindowTest2 {
    public static void main(String[] args) {
        //3.创建3个窗口  创建当前Thread的子类的对象
        Window w1=new Window();
        Window w2=new Window();
        Window w3=new Window();

        //命名
        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        //4.通过对象调用start(): 1.启动线程 2.调用当前线程的run()方法
        w1.start();
        w2.start();
        w3.start();
    }
}

所以这里票数不能这样写:int ticket=100;

那么加一个static呢?这样:static int ticket=100;现在ticket是静态变量,就是100张票了。

运行一下:

image.png

还是有重票和错票的问题出现,这是我们不希望看到的。

下一节来说在Java里面如何解决线程安全问题

二、安全问题分类总结

(1)局部变量不能共享

示例代码:

package com.atguigu.unsafe;

class Window extends Thread {
    public void run() {
        int ticket = 100;
        while (ticket > 0) {
            System.out.println(getName() + "卖出一张票,票号:" + ticket);
            ticket--;
        }
    }
}

public class SaleTicketDemo1 {
    public static void main(String[] args) {
        Window w1 = new Window();
        Window w2 = new Window();
        Window w3 = new Window();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

结果:发现卖出300张票。

问题:局部变量是每次调用方法都是独立的,那么每个线程的run()的ticket是独立的,不是共享数据。

(2)不同对象的实例变量不共享

package com.atguigu.unsafe;

class TicketWindow extends Thread {
    private int ticket = 100;

    public void run() {
        while (ticket > 0) {
            System.out.println(getName() + "卖出一张票,票号:" + ticket);
            ticket--;
        }
    }
}

public class SaleTicketDemo2 {
    public static void main(String[] args) {
        TicketWindow w1 = new TicketWindow();
        TicketWindow w2 = new TicketWindow();
        TicketWindow w3 = new TicketWindow();

        w1.setName("窗口1");
        w2.setName("窗口2");
        w3.setName("窗口3");

        w1.start();
        w2.start();
        w3.start();
    }
}

结果:发现卖出300张票。

问题:不同的实例对象的实例变量是独立的。

(3)静态变量是共享的

示例代码:

package com.atguigu.unsafe;

class TicketSaleThread extends Thread {
    private static int ticket = 100;

    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(10);//加入这个,使得问题暴露的更明显
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(getName() + "卖出一张票,票号:" + ticket);
            ticket--;
        }
    }
}

public class SaleTicketDemo3 {
    public static void main(String[] args) {
        TicketSaleThread t1 = new TicketSaleThread();
        TicketSaleThread t2 = new TicketSaleThread();
        TicketSaleThread t3 = new TicketSaleThread();

        t1.setName("窗口1");
        t2.setName("窗口2");
        t3.setName("窗口3");

        t1.start();
        t2.start();
        t3.start();
    }
}

运行结果:

窗口1卖出一张票,票号:100
窗口2卖出一张票,票号:100
窗口3卖出一张票,票号:100
窗口3卖出一张票,票号:97
窗口1卖出一张票,票号:97
窗口2卖出一张票,票号:97
窗口1卖出一张票,票号:94
窗口3卖出一张票,票号:94
窗口2卖出一张票,票号:94
窗口2卖出一张票,票号:91
窗口1卖出一张票,票号:91
窗口3卖出一张票,票号:91
窗口3卖出一张票,票号:88
窗口1卖出一张票,票号:88
窗口2卖出一张票,票号:88
窗口3卖出一张票,票号:85
窗口1卖出一张票,票号:85
窗口2卖出一张票,票号:85
窗口3卖出一张票,票号:82
窗口1卖出一张票,票号:82
窗口2卖出一张票,票号:82
窗口2卖出一张票,票号:79
窗口3卖出一张票,票号:79
窗口1卖出一张票,票号:79
窗口3卖出一张票,票号:76
窗口1卖出一张票,票号:76
窗口2卖出一张票,票号:76
窗口1卖出一张票,票号:73
窗口2卖出一张票,票号:73
窗口3卖出一张票,票号:73
窗口2卖出一张票,票号:70
窗口1卖出一张票,票号:70
窗口3卖出一张票,票号:70
窗口2卖出一张票,票号:67
窗口3卖出一张票,票号:67
窗口1卖出一张票,票号:67
窗口1卖出一张票,票号:64
窗口3卖出一张票,票号:64
窗口2卖出一张票,票号:64
窗口2卖出一张票,票号:61
窗口3卖出一张票,票号:61
窗口1卖出一张票,票号:61
窗口1卖出一张票,票号:58
窗口2卖出一张票,票号:58
窗口3卖出一张票,票号:58
窗口2卖出一张票,票号:55
窗口1卖出一张票,票号:55
窗口3卖出一张票,票号:55
窗口3卖出一张票,票号:52
窗口1卖出一张票,票号:52
窗口2卖出一张票,票号:52
窗口2卖出一张票,票号:49
窗口1卖出一张票,票号:49
窗口3卖出一张票,票号:49
窗口2卖出一张票,票号:46
窗口3卖出一张票,票号:46
窗口1卖出一张票,票号:46
窗口2卖出一张票,票号:43
窗口3卖出一张票,票号:43
窗口1卖出一张票,票号:43
窗口3卖出一张票,票号:40
窗口1卖出一张票,票号:40
窗口2卖出一张票,票号:40
窗口2卖出一张票,票号:37
窗口3卖出一张票,票号:37
窗口1卖出一张票,票号:37
窗口2卖出一张票,票号:34
窗口1卖出一张票,票号:34
窗口3卖出一张票,票号:34
窗口3卖出一张票,票号:31
窗口2卖出一张票,票号:31
窗口1卖出一张票,票号:31
窗口1卖出一张票,票号:28
窗口2卖出一张票,票号:28
窗口3卖出一张票,票号:28
窗口2卖出一张票,票号:25
窗口1卖出一张票,票号:25
窗口3卖出一张票,票号:25
窗口2卖出一张票,票号:22
窗口3卖出一张票,票号:22
窗口1卖出一张票,票号:22
窗口3卖出一张票,票号:19
窗口1卖出一张票,票号:19
窗口2卖出一张票,票号:19
窗口2卖出一张票,票号:16
窗口3卖出一张票,票号:16
窗口1卖出一张票,票号:16
窗口2卖出一张票,票号:13
窗口1卖出一张票,票号:13
窗口3卖出一张票,票号:13
窗口2卖出一张票,票号:10
窗口1卖出一张票,票号:10
窗口3卖出一张票,票号:10
窗口3卖出一张票,票号:7
窗口1卖出一张票,票号:7
窗口2卖出一张票,票号:7
窗口3卖出一张票,票号:4
窗口1卖出一张票,票号:4
窗口2卖出一张票,票号:4
窗口3卖出一张票,票号:1
窗口2卖出一张票,票号:1
窗口1卖出一张票,票号:1

结果:发现卖出近100张票。

问题1:但是有重复票或负数票问题。

原因:线程安全问题

问题2:如果要考虑有两场电影,各卖100张票等

原因:TicketThread类的静态变量,是所有TicketThread类的对象共享

(4)同一个对象的实例变量共享

示例代码:多个Thread线程使用同一个Runnable对象

package com.atguigu.safe;

class TicketSaleRunnable implements Runnable {
    private int ticket = 100;

    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(10);//加入这个,使得问题暴露的更明显
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);
            ticket--;
        }
    }
}

public class SaleTicketDemo4 {
    public static void main(String[] args) {
        TicketSaleRunnable tr = new TicketSaleRunnable();
        Thread t1 = new Thread(tr, "窗口一");
        Thread t2 = new Thread(tr, "窗口二");
        Thread t3 = new Thread(tr, "窗口三");

        t1.start();
        t2.start();
        t3.start();
    }
}

结果:发现卖出近100张票。

问题:但是有重复票或负数票问题。

原因:线程安全问题

(5)抽取资源类,共享同一个资源对象

示例代码:

package com.atguigu.unsafe;

//1、编写资源类
class Ticket {
    private int ticket = 100;

    public void sale() {
        if (ticket > 0) {
            try {
                Thread.sleep(10);//加入这个,使得问题暴露的更明显
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖出一张票,票号:" + ticket);
            ticket--;
        } else {
            throw new RuntimeException("没有票了");
        }
    }

    public int getTicket() {
        return ticket;
    }
}

public class SaleTicketDemo5 {
    public static void main(String[] args) {
        //2、创建资源对象
        Ticket ticket = new Ticket();

        //3、启动多个线程操作资源类的对象
        Thread t1 = new Thread("窗口一") {
            public void run() {
                while (true) {
                    ticket.sale();
                }
            }
        };
        Thread t2 = new Thread("窗口二") {
            public void run() {
                while (true) {
                    ticket.sale();
                }
            }
        };
        Thread t3 = new Thread(new Runnable() {
            public void run() {
                ticket.sale();
            }
        }, "窗口三");


        t1.start();
        t2.start();
        t3.start();
    }
}

结果:发现卖出近100张票。

问题:但是有重复票或负数票问题。

原因:线程安全问题。

下一节来介绍如何解决问题。

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

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

相关文章

如何使用宝塔面板搭建MySQL 5.5数据库并实现公网远程连接

文章目录 前言1.Mysql服务安装2.创建数据库3.安装cpolar3.2 创建HTTP隧道 4.远程连接5.固定TCP地址5.1 保留一个固定的公网TCP端口地址5.2 配置固定公网TCP端口地址 前言 宝塔面板的简易操作性,使得运维难度降低,简化了Linux命令行进行繁琐的配置,下面简单几步,通过宝塔面板cp…

当包容结构体遇见灵活的内存管理

&#x1f308;个人主页&#xff1a;小田爱学编程 &#x1f525; 系列专栏&#xff1a;c语言从基础到进阶 &#x1f3c6;&#x1f3c6;关注博主&#xff0c;随时获取更多关于c语言的优质内容&#xff01;&#x1f3c6;&#x1f3c6; &#x1f600;欢迎来到小田代码世界~ &#x…

MVCC原理讲解(深入浅出)

目录 一、什么是MVCC 二、当前读、快照读都是什么鬼 三、当前读 四、快照读 五、数据库的并发场景 六、MVCC解决并发的哪些问题 1.解决问题如下&#xff1a; 七、MVCC的实现原理 1.版本链 八、undo日志 1.undo log 的用途 2.undo log主要分为两种 九、Read View…

HCIP寒假第8次作业

第一步把ipv4网络配通 [r1]int g0/0/0 [r1-GigabitEthernet0/0/0]ip add 12.1.1.1 24 [r1-GigabitEthernet0/0/0]int l0 [r1-LoopBack0]ip add 1.1.1.1 32 [r1]ospf 1 router-id 1.1.1.1 [r1-ospf-1]area 0 [r1-ospf-1-area-0.0.0.0]network 0.0.0.0 255.255.255.255[r2]int g…

Linux使用匿名管道实现进程池得以高效通信

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;Nonsense—Sabrina Carpenter 0:50━━━━━━️&#x1f49f;──────── 2:43 &#x1f504; ◀️ ⏸ ▶️ …

Unity 外观模式(实例详解)

文章目录 示例1&#xff1a;初始化游戏场景中的多个子系统示例2&#xff1a;管理音频播放示例3&#xff1a;场景加载流程示例4&#xff1a;UI管理器示例5&#xff1a;网络服务通信 在Unity中使用外观模式&#xff08;Facade&#xff09;时&#xff0c;主要目的是为了简化复杂子…

Android创建工程

语言选择Java&#xff0c;我用的Java 最小SDK&#xff1a;就是开发的APP支持的最小安卓版本 Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具&#xff0c;支持 Maven&#xff0c;JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件&#xff0c;转而…

如何快速掌握DDT数据驱动测试?

前言 网盗概念相同的测试脚本使用不同的测试数据来执行&#xff0c;测试数据和测试行为完全分离&#xff0c; 这样的测试脚本设计模式称为数据驱动。(网盗结束)当我们测试某个网站的登录功能时&#xff0c;我们往往会使用不同的用户名和密码来验证登录模块对系统的影响&#x…

2023年:个人年度成长与团队协作成就

文章目录 个人职业发展的喜悦团队成就的辉煌公众号CSDN申请了移动安全领域新星创作者获得6月城市之星北京TOP 10获得23年博客之星TOP 41年度总结 知识星球 开拓新领域的决心免费知识大陆付费知识大陆 展望未来福利时间知识星球会员一年知识星球立减88券 在这个充满挑战与机遇的…

Linux 挂载读取、卸载 ntfs格式硬盘

windows常用的ntfs硬盘分区格式&#xff0c;在linux通常不能直接读取&#xff0c;不过挂载也是非常容易 一、挂载ntfs分区 1.安装 apt-get install ntfs-3g2.查看现在接上的硬盘 fdisk -l可以找到类似如下的&#xff0c;会显示microsoft basic data 3.创建挂载的目录 创…

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求

全能相似度计算与语义匹配搜索工具包,多维度实现多种算法,涵盖文本、图像等领域。支持文图搜索,满足您在不同场景下的搜索需求。 Similarities:精准相似度计算与语义匹配搜索工具包,多维度实现多种算法,覆盖文本、图像等领域,支持文搜、图搜文、图搜图匹配搜索 Similar…

腾讯云超60款产品,限时免费试用!

懂行的开发同学都应该知道&#xff0c;腾讯云的产品嘎嘎好用&#xff01; 很多同学只能眼红&#xff0c;却不能体验一二。毕竟付费的门槛在那里&#xff0c;体验不到&#xff0c;自然没办法做出评价。 今天&#xff0c;开放10个免费体验产品的名额给你&#xff0c;想体验哪款…

4核16G幻兽帕鲁服务器优惠价格表,阿里云和腾讯云报价

幻兽帕鲁服务器价格多少钱&#xff1f;4核16G服务器Palworld官方推荐配置&#xff0c;阿里云4核16G服务器32元1个月、96元3个月&#xff0c;腾讯云幻兽帕鲁服务器服务器4核16G14M带宽66元一个月、277元3个月&#xff0c;8核32G22M配置115元1个月、345元3个月&#xff0c;16核64…

【文本到上下文 #7】探索 NLP 中的 Seq2Seq、编码器-解码器和注意力机制

一、说明 今天&#xff0c;我们将探讨序列到序列 &#xff08;seq2seq&#xff09; 模型的复杂工作原理&#xff0c;特别关注编码器-解码器架构和注意力机制。这些概念是各种 NLP 应用的基础&#xff0c;从机器翻译到问答系统。 这是可以期待的&#xff1a; Seq2Seq模型中的编码…

【技术分享】远程透传网关-单网口快速实现威纶通触摸屏程序远程上下载

准备工作 一台可联网操作的电脑一台单网口的远程透传网关及博达远程透传配置工具网线一条&#xff0c;用于实现网络连接和连接触摸屏一台威纶通触摸屏及其编程软件一张4G卡或WIFI天线实现通讯(使用4G联网则插入4G SIM卡&#xff0c;WIFI联网则将WIFI天线插入USB口&#xff09;…

Linux实验记录:添加硬盘设备

前言&#xff1a; 本文是一篇关于Linux系统初学者的实验记录。 参考书籍&#xff1a;《Linux就该这么学》 实验环境&#xff1a; VmwareWorkStation 17——虚拟机软件 RedHatEnterpriseLinux[RHEL]8——红帽操作系统 目录 前言&#xff1a; 备注&#xff1a; 添加硬盘…

error: failed to open index: Database already open. Cannot acquire lock报错解决办法

ordinals节点数据同步出现报错 error: failed to open index: Database already open. Cannot acquire lock.问题分析&#xff1a; 出现问题的原因是btcoin core节点数据没有同步完我们就开始进行ordinals数据同步&#xff0c;导致/root/.local/share/ord/index.redb 文件数据…

自然语言处理,基于预训练语言模型的方法,车万翔,引言部分

文章目录 自然语言处理应用任务1. 信息抽取2. 情感分析3. 问答系统4. 机器翻译5. 对话系统 自然语言处理应用任务 1. 信息抽取 信息抽取&#xff08;Information Extraction, IE&#xff09;&#xff0c;是从非结构化的文本中&#xff0c;抽取出结构化信息的过程&#xff0c;…

GBASE南大通用分享-ConnectionTimeout 属性

GBASE南大通用分享 获取或设置连接超时时间&#xff0c;值为‚0‛时没有限制。  语法 [Visual Basic] Public Overrides ReadOnly Property ConnectionTimeout As Integer Get [C#] public override int ConnectionTimeout { get; }  实现 IDbConnection.Connecti…

拼接url - 华为OD统一考试

OD统一考试 分值&#xff1a; 100分 题解&#xff1a; Java / Python / C 题目描述 给定一个 url 前缀和 url 后缀, 通过 “,” 分割&#xff0c; 需要将其连接为一个完整的 url 。 如果前缀结尾和后缀开头都没有 /&#xff0c;需要自动补上 / 连接符&#xff1b; 如果前缀结…