Volatile不保证原子性

news2024/11/18 9:46:49

目录

前言

原子性

代码测试

为什么出现数值丢失

如何解决

其它解决方法

字节码指令表


前言

通过前面对JMM的介绍,我们知道,各个线程对主内存中共享变量的操作都是各个线程各自拷贝到自己的工作内存进行操作后在写回到主内存中的。

这就可能存在一个线程AAA修改了共享变量X的值,但是还未写入主内存时,另外一个线程BBB又对主内存中同一共享变量X进行操作,但此时A线程工作内存中共享变量X对线程B来说是不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题。

这种情况 在数据库事务中称为脏写,只是 这里是线程最终是写入共享内存,而事务是提交操作

原子性

不可分割,完整性,也就是说某个线程正在做某个具体业务时,中间不可以被加塞或者被分割,需要具体完成,要么同时成功,要么同时失败。

数据库也经常提到事务具备原子性。

代码测试

为了测试volatile是否保证原子性,我们创建了20个线程,然后每个线程分别循环1000次,来调用number++的方法

MyData myData = new MyData();

        // 创建10个线程,线程里面进行1000次循环
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                // 里面
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }
            }, String.valueOf(i)).start();
        }

最后通过 Thread.activeCount(),来感知20个线程是否执行完毕,这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程

// 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值
while(Thread.activeCount() > 2) {
    // yield表示不执行
    Thread.yield();
}

然后在线程执行完毕后,我们在查看number的值,假设volatile保证原子性的话,那么最后输出的值应该是

20 * 1000 = 20000,

完整代码如下所示:


/**
 * Volatile Java虚拟机提供的轻量级同步机制
 *
 * 可见性(及时通知)
 * 不保证原子性
 * 禁止指令重排
 *
 * @create: 2020-03-09-15:58
 */

import java.util.concurrent.TimeUnit;

/**
 * 假设是主物理内存
 */
class MyData {
    /**
     * volatile 修饰的关键字,是为了增加 主线程和线程之间的可见性,只要有一个线程修改了内存中的值,其它线程也能马上感知
     */
    volatile int number = 0;

    public void addTo60() {
        this.number = 60;
    }

    /**
     * 注意,此时number 前面是加了volatile修饰
     */
    public void addPlusPlus() {
        number ++;
    }
}

/**
 * 验证volatile的可见性
 * 1、 假设int number = 0, number变量之前没有添加volatile关键字修饰
 * 2、添加了volatile,可以解决可见性问题
 *
 * 验证volatile不保证原子性
 * 1、原子性指的是什么意思?
 */
public class VolatileDemo {

    public static void main(String args []) {

        MyData myData = new MyData();

        // 创建10个线程,线程里面进行1000次循环
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                // 里面
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                }
            }, String.valueOf(i)).start();
        }

        // 需要等待上面20个线程都计算完成后,在用main线程取得最终的结果值
        // 这里判断线程数是否大于2,为什么是2?因为默认是有两个线程的,一个main线程,一个gc线程
        while(Thread.activeCount() > 2) {
            // yield表示不执行
            Thread.yield();
        }

        // 查看最终的值
        // 假设volatile保证原子性,那么输出的值应该为:  20 * 1000 = 20000
        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);

    }
}

最终结果我们会发现,number输出的值并没有20000,而且是每次运行的结果都不一致的,这说明了volatile修饰的变量不保证原子性

第一次:

 第二次

 第三次:

 

为什么出现数值丢失

 

各自线程在写入主内存的时候,出现了数据的丢失,而引起的数值缺失的问题

下面我们将一个简单的number++操作,转换为字节码文件一探究竟

public class T1 {
    volatile int n = 0;
    public void add() {
        n++;
    }
}

转换后的字节码文件

javap -c

public class com.moxi.interview.study.thread.T1 {
  volatile int n;

  public com.moxi.interview.study.thread.T1();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_0
       6: putfield      #2                  // Field n:I
       9: return

  public void add();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2                  // Field n:I
       5: iconst_1
       6: iadd
       7: putfield      #2                  // Field n:I
      10: return
}
关于字节码文件的解读,可以参JAVA虚拟机规范

如果出现了找不到指定类,那是因为我们创建的是spring boot的maven项目,我们之前需要执行mvn package命令,进行打包操作,将其编译成class文件

移动到底部,有一份字节码指令对照表,方便我们进行阅读

下面我们就针对 add() 这个方法的字节码文件进行分析

  public void add();
    Code:
       0: aload_0
       1: dup
       2: getfield      #2    // Field n:I
       5: iconst_1
       6: iadd
       7: putfield      #2    // Field n:I
      10: return

我们能够发现 n++这条命令,被拆分成了3个指令

  • 执行getfield 从主内存拿到原始n
  • 执行iadd 进行加1操作
  • 执行putfileld 把累加后的值写回主内存

假设我们没有加 synchronized那么第一步就可能存在着,三个线程同时通过getfield命令,拿到主存中的 n值,然后三个线程,各自在自己的工作内存中进行加1操作,但他们并发进行 iadd 命令的时候,因为只能一个进行写,所以其它操作会被挂起,假设1线程,先进行了写操作,在写完后,volatile的可见性,应该需要告诉其它两个线程,主内存的值已经被修改了,但是因为太快了,其它两个线程,陆续执行 iadd命令,进行写入操作,这就造成了其他线程没有接受到主内存n的改变从而覆盖了原来的值,出现写丢失,这样也就让最终的结果少于20000

如何解决

因此这也说明,在多线程环境下 number ++ 在多线程环境下是非线程安全的,解决的方法有哪些呢?

  • 在方法上加入 synchronized
public synchronized void addPlusPlus() {
        number ++;
    }

 

我们能够发现引入synchronized关键字后,保证了该方法每次只能够一个线程进行访问和操作,最终输出的结果也就为20000

其它解决方法

上面的方法引入synchronized,虽然能够保证原子性,但是为了解决number++,而引入重量级的同步机制,有种 杀鸡焉用牛刀

除了引用synchronized关键字外,还可以使用JUC下面的原子包装类,即刚刚的int类型的number,可以使用AtomicInteger来代替

    /**
     *  创建一个原子Integer包装类,默认为0
      */
    AtomicInteger atomicInteger = new AtomicInteger();

    public void addAtomic() {
        // 相当于 atomicInter ++
        atomicInteger.getAndIncrement();
    }

然后同理,继续刚刚的操作

        // 创建10个线程,线程里面进行1000次循环
        for (int i = 0; i < 20; i++) {
            new Thread(() -> {
                // 里面
                for (int j = 0; j < 1000; j++) {
                    myData.addPlusPlus();
                    myData.addAtomic();
                }
            }, String.valueOf(i)).start();
        }
// 假设volatile保证原子性,那么输出的值应该为:  20 * 1000 = 20000
        System.out.println(Thread.currentThread().getName() + "\t finally number value: " + myData.number);
        System.out.println(Thread.currentThread().getName() + "\t finally atomicNumber value: " + myData.atomicInteger);

下面的结果,一个是引入synchronized,一个是使用了原子包装类AtomicInteger

 

字节码指令表

为了方便阅读JVM字节码文件,我从网上找了一份字节码指令表

JVM字节码指令表

字节码助记符指令含义
0x00nopNone
0x01aconst_null将null推送至栈顶
0x02iconst_m1将int型-1推送至栈顶
0x03iconst_0将int型0推送至栈顶
0x04iconst_1将int型1推送至栈顶
0x05iconst_2将int型2推送至栈顶
0x06iconst_3将int型3推送至栈顶
0x07iconst_4将int型4推送至栈顶
0x08iconst_5将int型5推送至栈顶
0x09lconst_0将long型0推送至栈顶
0x0alconst_1将long型1推送至栈顶
0x0bfconst_0将float型0推送至栈顶
0x0cfconst_1将float型1推送至栈顶
0x0dfconst_2将float型2推送至栈顶
0x0edconst_0将double型0推送至栈顶
0x0fdconst_1将double型1推送至栈顶
0x10bipush将单字节的常量值(-128~127)推送至栈顶
0x11sipush将一个短整型常量(-32768~32767)推送至栈顶
0x12ldc将int,float或String型常量值从常量池中推送至栈顶
0x13ldc_w将int,float或String型常量值从常量池中推送至栈顶(宽索引)
0x14ldc2_w将long或double型常量值从常量池中推送至栈顶(宽索引)
0x15iload将指定的int型本地变量推送至栈顶
0x16lload将指定的long型本地变量推送至栈顶
0x17fload将指定的float型本地变量推送至栈顶
0x18dload将指定的double型本地变量推送至栈顶
0x19aload将指定的引用类型本地变量推送至栈顶
0x1aiload_0将第一个int型本地变量推送至栈顶
0x1biload_1将第二个int型本地变量推送至栈顶
0x1ciload_2将第三个int型本地变量推送至栈顶
0x1diload_3将第四个int型本地变量推送至栈顶
0x1elload_0将第一个long型本地变量推送至栈顶
0x1flload_1将第二个long型本地变量推送至栈顶
0x20lload_2将第三个long型本地变量推送至栈顶
0x21lload_3将第四个long型本地变量推送至栈顶
0x22fload_0将第一个float型本地变量推送至栈顶
0x23fload_1将第二个float型本地变量推送至栈顶
0x24fload_2将第三个float型本地变量推送至栈顶
0x25fload_3将第四个float型本地变量推送至栈顶
0x26dload_0将第一个double型本地变量推送至栈顶
0x27dload_1将第二个double型本地变量推送至栈顶
0x28dload_2将第三个double型本地变量推送至栈顶
0x29dload_3将第四个double型本地变量推送至栈顶
0x2aaload_0将第一个引用类型本地变量推送至栈顶
0x2baload_1将第二个引用类型本地变量推送至栈顶
0x2caload_2将第三个引用类型本地变量推送至栈顶
0x2daload_3将第四个引用类型本地变量推送至栈顶
0x2eiaload将int型数组指定索引的值推送至栈顶
0x2flaload将long型数组指定索引的值推送至栈顶
0x30faload将float型数组指定索引的值推送至栈顶
0x31daload将double型数组指定索引的值推送至栈顶
0x32aaload将引用类型数组指定索引的值推送至栈顶
0x33baload将boolean或byte型数组指定索引的值推送至栈顶
0x34caload将char型数组指定索引的值推送至栈顶
0x35saload将short型数组指定索引的值推送至栈顶
0x36istore将栈顶int型数值存入指定本地变量
0x37lstore将栈顶long型数值存入指定本地变量
0x38fstore将栈顶float型数值存入指定本地变量
0x39dstore将栈顶double型数值存入指定本地变量
0x3aastore将栈顶引用类型数值存入指定本地变量
0x3bistore_0将栈顶int型数值存入第一个本地变量
0x3cistore_1将栈顶int型数值存入第二个本地变量
0x3distore_2将栈顶int型数值存入第三个本地变量
0x3eistore_3将栈顶int型数值存入第四个本地变量
0x3flstore_0将栈顶long型数值存入第一个本地变量
0x40lstore_1将栈顶long型数值存入第二个本地变量
0x41lstore_2将栈顶long型数值存入第三个本地变量
0x42lstore_3将栈顶long型数值存入第四个本地变量
0x43fstore_0将栈顶float型数值存入第一个本地变量
0x44fstore_1将栈顶float型数值存入第二个本地变量
0x45fstore_2将栈顶float型数值存入第三个本地变量
0x46fstore_3将栈顶float型数值存入第四个本地变量
0x47dstore_0将栈顶double型数值存入第一个本地变量
0x48dstore_1将栈顶double型数值存入第二个本地变量
0x49dstore_2将栈顶double型数值存入第三个本地变量
0x4adstore_3将栈顶double型数值存入第四个本地变量
0x4bastore_0将栈顶引用型数值存入第一个本地变量
0x4castore_1将栈顶引用型数值存入第二个本地变量
0x4dastore_2将栈顶引用型数值存入第三个本地变量
0x4eastore_3将栈顶引用型数值存入第四个本地变量
0x4fiastore将栈顶int型数值存入指定数组的指定索引位置
0x50lastore将栈顶long型数值存入指定数组的指定索引位置
0x51fastore将栈顶float型数值存入指定数组的指定索引位置
0x52dastore将栈顶double型数值存入指定数组的指定索引位置
0x53aastore将栈顶引用型数值存入指定数组的指定索引位置
0x54bastore将栈顶boolean或byte型数值存入指定数组的指定索引位置
0x55castore将栈顶char型数值存入指定数组的指定索引位置
0x56sastore将栈顶short型数值存入指定数组的指定索引位置
0x57pop将栈顶数值弹出(数值不能是long或double类型的)
0x58pop2将栈顶的一个(对于非long或double类型)或两个数值(对于非long或double的其他类型)弹出
0x59dup复制栈顶数值并将复制值压入栈顶
0x5adup_x1复制栈顶数值并将两个复制值压入栈顶
0x5bdup_x2复制栈顶数值并将三个(或两个)复制值压入栈顶
0x5cdup2复制栈顶一个(对于long或double类型)或两个(对于非long或double的其他类型)数值并将复制值压入栈顶
0x5ddup2_x1dup_x1指令的双倍版本
0x5edup2_x2dup_x2指令的双倍版本
0x5fswap将栈顶最顶端的两个数值互换(数值不能是long或double类型)
0x60iadd将栈顶两int型数值相加并将结果压入栈顶
0x61ladd将栈顶两long型数值相加并将结果压入栈顶
0x62fadd将栈顶两float型数值相加并将结果压入栈顶
0x63dadd将栈顶两double型数值相加并将结果压入栈顶
0x64isub将栈顶两int型数值相减并将结果压入栈顶
0x65lsub将栈顶两long型数值相减并将结果压入栈顶
0x66fsub将栈顶两float型数值相减并将结果压入栈顶
0x67dsub将栈顶两double型数值相减并将结果压入栈顶
0x68imul将栈顶两int型数值相乘并将结果压入栈顶
0x69lmul将栈顶两long型数值相乘并将结果压入栈顶
0x6afmul将栈顶两float型数值相乘并将结果压入栈顶
0x6bdmul将栈顶两double型数值相乘并将结果压入栈顶
0x6cidiv将栈顶两int型数值相除并将结果压入栈顶
0x6dldiv将栈顶两long型数值相除并将结果压入栈顶
0x6efdiv将栈顶两float型数值相除并将结果压入栈顶
0x6fddiv将栈顶两double型数值相除并将结果压入栈顶
0x70irem将栈顶两int型数值作取模运算并将结果压入栈顶
0x71lrem将栈顶两long型数值作取模运算并将结果压入栈顶
0x72frem将栈顶两float型数值作取模运算并将结果压入栈顶
0x73drem将栈顶两double型数值作取模运算并将结果压入栈顶
0x74ineg将栈顶int型数值取负并将结果压入栈顶
0x75lneg将栈顶long型数值取负并将结果压入栈顶
0x76fneg将栈顶float型数值取负并将结果压入栈顶
0x77dneg将栈顶double型数值取负并将结果压入栈顶
0x78ishl将int型数值左移指定位数并将结果压入栈顶
0x79lshl将long型数值左移指定位数并将结果压入栈顶
0x7aishr将int型数值右(带符号)移指定位数并将结果压入栈顶
0x7blshr将long型数值右(带符号)移指定位数并将结果压入栈顶
0x7ciushr将int型数值右(无符号)移指定位数并将结果压入栈顶
0x7dlushr将long型数值右(无符号)移指定位数并将结果压入栈顶
0x7eiand将栈顶两int型数值"按位与"并将结果压入栈顶
0x7fland将栈顶两long型数值"按位与"并将结果压入栈顶
0x80ior将栈顶两int型数值"按位或"并将结果压入栈顶
0x81lor将栈顶两long型数值"按位或"并将结果压入栈顶
0x82ixor将栈顶两int型数值"按位异或"并将结果压入栈顶
0x83lxor将栈顶两long型数值"按位异或"并将结果压入栈顶
0x84iinc将指定int型变量增加指定值(如i++, i--, i+=2等)
0x85i2l将栈顶int型数值强制转换为long型数值并将结果压入栈顶
0x86i2f将栈顶int型数值强制转换为float型数值并将结果压入栈顶
0x87i2d将栈顶int型数值强制转换为double型数值并将结果压入栈顶
0x88l2i将栈顶long型数值强制转换为int型数值并将结果压入栈顶
0x89l2f将栈顶long型数值强制转换为float型数值并将结果压入栈顶
0x8al2d将栈顶long型数值强制转换为double型数值并将结果压入栈顶
0x8bf2i将栈顶float型数值强制转换为int型数值并将结果压入栈顶
0x8cf2l将栈顶float型数值强制转换为long型数值并将结果压入栈顶
0x8df2d将栈顶float型数值强制转换为double型数值并将结果压入栈顶
0x8ed2i将栈顶double型数值强制转换为int型数值并将结果压入栈顶
0x8fd2l将栈顶double型数值强制转换为long型数值并将结果压入栈顶
0x90d2f将栈顶double型数值强制转换为float型数值并将结果压入栈顶
0x91i2b将栈顶int型数值强制转换为byte型数值并将结果压入栈顶
0x92i2c将栈顶int型数值强制转换为char型数值并将结果压入栈顶
0x93i2s将栈顶int型数值强制转换为short型数值并将结果压入栈顶
0x94lcmp比较栈顶两long型数值大小, 并将结果(1, 0或-1)压入栈顶
0x95fcmpl比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x96fcmpg比较栈顶两float型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x97dcmpl比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将-1压入栈顶
0x98dcmpg比较栈顶两double型数值大小, 并将结果(1, 0或-1)压入栈顶; 当其中一个数值为NaN时, 将1压入栈顶
0x99ifeq当栈顶int型数值等于0时跳转
0x9aifne当栈顶int型数值不等于0时跳转
0x9biflt当栈顶int型数值小于0时跳转
0x9cifge当栈顶int型数值大于等于0时跳转
0x9difgt当栈顶int型数值大于0时跳转
0x9eifle当栈顶int型数值小于等于0时跳转
0x9fif_icmpeq比较栈顶两int型数值大小, 当结果等于0时跳转
0xa0if_icmpne比较栈顶两int型数值大小, 当结果不等于0时跳转
0xa1if_icmplt比较栈顶两int型数值大小, 当结果小于0时跳转
0xa2if_icmpge比较栈顶两int型数值大小, 当结果大于等于0时跳转
0xa3if_icmpgt比较栈顶两int型数值大小, 当结果大于0时跳转
0xa4if_icmple比较栈顶两int型数值大小, 当结果小于等于0时跳转
0xa5if_acmpeq比较栈顶两引用型数值, 当结果相等时跳转
0xa6if_acmpne比较栈顶两引用型数值, 当结果不相等时跳转
0xa7goto无条件跳转
0xa8jsr跳转至指定的16位offset位置, 并将jsr的下一条指令地址压入栈顶
0xa9ret返回至本地变量指定的index的指令位置(一般与jsr或jsr_w联合使用)
0xaatableswitch用于switch条件跳转, case值连续(可变长度指令)
0xablookupswitch用于switch条件跳转, case值不连续(可变长度指令)
0xacireturn从当前方法返回int
0xadlreturn从当前方法返回long
0xaefreturn从当前方法返回float
0xafdreturn从当前方法返回double
0xb0areturn从当前方法返回对象引用
0xb1return从当前方法返回void
0xb2getstatic获取指定类的静态域, 并将其压入栈顶
0xb3putstatic为指定类的静态域赋值
0xb4getfield获取指定类的实例域, 并将其压入栈顶
0xb5putfield为指定类的实例域赋值
0xb6invokevirtual调用实例方法
0xb7invokespecial调用超类构建方法, 实例初始化方法, 私有方法
0xb8invokestatic调用静态方法
0xb9invokeinterface调用接口方法
0xbainvokedynamic调用动态方法
0xbbnew创建一个对象, 并将其引用引用值压入栈顶
0xbcnewarray创建一个指定的原始类型(如int, float, char等)的数组, 并将其引用值压入栈顶
0xbdanewarray创建一个引用型(如类, 接口, 数组)的数组, 并将其引用值压入栈顶
0xbearraylength获取数组的长度值并压入栈顶
0xbfathrow将栈顶的异常抛出
0xc0checkcast检验类型转换, 检验未通过将抛出 ClassCastException
0xc1instanceof检验对象是否是指定类的实际, 如果是将1压入栈顶, 否则将0压入栈顶
0xc2monitorenter获得对象的锁, 用于同步方法或同步块
0xc3monitorexit释放对象的锁, 用于同步方法或同步块
0xc4wide扩展本地变量的宽度
0xc5multianewarray创建指定类型和指定维度的多维数组(执行该指令时, 操作栈中必须包含各维度的长度值), 并将其引用压入栈顶
0xc6ifnull为null时跳转
0xc7ifnonnull不为null时跳转
0xc8goto_w无条件跳转(宽索引)
0xc9jsr_w跳转至指定的32位offset位置, 并将jsr_w的下一条指令地址压入栈顶

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

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

相关文章

Java Iterator(迭代器)

Java Iterator&#xff08;迭代器&#xff09;不是一个集合&#xff0c;它是一种用于访问集合的方法&#xff0c;可用于迭代 ArrayList 和 HashSet 等集合。 Iterator 是 Java 迭代器最简单的实现&#xff0c;ListIterator 是 Collection API 中的接口&#xff0c; 它扩展了 I…

微信小程序和ros2进行通信

微信小程序和ros2进行通信环境配置ubuntu下安装ros2ubuntu安装mqtt库windows安装微信开发者工具主要功能模块介绍ROS2基础程序讲解微信小程序基础程序讲解编译及运行ros2程序编译及运行微信小程序主要事项ROS2做为一款优秀的机器人操作系统软件&#xff0c;其搭载了丰富的机器人…

深耕低代码领域20年,这个老牌低代码平台有多靠谱

编者按&#xff1a;市面上低代码平台多如牛毛&#xff0c;用户在选型低代码平台时该如何甄别&#xff1f;本文通过介绍20年的老品牌低代码平台的特性&#xff0c;展示了优秀第低代码平台的魅力。 关键词&#xff1a;老厂商&#xff0c;私有化部署&#xff0c;源码交付&#xf…

实现effect的stop和onStop功能

06_实现effect的stop和onStop功能 一、实现stop &#xff08;一&#xff09;单元测试 it(stop, () > {let dummy;const obj reactive({ prop: 1 });const runner effect(() > {dummy obj.prop;});obj.prop 2;expect(dummy).toBe(2);stop(runner);obj.prop 3;expe…

zookeeper学习笔记1(小D课堂)

win和linux双环境安装zookeeper 我们不点击这个download。 进行解压&#xff1a; 我们进入到conf目录。 我们给这个文件进行重命名。 接下来我们去打开它&#xff0c;去修改一下配置&#xff1a; 我们先去掉这部分注释。 去掉注释&#xff0c;一共就这些内容。 tickTime是我们的…

如何开通阿里云语音通知服务?

阿里云语音服务是阿里云为用户提供的一种通信服务的能力。支持快速发送语音通知服务。 安全级别更高&#xff0c;难窃取。支持大容量、高并发&#xff0c;稳定可靠。 一、如何开通阿里云语音服务&#xff1f; 注册阿里云平台账户&#xff1b;实名登记认证&#xff1b;阿里云语…

手把手教你 如何利用github搭建个人网站 无需服务器

目录 前言 准备工作 教程来啦&#xff01;&#xff01;&#xff01; 第一部分 第二部分 链接说明 前言 哈喽&#xff0c;大家好&#xff0c;我是木易巷。 今天给大家分享一下&#xff1a;如何使用GitHub创建自己的个人网站&#xff1f; 准备工作 需要使用GitHub&#xff0c;当…

【python】语法分析-化学分子式解析「编译原理」

题目 编写程序&#xff0c;计算化学分子式中元素的数目&#xff0c;并完成以下测试&#xff1a; atom_count(“He”) 1 atom_count(“H2”) 2 atom_count(“H2SO4”) 7 atom_count(“CH3COOH”) 8 atom_count(“NaCl”) 2 atom_count(“C60H60”) 120 参考语法 specie…

学习周报-20221230

文章目录一 如何设置字符集二 NFS配置文件父目录权限影响子目录三 对IP分组可以批量管理NFS客户端首先查看系统环境 [rootnfs-server ~]# cat /etc/redhat-release Red Hat Enterprise Linux release 8.7 (Ootpa) [rootnfs-server ~]# uname -r 4.18.0-425.3.1.el8.x86_64这是…

JAVA零基础小白学习免费教程day13-Collection数据结构

day13_JAVAOOP 课程目标 1. 【理解】集合的体系结构 2. 【掌握】Collection集合中常用的方法 3. 【理解】Iterator迭代器 4. 【掌握】增强for的使用 5. 【理解】List集合的特点 6. 【掌握】List集合中特有的方法 7. 【理解】LinkedList集合的特点 8. 【理解】LinkedList集合中…

解读YOLO v7的代码(二)训练数据的准备

在上一篇文章解读YOLO v7的代码(一)模型结构研究_gzroy的博客-CSDN博客&#xff0c;我对Yolo v7的模型结构进行了分析&#xff0c;那么这次我们将进一步研读代码的关键部分&#xff0c;学习是如何对模型进行训练的。 训练数据的准备是模型训练的关键&#xff0c;通常我们需要对…

保姆教程系列一、什么?Redis部署 so easy

系列文章目录 &#xff01;&#xff01;&#xff01;是的没错&#xff0c;胖友们&#xff0c;保姆教程系列又更新了&#xff01;&#xff01;&#xff01; 保姆教程系列一、Redis部署 so easy 保姆教程系列二、Redis高可用&#xff08;主从同步哨兵模式&#xff09; 保姆教程系…

(3)Qt中的变体数据类型(QVariant)

QVariant的使用 QVariant(变体数据类型)这个类很神奇&#xff0c;或者说方便。很多时候&#xff0c;需要几种不同的数据类型需要传递&#xff0c;如果用结构体&#xff0c;又不大方便&#xff0c;容器保存的也只是一种数据类型&#xff0c;而QVariant则可以统统搞定。QVariant …

ceph-mds文件系统操作指南

前言&#xff1a;ceph-mds文件系统操作&#xff0c;常规操作汇总&#xff0c;看这一篇就够了 一、文件系统简介 Ceph 文件系统 (CephFS) 是兼容 POSIX 标准的文件系统&#xff0c;在 Ceph 的分布式对象存储基础上构建&#xff0c;称为 RADOS&#xff08;可靠的自主分布式对象存…

用户级线程和内核级线程

线程的实现可以分为两类&#xff1a;用户级线程和内核级线程&#xff0c;后者又称为内核支持的线程或轻量级进程。在多线程操作系统中&#xff0c;各个系统的实现方式并不相同&#xff0c;在有的系统中实现了用户级线程&#xff0c;有的系统中实现了内核级线程。 用户级线程&am…

Python数据分析案例13——文本特征抽取(TfidfVectorizer)

在做机器学习的时候&#xff0c;构建特征变量有很多时候都是文本型的&#xff0c;比如电影分类的时候的电影标题&#xff0c;房价预测的时候房子地址&#xff0c;股吧评论等......都是文本类型的数据。 文本型数据怎么构建特征&#xff0c;它又不是分类变量不能直接独立热编码…

【再学Tensorflow2】TensorFlow2的模型训练组件(2)

TensorFlow2的模型训练组件&#xff08;2&#xff09;损失函数损失函数和正则化项Tensorflow2内置的损失函数自定义损失函数评估指标常用的内置评估指标自定义评估指标优化器优化器的使用使用optimizer.apply_gradients使用optimizer.minimize使用model.fitTensorflow2内置的优…

【nowcoder】笔试强训Day14

目录 一、选择题 二、编程题 2.1计算日期到天数转换 2.2幸运的袋子 一、选择题 1.定义学生、教师和课程的关系模式 S (S#,Sn,Sd,Dc,SA &#xff09;&#xff08;其属性分别为学号、姓名、所在系、所在系的系主任、年龄&#xff09;&#xff1b; C &#xff08; C#,Cn,P# &…

ansible的安装以及实例

目录 ansible的安装&#xff1a; 一、配置centos8基本源 二、配置epel 三、安装ansible 四、查看ansible是否安装以及版本 实例&#xff1a; 实例一&#xff1a;控制主机和受控主机通过root用户免密验证远程控制主机实施对应任务 实例二&#xff1a;控制主机连接受控主机…

SSH协议理论讲解

目录 基本概念 SSH协议的组成 SSH工作原理 SSH版本协商阶段&#xff08;确定V1版本或V2版本&#xff09; 算法协商阶段 密钥交换阶段 用户认证阶段 会话交互阶段 基本概念 SSH&#xff08;Secure Shell&#xff09;安全外壳协议&#xff0c;是一种用于在不安全网络上进…