并发编程(二)有序性

news2025/1/6 20:32:19

【问题的产生】:

程序真的是按照顺序执行的吗?

/**
 * 本程序跟可见性无关,曾经有同学用单核也发现了这一点
 */

import java.util.concurrent.CountDownLatch;

public class T01_Disorder {
    private static int x = 0, y = 0;
    private static int a = 0, b = 0;

    public static void main(String[] args) throws InterruptedException {

        for (long i = 0; i < Long.MAX_VALUE; i++) {
            x = 0;
            y = 0;
            a = 0;
            b = 0;
            CountDownLatch latch = new CountDownLatch(2);

            Thread one = new Thread(new Runnable() {
                public void run() {
                    a = 1;
                    x = b;

                    latch.countDown();
                }

            });

            Thread other = new Thread(new Runnable() {
                public void run() {
                    b = 1;
                    y = a;

                    latch.countDown();
                }
            });
            one.start();
            other.start();
            latch.await();
            String result = "第" + i + "次 (" + x + "," + y + ")";
            if (x == 0 && y == 0) {
                System.err.println(result);
                break;
            }
        }
    }
}

【最终输出】:
在这里插入图片描述
【图解程序 】:
在这里插入图片描述
//上图是8种可能出现的执行情况。你会发现——出现(0,0)的情况就意味着一定是线程内部执行的时候调换了顺序。

【 乱序的原因 】:

//简单说 , 就是为了提高效率。
【下图】:
第一条CPU指令是去内存读数据,等待数据的返回;
第二条CPU指令是本地寄存器自增。
在这里插入图片描述
【分析】:
//CPU的速度要比内存的速度快100倍。如果必须要按顺序执行的话,在等待返回过程需要大量的等待。

【什么情况下,两条指令可以交换顺序呢? 】:

如果前后指令之间有依赖关系 , 后续指令必须依赖前面的指令,那么是无法交换顺序的。——如果前后两条指令不存在依赖关系,则是可以进行交换的。

【 乱序存在的条件 】:

as——if——serial

不影响单线程的最终一致性。

不影响单线程的最终一致性时,各指令可以交换顺序。
【如】:
x=1; y=1。

x=a; y=a。

//上述两组指令都不影响最终结果。(打乱执行顺序的话)

【但如下就不行】:
x=1; x++。

【程序理解可见性和有序性】:

《Java并发编程实践》 中的一个例子。

 package T04_YouXuXing;

public class T02_NoVisibility {
    private static boolean ready = false;
    private static int number;

    private static class ReaderThread extends Thread {
        @Override
        public void run() {
            while (!ready) {
                Thread.yield();
            }
            System.out.println(number);    //这里的打印有可能会是0.
        }
    }

    public static void main(String[] args) throws Exception {
        Thread t = new ReaderThread();
        t.start();
        number = 42;    //没有前后依赖关系。
        ready = true;   //没有前后依赖关系。
        t.join();
    }
}
  • 思考—上述程序存在什么问题吗?
    (1)可见性问题
    ready设置为true后不会马上停止,但也有可能马上停止。
    要在number上加volatile关键字。——保证可见性。
    (2)有序性问题
    number = 42; //没有前后依赖关系。
    ready = true; //没有前后依赖关系。
    //这两个指令有可能第二个先执行——此时就会输出0 , 因为还没有执行到设置值为42的那一步。

【线程的半初始化状态】:

【对象的创建过程】:

在这里插入图片描述
在这里插入图片描述
0——申请内存;
4——特殊调用,特殊调用了T的init方法即默认的构造方法。
7——建立关联,和t变量建立关联。

【安全性】:
在这里插入图片描述
当我们new出一个对象 , 里面成员变量m的值是多少呢?——其实是和上一个使用这一块区域的程序有关系( C语言 )。
在这里插入图片描述
//Java中int类型的默认值是 0。
在这里插入图片描述
//这一句执行完,m=0 , 这是对象的半初始化状态。
在这里插入图片描述
//执行完这一句指令后——m的值才会变为8。
在这里插入图片描述
t变量和内存区域建立关联。

【 this对象逸出 】:

package T04_YouXuXing;

public class T03_ThisEscape {

    private int num = 8;


    public T03_ThisEscape() {
        new Thread(() -> System.out.println(this.num)      //这里有可能输出中间状态0.
        ).start();
    }

    public static void main(String[] args) throws Exception {
        new T03_ThisEscape();
        System.in.read();      //进入阻塞
    }
}

//——这里是有可能出现问题的。
【 num为何可能输出中间状态呢? 】:
this存在于局部变量表里面。

//这两条指令是有可能换顺序的。这样就会先建立关联,关联完之后才调用构造方法 , 结果在调用构造方法的过程当中,新建了一个线程去输出当前的num值——此时因为是先关联的,所以为0 , 就先输出0了。
在这里插入图片描述
即——先进行了关联 , 关联完之后再调用的构造方法。

【防止this逸出现象】:

可以在构造方法里NEW线程,但是!!!!!!!————不要让它在那里启动。
什么时候启动?——单独写一个方法。
【 修改程序 】:

package T04_YouXuXing;

public class T03_ThisEscape2 {

    private int num = 8;

    Thread t;

    public T03_ThisEscape2() {
        t = new Thread(() -> System.out.println(this.num) );
    }

    public void start(){
        t.start();
    }

    public static void main(String[] args) throws Exception {
        new T03_ThisEscape2();
        System.in.read();      //进入阻塞
    }
}

【 美团面试题 】:

关于Object o = new Object( )

【1–请解释一下对象的创建过程(半初始化)】:

创建对象是有一个半初始化过程的。Java里半初始化过程是赋默认值的,C++等语言是赋内存上遗留下来的值。

【2–加问DCL与volatile问题(指令重排)】:

DCL单例是否要加volatile , 这里面主要涉及指令重排序的问题。
volatile有两大作用——线程可见&禁止重排。as_if_serial这种机制是为了提高利用率。
在这里插入图片描述
单线程中as_if_serial结果幂等 , 执行指令可以随意排序。——但是,这种程序一旦到了多线程中就会出现问题!!!!!!
如果不想让其排序的话 , 怎么办?——使用volatile可以禁止重排~~~! ! !

【DCL】:

饿汉式单例代码:

package T04_YouXuXing.T05_singleton;

/*
饿汉式
类加载到内存之后,就实例化一个单例,JVM保证线程安全;
唯一缺点————不管用到与否,类装载时就完成实例化;
* */
public class Mgr01 {
    private static final Mgr01 INSTANCE = new Mgr01();
    private Mgr01(){};
    public static Mgr01 getInstance(){ return INSTANCE; }

    public void m(){
        System.out.println("m");
    }

    public static void main(String[] args){
        Mgr01 m1 = Mgr01.getInstance();
        Mgr01 m2 = Mgr01.getInstance();
        System.out.println(m1==m2);
    }
}

【最终输出】:
True
懒汉式代码:

package T04_YouXuXing.T05_singleton;

/*
* 懒汉式写法
* 【缺点】:多线程访问的时候,你不能够保证一致性,你不能够保证NEW出来的都是同一个对象;
* */
public class Mgr03 {
    private static Mgr03 INSTANCE;

    private Mgr03(){ }

    public static Mgr03 getInstance(){
        if (INSTANCE == null){           //两个线程接连判断都为NULL(还没来得及NEW出来),这时就会新建两个。
            try{
                Thread.sleep(1);   //让更多的线程进入IF循环来新建对象就能更清晰的展示问题。
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE = new Mgr03();
        }
        return INSTANCE;
    }

    public void m(){
        System.out.println(" m ");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(
                    ()-> System.out.println(  Mgr03.getInstance().hashCode()  )
            ).start();
        }
    }
}

【最终输出】:
在这里插入图片描述
【修正程序】:
//只在上述程序的基础上加synchronized

    public static synchronized Mgr03_02 getInstance(){    
        if (INSTANCE == null){           //两个线程接连判断都为NULL(还没来得及NEW出来),这时就会新建两个。
            try{
                Thread.sleep(1);   //让更多的线程进入IF循环来新建对象就能更清晰的展示问题。
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            INSTANCE = new Mgr03_02();
        }
        return INSTANCE;
    }

在这里插入图片描述
//但是仍然有问题——锁的粒度太粗了~ ~ ~ ! ! !
【继续修改】:

    public static  Mgr03_03 getInstance(){

        //业务代码
        
        if (INSTANCE == null){           //两个线程接连判断都为NULL(还没来得及NEW出来),这时就会新建两个。
            //妄图通过缩减同步代码块的方式提高效率,然后不可行。
            synchronized(Mgr03_03.class) {
                try {
                    Thread.sleep(1);   //让更多的线程进入IF循环来新建对象就能更清晰的展示问题。
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                INSTANCE = new Mgr03_03();
            }
        }
        return INSTANCE;
    }

在这里插入图片描述
//还是无法保证一致性~~~!!!!!!前一个线程刚释放锁,后一个线程就拿到锁了,然后就立即NEW了一个对象。
【DCL机制】:

package T04_YouXuXing.T05_singleton;

// DCL  Double Check Lock
public class Mgr06 {
    private static volatile Mgr06 INSTANCE;  //JIT

    private Mgr06(){}

    public static Mgr06 getInstance(){
        //业务代码省略

        if (INSTANCE == null){    //Double  Check  Lock

            synchronized(Mgr06.class) {

                if (INSTANCE==null) {
                    try {
                        Thread.sleep(1);   //让更多的线程进入IF循环来新建对象就能更清晰的展示问题。
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    INSTANCE = new Mgr06();
                }
            }
        }
        return INSTANCE;
    }
    public void m(){
        System.out.println(" m ");
    }

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(
                    ()-> System.out.println(  Mgr06.getInstance().hashCode()  )
            ).start();
        }
    }
}

【提高效率的细节】:

         if (INSTANCE == null){    //Double  Check  Lock——————这一行可以提高效率
            synchronized(Mgr06.class) {
                if (INSTANCE==null) {

第一个判断INSTANCE是否==null 是非常有必要的,如果去掉的话,那么所有的线程二话不说一上来全都在抢锁,竞争锁消耗的资源是非常高的。

【DCL和volatile的问题】:

【结论】:
DCL必须要加上volatile

private static volatile Mgr06 INSTANCE;  //JIT

【Q】:不加的话会怎么样呢???
在这里插入图片描述
在这里插入图片描述
//第一个线程进来,刚刚完成半初始化,这个时候发生了指令重排序。
在这里插入图片描述
//刚执行完连接的时候,外面就有一个线程进入了IF判断!!!(这个时候虽然没有完成初始化,但是已经赋上了默认值,外面的线程就会拿到半初始化状态的对象!!!)

【总结】:
在这里插入图片描述
thread1刚刚执行完连接那一步,这个时候thread2进入了IF判断,拿到刚半初始化完(没有完全初始化,没有调用构造方法)的对象。

【3–对象在内存中的存储布局(对象与数组的存储不同)】:

普通对象

在这里插入图片描述
mrakword——标记字。
class pointer——当你new出一个对象来 , 你这个对象是属于哪一个class的呢? ? ?
padding——64位虚拟机的话,就是8字节对齐( 将前面三块markword、class pointer、instance data补到能被8整除的字节 ),就和用集装箱装东西一样,用8字节比较规整。
【UseCompressedClassPointers】:
使用压缩类指针;
【UseCompressedOops】:
OrdinaryObjectPointer
使用压缩的普通对象指针。

【类指针】:

在这里插入图片描述
//这个类指针默认是启动压缩的 , 压缩完是4byte 。

【普通对象指针】:

String s , s就是一个指针,默认这两个的压缩都是开启的。

【数组类型】:

在这里插入图片描述
//唯一区别是多了4字节的数组长度;

【4–对象头具体包括什么(markword classpointer)synchronized锁信息】:

对象头主要包括markword 和 class_pointer两部分,class pointer是默认开压缩的4个字节 , 如果不开压缩就是8个字节。

【 研究一个对象内存布局的工具 】:
org.openjdk.jol

全称——JavaObjectLayout
在这里插入图片描述
在这里插入图片描述
//parseInstance——解析这个对象 , 然后变成可以打印的。
在这里插入图片描述
Instance size:16 bytes ———一共有16个字节构成。
在这里插入图片描述
//指向Object.class
在这里插入图片描述
空间的丢失4个字节。空间丢失就是为了补齐的意思。

【多加一个变量然后进行测试】:
在这里插入图片描述
markword ——8 ;
classpointer——4;
instance data——4;
8+4+4=16 , 所以已经是8的倍数了 , 没有必要再去补齐。
在这里插入图片描述
在这里插入图片描述
//最后的4个字节是m变量。

【再进行一例测试】:
在这里插入图片描述
在这里插入图片描述
//最后补了4个字节。

【markword中主要包括什么呢 ? 】:

最主要的是包含锁信息 , 其他的都是次要的。
锁信息、GC信息、IdentityHashCode 。

【5–对象怎么定位?(直接 间接)】

句柄方式
直接指针
在这里插入图片描述
在这里插入图片描述
//定位就是指——如何通过 t 来找到 T 。

【6–对象怎么分配?(栈上—线程本地—Eden—Old )】:

首先会尝试在栈上分配 , 如果能在栈上分配就分配在栈上;分配在栈上的好处就是——一旦弹出,它的生命周期就结束了。
在这里插入图片描述
【 什么样的对象能够在栈上分配呢 ? 】:
逃逸分析;
标量替换;
在这里插入图片描述

如果个儿超级大——扔到老年代。
如果不大不小——往TLAB中分配。TLAB全称是——Thread Local Allocation Buffer( 线程本地分配缓冲区 ) , E代表伊甸区 ,AGE是年龄 。
【解释线程本地分配】:
当我们往一个内存区域里NEW对象的时候 , 总是要分配空间的。多个线程往同一个内存空间里分配对象的时候,必须要经过同步。有线程的协调,就会有效率上的损失。
【线程本地分配缓冲区】:
当一个线程启动的时候 , 为这个线程在伊甸区里分配一个小小的空间,这个空间是线程所独享的 , 如果线程NEW了任何对象,就往对应的空间里扔,往自己的兜里扔东西的话,就不需要进行争抢了。

【7–Object o = new Object( ) 在内存中占用多少字节? 】:

1)有没有压缩class pointer 。
2) 有没有压缩oops 。
3) 看内存是不是32G以下或者以上 。

【8–为什么hotspot不使用C++对象来代表Java对象?】:

因为C++中有一个 vtbl 的指针 , 它指向的是虚方法表 , 虚方法表是用来实现多态的。Java中的是oop-class二元机制。

【9–Class对象是在堆还是在方法区?】:

在这里插入图片描述
O.class是给反射用的;
OOM溢出实际上是方法区里溢出了:
在这里插入图片描述

【阶段小结】:

在这里插入图片描述

【happens—before原则】:

【CPU级别】:
只要你不影响单线程的一致性,指令随便换。

【JVM级别】:
对Java的哪些指令不可以互换做了一些规定。

【CPU用屏障指令阻止乱序】:

【CPU汇编指令一级】:

内存屏障

所谓的内存屏障就是一条特殊的指令 ,当看到这种指令的时候,前面的指令和后面的指令不可以换顺序。
内存屏障是特殊指令:看到这种指令,前面的必须执行完,后面的才能执行

intel : lfence(读) sfence(写) mfence(读和写)(CPU特有指令)

//我们的JVM并不是靠这种底层指令来实现的 , 它并没有去针对不同的CPU从而使用不同CPU的内存屏障指令,不是。

【JVM要求实现的四种屏障】:

【JVM层级的内存屏障】:

在这里插入图片描述
所有要求实现JVM的Java虚拟机 , 都应该实现自己的JVM级别的内存屏障 , 你的JVM实现应该有这四条的屏障,不论你底层采用什么汇编语言来实现。JVM层级必须得有能实现这四个屏障的效果。
Load叫 “读” , Store叫 “写”。
【LoadLoad】:
在这里插入图片描述
//两条读之令中间夹了一个指令——LoadLoad , 那么上面的Load指令就不能和下面的Load指令交换顺序。其他的三个屏障可以类比理解。

【用volatile禁止指令重排】:

在这里插入图片描述
//StoreStoreBarrier——在我写之前,以前所有的写指令必须先完成,别人写完我才能写。
后面还一个指令:
//StoreLoadBarrier——等我写完别人才能读。
在这里插入图片描述
LoadLoadBarrier——等我读完,别人才能读;
LoadStoreBarrier——等我读完,别人才能写。

【volatile在hotspot中的实现】:

volatile的底层实现

volatile修饰的内存,不可以重排序,对volatile修饰变量的读写访问,都不可以换顺序

1: volatile i

2: ACC_VOLATILE

3: JVM的内存屏障

​ 屏障两边的指令不可以重排!保障有序!

​ happends-before

​ as - if - serial

4:hotspot实现

bytecodeinterpreter.cpp

int field_offset = cache->f2_as_index();
          if (cache->is_volatile()) {
            if (support_IRIW_for_not_multiple_copy_atomic_cpu) {
              OrderAccess::fence();
            }

orderaccess_linux_x86.inline.hpp

inline void OrderAccess::fence() {
  if (os::is_MP()) {   //如果操作系统是多核的。
    // always use locked addl since mfence is sometimes expensive
#ifdef AMD64
    __asm__ volatile ("lock; addl $0,0(%%rsp)" : : : "cc", "memory");    //这条指令的核心是lock而非后面的add。
#else
    __asm__ volatile ("lock; addl $0,0(%%esp)" : : : "cc", "memory");
#endif
  }
}

LOCK 用于在多处理器中执行指令时对共享内存的独占使用。
它的作用是能够将当前处理器对应缓存的内容刷新到内存,并使其他处理器对应的缓存失效。

另外还提供了有序的指令无法越过这个内存屏障的作用。
lock指令是比较特殊的,后面必须要跟其他的指令——表示当我执行后面的指令的时候,对总线/缓存 进行锁定,后面的这个指令不能是空指令,不能是NOP , addl $0,0(%%rsp)即给寄存器加了一个0,相当于是空操作。
在这里插入图片描述
//两颗CPU访问同一块内存的话 , 我会将总线进行锁定或者我会把对应的缓存行进行锁定(一个叫总线锁 ,一个叫缓存锁)。

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

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

相关文章

java小技能:集成开发工具(IDE)

文章目录 I IDEA1.1 下载1.2 试用II 忽略IntelliJ IDEA 文件2.1 .gitignore的例子2.2 从idea进行忽略III idea使用非模式提交界面IV DataGrip4.1安装4.2 Actsee alsoI IDEA 1.1 下载 https://www.jetbrains.com/zh-cn/idea/download/other.html 1.2 试用 IntelliJ IDEA 2021…

快手如何玩转复杂场景下的说话人识别?| ASRU 2021

快手是一个短视频社区&#xff0c;短视频和直播中通常混合各种形式的声音&#xff0c;如语音、音乐、特效音和背景噪声等&#xff0c;这些声音很好的提升了短视频和直播的用户消费体验&#xff0c;但同时也为音频内容理解带来极大的困难和挑战。如何在复杂场景下准确高效的进行…

AMS的启动

AMS的启动 Launcher请求AMS阶段 AMS到ApplicationThread阶段 ApplicationThread到Activity阶段 API28重构之后&#xff0c;ApplicationThread到Activity阶段 应用程序启动涉及的进程间通信 根Activity启动过程涉及到的进程之间的关系 根Activity启动过程中的进程调用时序图 A…

HTML期末大作业:基于HTML+CSS+JavaScript新能源汽车资讯门户网站

&#x1f389;精彩专栏推荐 &#x1f4ad;文末获取联系 ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业&#xff1a; 【&#x1f4da;毕设项目精品实战案例 (10…

兼容模式怎么设置?5个常用浏览器的设置方法

在使用电脑浏览器时&#xff0c;有时需要切换到兼容模式才能打开相应界面。许多浏览器现在都有自己的防病毒功能&#xff0c;这可能会直接将许多组件作为病毒屏蔽&#xff0c;导致某些元素无法在正常模式下显示。但是常用浏览器的兼容模式怎么设置呢&#xff1f;接下来让我们一…

【附源码】计算机毕业设计JAVA研究生入学考试备考辅助系统

【附源码】计算机毕业设计JAVA研究生入学考试备考辅助系统 目运行 环境项配置&#xff1a; Jdk1.8 Tomcat8.5 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a;…

网络编程——socket定义和地址格式

网络编程——socket定义和地址格式 目录 socket 是什么&#xff1f;套接字地址格式 1. socket 是什么&#xff1f; 网络编程中&#xff0c; socket 翻译为套接字或套接口&#xff0c;指可以通过插口接入的方式&#xff0c;快速完成网络连接和数据收发。上图表示网络编程中&…

智能终端测试解决方案

概述 随着互联网产业的蓬勃发展&#xff0c;智能终端的崛起&#xff0c;无论是移动终端制造商、移动通信运营商以及移动互联网内容服务商&#xff0c;都面临着新技术、新业务、新服务不断涌现&#xff0c;商用进程不断加快的局面。 当前基于5G核心技术的移动传输网络已开发出…

只因简历上有“精通”Redis,阿里三面被面试官狂问 Redis,再也不敢乱写了

Redis 在国内各大公司都很热门&#xff0c;比如新浪、阿里、腾讯、百度、美团、小米等。Redis 也是大厂面试最爱问的&#xff0c;尤其是 Redis 客户端、Redis 高级功能、Redis 持久化和开发运维常用问题探讨、Redis 复制的原理和优化策略、Redis 分布式解决方案等。 Redis 我们…

Python 字符串详解

一、字符串概念 用于保存字符信息的数据模型(容器)。 1、只能存放一个值 2、不可变类型 3、有序&#xff0c;索引从0开始顺序访问 字符串语法格式&#xff1a; str1 “字符串信息” str2 字符串信息 str3 字符串信息 str4 字符串信息 二、字符串常用操作 1、字符串…

【论文阅读】社交网络传播最大化问题-02

Leader-Based Community Detection Algorithmin Attributed Networks以往leader-aware算法创新点问题定义定义基础概念定义创新概念模型构造第一步&#xff1a;确定每个节点的leader第二步&#xff1a;合并小分支以得到最终结果实验数据集人工合成网络现实世界的网络基线方法和…

leetcode:2478. 完美分割的方案数【预处理 + dp定义 + 前缀和优化】

目录题目截图题目分析ac code总结题目截图 题目分析 开头必定是prime&#xff0c;结尾必定是not primek 1特判找到所有可能的结尾点&#xff08;最后一个不考虑&#xff09;结尾点i必须满足s[i]->not prime, s[i 1]->prime设结尾点集合为x0 < x1 < x2 < … &…

Docker的容器管理

1、创建容器 容器创建&#xff1a;就是将镜像加载到容器的过程。 创建容器时如果没有指定容器名称&#xff0c;系统会自动创建一个名称。 新创建的容器默认处于停止状态&#xff0c;不运行任何程序&#xff0c;需要在其中发起一个进程来启动容器。 docker create创建的容器…

MCE | ROS 与疾病的关系

前段时间&#xff0c;一篇刊登在国际杂志 Cell Metabolism 上的研究报告表明&#xff0c;从表型正常的细胞转化为癌变细胞或许涉及抗氧化剂防御 (Antioxidant defense) 和核苷酸合成 (Nucleotide synthesis) 两个部分&#xff0c;而突破这些限制可能是致癌转化的关键。文中通过…

oracle常见报错问题处理

文章目录协议适配器错误怎么办&#xff1f;忘记密码了怎么办&#xff1f;报错:ora-01033oracle initialization or shutdown in progress&#xff1b;edit无法使用怎么办&#xff1f;报错:ORA-28547:connection to server failed,probable Oracle Net admin errorORA-12505:监听…

大一学生HTML期末作业_ 季奥林匹克运动会 8页 无js 带表单 带报告5200字

⛵ 源码获取 文末联系 ✈ Web前端开发技术 描述 网页设计题材&#xff0c;DIVCSS 布局制作,HTMLCSS网页设计期末课程大作业 | 校园篮球网页设计 | 足球体育运动 | 体育游泳运动 | 兵乓球 | 网球 | 等网站的设计与制作HTML期末大学生网页设计作业 HTML&#xff1a;结构 CSS&…

让你全方位了解Shell终端,轻松学习

关于shell终端&#xff0c;本文将在以下几个方面做以总结和说明&#xff0c;希望对大家有所帮助。 1.什么是shell Shell是用户和Linux操作系统之间的接口。Linux中有多重shell&#xff0c;其中缺省使用的是Bash。如果把Linux内核想象成一个球体的中心&#xff0c;shell就是围绕…

[go学习笔记.第十六章.TCP编程] 3.项目-海量用户即时通讯系统-redis介入,用户登录,注册

1.实现功能-完成用户登录 在redis手动添加测试用户,并画出示意图以及说明注意事项(后续通过程序注册用户) 如:输入用户名和密码,如果在redis中存在并正确,则登录,否则退出系统,并给出相应提示: 提示信息: 1.用户不存在或者密码错误 2.重新注册并登录 redis手动添加测试用户 ser…

GitLab的使用

目录 一、腾讯云 / 阿里云 购买服务器 二、服务器上安装GitLab-ce 三、使用git 1、首先把服务器上的代码克隆下来 2、将所有有改动的全部添加到要提交的本地库中 3、将修改提交到本地库 4、将本地库的commit推送到远程服务器 5、拉取服务器上最新资源&#xff1a; 6、…

智创未来 · 引领5G价值,广和通携5G AIoT创新应用亮相2022德国慕尼黑电子展

11月15-18日&#xff0c;两年一届的全球电子行业盛会——德国慕尼黑电子展&#xff08;Electronica 2022&#xff09;于慕尼黑展览中心顺利举行。作为全球领先的物联网无线通信解决方案和无线模组供应商&#xff0c;广和通精彩亮相B5馆139展台&#xff0c;向全球物联网企业分享…