JUC并发编程(五)

1、java内存模型

        Java内存模型(Java Memory Model,JMM)是Java编程语言中用于处理并发编程的一组规则和规范。它定义了Java程序中多线程之间如何交互以及内存如何被共享和访问的规则。它定义了主内存,工作内存的抽象概念,底层对应着 CPU 寄存器、缓存、硬件内存、 CPU 指令优化等。

  • 主内存(Main Memory):主内存是所有线程共享的内存区域,包含了所有的变量、实例对象和类信息。
  • 工作内存(Working Memory)每个线程都有自己的工作内存,用于存储主内存中的部分数据副本。线程对变量的读写操作都在工作内存中进行。

        JMM体现在以下几个方面:

  • 原子性(Atomicity):指的是对于基本数据类型(例如int、long等)的读写操作是原子的,即不可分割的。这意味着其他线程要么看到完整的操作结果,要么看不到。
  • 可见性(Visibility):可见性指的是一个线程对变量的修改能否被其他线程立即看到。在Java内存模型中,如果一个线程修改了共享变量的值,其他线程可能不会立即看到这个变化,除非采取特定的同步机制。
  • 有序性(Ordering):指的是程序执行的顺序必须遵循一定的规则。在没有同步机制的情况下,指令的执行顺序可能会被重排序,但是Java内存模型保证适当使用同步机制时指令的顺序不会被打乱。

Q1:主内存和工作内存有什么区别和联系?

主内存和工作内存都是Java内存模型中的概念,用于描述线程之间的内存交互和数据共享。

主内存中的数据是线程之间共享的,所有线程都可以读取和修改主内存中的数据。当一个线程对主内存中的数据进行修改时,其他线程可能不会立即看到这个变化,除非采取特定的同步机制来确保可见性。

此外,主内存是所有线程共享的内存区域,而工作内存是每个线程私有的内存区域。主内存存储了所有的变量和实例对象,而工作内存中只包含了线程需要使用的部分数据副本。线程对变量的读写操作都在工作内存中进行,不同线程之间的操作不会直接影响到主内存,需要通过特定的机制来同步和确保可见性。


 2、可见性

2.1、案例一

首先我们来看一个案例:

@Slf4j(topic = "c.Demo1")
public class Demo1 {static boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (flag){}},"t1");t1.start();Thread.sleep(1000);log.info("t1线程停止");flag = false;}
}

此案例期望的结果是,主线程在睡眠1s后将标记设置为false,然后t1线程的条件不满足就停止运行。

最终结果:虽然主线程在睡眠1s后将标记修改成了false,但是t1线程没有停止运行。

2.2、问题分析

为什么会造成上面的结果?答案是与JMM中的可见性有关。

初始状态下,主内存中会记录成员变量run的值为true,此时t1线程读取的是主内存中的值。

因为 t 线程要频繁从主内存中读取 run 的值,JIT 编译器会将 run 的值缓存至自己工作内存中的高速缓存中, 减少对主存中 run 的访问,提高效率。如下图,类似于redis的缓存。

主线程睡眠了1s之后修改run的值为false,此时修改的是主存中的run。但是t线程还是在从工作内存中读取run,造成读取的结果永远是旧的值。类似于redis缓存不一致问题。

2.3、解决方案
@Slf4j(topic = "c.Demo1")
public class Demo1 {//从主内存获取最新值volatile static boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (flag){}},"t1");t1.start();Thread.sleep(1000);log.info("t1线程停止");flag = false;}
}

在成员变量上使用volatile关键字。

它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取 它的值,线程操作 volatile 变量都是直接操作主存。

2.4、可见性&原子性

        可以确保变量的可见性,但不能保证原子性。原子性是指对变量的读取和写入操作是不可分割的,要么全部完成,要么全部不完成。volatile关键字只能保证对被volatile修饰变量的读取和写入操作在内存中是可见的,但不能保证复合操作的原子性。

        例如我有一个成员变量volatile int i = 0;虽然加了volatile关键字保证多线程间对于i修改是即时可见的,但是如果我要进行i++操作,因为i++操作在字节码层面不是原子性的,只是加上volatile关键字无法解决线程时间片到期,上下文切换的问题。

Q1:synchronized关键字能够保证可见性吗?

当一个线程获取到对象的锁(或类的锁)并执行进入临界区代码时,它会清空工作内存中的数据,从主内存中重新读取变量的值,确保获取到最新的值。当线程释放锁时,会将对变量的修改刷新回主内存,从而保证了可见性。近似于redis缓存策略的先删除缓存再更新数据库。

具体来说,synchronized保证了以下几点:

  • 获取锁时刷新变量值:线程获取锁时会清空工作内存中的数据,并重新从主内存中获取变量的最新值,确保线程获取到的是最新的值。
  • 释放锁时刷新变量值:线程在释放锁之前会将对变量的修改刷新回主内存,使得其他线程在获取锁后能够看到最新的值。
  • 同一时刻只有一个线程能够执行:对变量的修改操作不会被多个线程同时执行,避免了竞态条件和可见性问题。

但是有一个前提条件:只有被synchronized完全控制的变量才可以保证可见性。

 2.5、使用volatile改造两阶段终止模式
@Slf4j(topic = "c.TwoStagesBreak")
public class TwoStagesBreak {public static void main(String[] args) throws InterruptedException {//使用volatiles改造两阶段中止模式log.info(Thread.currentThread().getName() + "->run");Monitor monitor = new Monitor();monitor.startMonitor();//当前线程睡眠3s后执行打断操作Thread.sleep(3000);log.info("stop monitor");monitor.stopMonitor();}
}@Slf4j(topic = "c.Monitor")
class Monitor {private Thread monitor;private volatile Boolean flag = false;private Boolean isStart = false;/*** 开始监控线程*/public void startMonitor() {synchronized (this) {if (isStart) {return;}isStart = true;}monitor = new Thread(() -> {log.info(Thread.currentThread().getName() + "->run");while (true) {if (flag) {log.info("Monitor interrupted");break;}try {Thread.sleep(2000);//睡眠时非正常打断log.info("执行监控");} catch (InterruptedException e) {}}});monitor.start();}/*** 打断线程监控*/public void stopMonitor() {flag = true;monitor.interrupt();}
}

        打断线程监控方法中,此时将被volatile关键字修饰的flag改为true,因为flag被volatile修饰,所以修改对监视线程同步可见。

        再次调用监视线程的interrupt方法是为了如果在监视线程睡眠时打断,使其进入catch块,不再多执行一次执行监控相关代码。

        如果多个线程同时访问startMonitor方法,可能会开启多次监控。此时我们采用了Balking模式解决这个问题:在一个线程发现另一个线程或本线程已经做了某一件相同的事,那么本线程就无需再做 了,直接结束返回。

        在上面的案例中,引入了isStart变量。并且对监控线程使用了同步代码块。当线程一进入方法,争抢到了锁,会判断isStart,因为是第一次调用,所以isStart == null,然后线程一会将isStart设置成true,执行剩下的代码并且释放锁,当线程二争抢到锁后,发现此时isStart != null,就直接返回避免重复执行。


3、有序性

        有序性指的是程序执行的顺序必须遵循一定的规则。在多线程环境中,由于指令执行的并发性,可能会导致指令的执行顺序发生变化,从而产生意料之外的结果。为了解决这个问题,Java内存模型定义了一系列规则,确保适当使用同步机制时指令的顺序不会被打乱,保证程序的有序性。

3.1、指令重排   

        而有序性又引出了指令重排的概念:

        指令重排是指处理器或编译器为了优化程序执行速度而对指令执行顺序进行重新排序的过程。在现代计算机系统中,为了提高性能,处理器和编译器可能会对指令进行重排,但这种重排不能影响程序的最终结果,必须保证程序的语义不变。

因为i和j都是赋值操作,并且互相不依赖,先执行给i赋值和先执行给j赋值没有必然的先后顺序的联系。所以可以是:

也可以是:

但是在多线程下的指令重排,也可能会造成以下的问题:

  • 数据竞争(Data Race):如果指令重排导致多个线程对共享数据的操作顺序发生变化,可能会导致数据竞争问题,从而产生不确定的结果。
  • 可见性问题:指令重排可能导致某些变量的修改对其他线程不可见,破坏了程序的可见性。
3.2、案例二

常规情况下可能会有的结果

1、先执行线程二,线程二全部执行完成后执行线程一,此时的值为4。

2、先执行线程一,线程一全部执行完在执行线程二,此时的值为1。

3、线程二执行完成赋值操作,此时切换到线程一,值为1。

但是因为上文所说指令重排序的问题,最终的执行结果还有可能为0。

我们可以使用volatile关键字解决这样的问题。

4、volatile原理

volatile 的底层实现原理是内存屏障,Memory Barrier(Memory Fence)。

  • 对 volatile 变量的写指令后会加入写屏障。

  • 对 volatile 变量的读指令前会加入读屏障。

写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中:

而读屏障(lfence)保证在该屏障之后,对共享变量的读取,加载的是主存中最新数据。

写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后,读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前,从而保证了有序性。

例如在上面的代码中,ready变量使用了volatile关键字,上一行的num=2就不会排在ready=true的后面。

但是,并不能保证读屏障不会排在写屏障的前面,只能保证写屏障前的所有代码不会排在写屏障的后面。有序性也只能保证本线程中代码的有序,不能控制其他线程。


5、双检锁单例模式问题

5.1、存在的问题&分析
@Slf4j(topic = "c.Demo3")
public final class Singleton {private Singleton() {}private static Singleton INSTANCE = null;public static Singleton getInstance() {if (INSTANCE == null) { // t2// 首次访问会同步,而之后的使用没有 synchronizedsynchronized (Singleton.class) {if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}
}

上面是一个单例模式的实现。其具有以下特点:

  • 懒惰实例化,并非随着类的加载而加载。
  • 首次使用 getInstance() 才使用 synchronized 加锁,后续使用时无需加锁。
  • 第一个 if 使用了 INSTANCE 变量,是在同步块之外,这就会导致多线程并发运行时会存在问题。

下面从字节码的层面分析一下问题:

字节码的含义:

getstatic #2:获取静态字段

ifnonnull 37:如果静态字段不为 null,则跳转到第 37 行,意味着单例对象已经被创建,无需再次创建。

ldc #3 :将常量池中索引为 3 的类引用推送至栈顶

dup:复制栈顶数值并将复制值压入栈顶,此时栈顶有两个相同的类引用。

astore_0:将栈顶引用类型数值存储到局部变量表中第 0 个位置。

monitorenter:进入对象的监视器(锁定),用于实现线程同步,下面是对单例对象的创建部分,需要加锁。

getstatic #2:再次获取静态字段,检查是否已经被其他线程创建。

ifnonnull 27:如果静态字段不为 null,则跳转到第 27 行,意味着单例对象已经被其他线程创建,无需再次创建。

new #3 :创建一个新实例,并将其引用推送至栈顶。

dup:复制栈顶数值并将复制值压入栈顶,此时栈顶有两个相同的单例对象引用。

invokespecial #4 :调用对象构造函数 <init> 进行初始化。

putstatic #2 :将栈顶的单例对象引用存储到静态字段中,完成单例对象的创建。

aload_0:将局部变量表中第 0 个位置的引用类型数值加载到栈顶。

monitorexit:退出对象的监视器(释放锁),解锁完成对象创建。

goto 37:跳转到第 37 行,即整个单例创建流程结束。

astore_1:将栈顶引用类型数值存储到局部变量表中第 1 个位置,用于异常处理。

aload_0:将局部变量表中第 0 个位置的引用类型数值加载到栈顶,即单例对象引用。

monitorexit:在异常处理时,再次退出对象的监视器(释放锁),确保锁的正常释放。
aload_1:将局部变量表中第 1 个位置的引用类型数值加载到栈顶,即异常对象引用。

athrow:将栈顶的异常对象引用抛出。

getstatic #2:最后再次获取静态字段,确保单例对象已经被创建。

areturn:将栈顶的单例对象引用返回给调用者。

        我们重点看标红的四行。在执行过程中,JIT即时编译器有可能会对指令进行重排序,先将栈顶的单例对象引用存储到静态字段中,完成单例对象的创建。调用对象构造函数 <init> 进行初始化。

        上述的流程,在单线程环境下是没有问题的,但是在多线程环境下,可能存在,t1线程获取到了锁,执行了INSTANCE = new Singleton();但是由于字节码中是先创建了对象,导致INSTANCE不为null,此时t2线程执行到了INSTANCE == null的if判断(此行不在锁范围内)不满足判断条件,直接返回了INSTANCE。此时返回的INSTANCE还没有调用构造方法,一些可能存在的初始化的代码还没有执行,也就是对象没有创建完全。

5.2、解决方案

        我们可以通过volatile关键字解决上述问题:

@Slf4j(topic = "c.Demo3")
public final class Singleton {private Singleton() {}private static volatile Singleton INSTANCE = null;public static Singleton getInstance() {if (INSTANCE == null) { // t2// 首次访问会同步,而之后的使用没有 synchronizedsynchronized (Singleton.class) {if (INSTANCE == null) { // t1INSTANCE = new Singleton();}}}return INSTANCE;}
}

        为什么加上volatile关键字就能解决问题?此前我们有提到过,voltile是通过读写屏障来保证可见性和有序性。

         如上图所示,因为成员变量INSTANCE被volatile修饰,所以INSTANCE = new Singleton();这一行给INSTANCE赋值操作具有写屏障,会保证字节码操作不会出现指令重排序,一定是先调用了构造方法,再给INSTANCE赋值。INSTANCE == null具有读屏障,会保证读取到的都是主存中最新的值。且不会将读屏障之后的代码排在读屏障之前。

        此时即使发生极端情况,线程t1在执行INSTANCE = new Singleton();时线程t2进行了INSTANCE == null的if判断,由于t1未执行完成赋值操作,t2从主存中读取到的依旧是null


6、happens-before

6.1、基本概念

        "Happens-before" 是 Java 内存模型(Java Memory Model,JMM)中的一个概念,用于描述多线程环境下操作之间的可见性和顺序性规则。它指定了对一个变量的写操作对于后续对该变量的读操作是可见的,这是为了保证多线程程序的正确性和可预测性而设计的一种规范。

        具体来说,如果一个操作 A happens-before 另一个操作 B,那么 A 在时间上先于 B,并且在执行时会对 B 产生一定的影响,确保 B 能够看到 A 对共享变量所做的修改。

6.2、案例

        线程解锁 m 之前对变量的写,对于接下来对 m 加锁的其它线程对该变量的读可见:

static int x;
static Object m = new Object();new Thread(()->{synchronized(m) {x = 10;}
},"t1").start();new Thread(()->{synchronized(m) {System.out.println(x);}
},"t2").start();

        线程对 volatile 变量的写,对接下来其它线程对该变量的读可见:

volatile static int x;new Thread(()->{x = 10;
},"t1").start();new Thread(()->{System.out.println(x);
},"t2").start();

        线程 start 前对变量的写,对该线程开始后对该变量的读可见:

static int x;
x = 10;new Thread(()->{System.out.println(x);
},"t2").start();

        线程结束前对变量的写,对其它线程得知它结束后的读可见(比如其它线程调用 t1.isAlive() 或 t1.join()等待 它结束):

static int x;Thread t1 = new Thread(()->{x = 10;
},"t1");t1.start();
t1.join();
System.out.println(x);

        线程 t1 打断 t2(interrupt)前对变量的写,对于其他线程得知 t2 被打断后对变量的读可见(通过 t2.interrupted 或 t2.isInterrupted):

static int x;
public static void main(String[] args) {Thread t2 = new Thread(()->{while(true) {if(Thread.currentThread().isInterrupted()) {System.out.println(x);break;}}},"t2");t2.start();new Thread(()->{sleep(1);x = 10;t2.interrupt();},"t1").start();while(!t2.isInterrupted()) {Thread.yield();}System.out.println(x);}

        对变量默认值(0,false,null)的写,对其它线程对该变量的读可见。

        以及具有传递性(如果 A happens-before B,且 B happens-before C,那么 A happens-before C。)

volatile static int x;
static int y;
new Thread(()->{ y = 10;x = 20;
},"t1").start();new Thread(()->{// x=20 对 t2 可见, 同时 y=10 也对 t2 可见System.out.println(x); 
},"t2").start();

7、线程安全单例案例

解答:

  1. 使用final标记为最终,可以避免子类继承父类破坏单例。
  2. 在类中加上Object readResolve()方法,在反序列的过程中,如果该方法返回了对象,就会使用该方法返回的对象,而不是反序列化过程中产生的对象。
  3. 设置为私有是为了避免外界调用构造方法创建对象,但是不能防止反射创建新的实例(setAccessible(true))
  4. 可以保证线程安全,静态成员变量的初始化是在类加载时完成,类加载由jvm保证线程安全。
  5. 封装成方法更加灵活,可以对初始化成员变量的过程中进行一些控制,例如实现懒加载。

解答:

  1. 枚举中的每个值都相当于其静态成员变量。而上面提到,静态成员变量的初始化是在类加载时完成,类加载由jvm保证线程安全。
  2. 同上。
  3. 不能。
  4. 可以被反序列化,因为枚举类都默认实现了序列化接口,但是在实现中考虑到了这样的问题,所以不会被破坏单例。
  5. 属于饿汉式,枚举中的每个值都相当于其静态成员变量。而上面提到,静态成员变量的初始化是在类加载时完成。
  6. 可以加上构造方法进行逻辑处理。

解答:线程是安全的,在静态方法上加锁,相当于锁住类的.class。同一时刻只能有一个线程进入该方法。缺点是锁的范围过大,会影响性能。

解答:

  1. 为了保证成员变量INSTANCE在初始化时的有序性和可见性,避免字节码操作指令重排序,返回未初始完成的INSTANCE对象。
  2. 缩小锁的范围有利于提高性能。
  3. INSTANCE!=null的if判断没有加锁,避免两个线程同时判断为null,其中一个线程获取到了锁,初始化并返回了对象,释放锁,第二个线程拿到锁后再次初始化对象。

解答:

  1. 属于懒汉式,对象随着类的加载而创建
  2. 没有,静态成员变量的初始化是在类加载时完成,类加载由jvm保证线程安全。

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

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

相关文章

一文让你简单了解跨境电商需要购买堡垒机的几大原因

随着互联网技术的快速发展&#xff0c;跨境电商蓬勃发展&#xff0c;但发展过程中网络安全问题日益凸显。因此不少跨境电商企业购买了堡垒机。这是为什么呢&#xff1f;一文让你简单了解跨境电商需要购买堡垒机的几大原因。 一文让你简单了解跨境电商需要购买堡垒机的几大原因 …

代码随想录day26(2)二叉树:二叉搜索树中的众数(leetcode501)

题目要求&#xff1a;给定一个有相同值的二叉搜索树&#xff08;BST&#xff09;&#xff0c;找出 BST 中的所有众数。结点左子树中所含结点的值小于等于当前结点的值&#xff0c;结点右子树中所含结点的值大于等于当前结点的值。 思路&#xff1a;如果不考虑二叉搜索树&#…

TCP机械臂控制

通过w(红色臂角度增大)s&#xff08;红色臂角度减小&#xff09;d&#xff08;蓝色臂角度增大&#xff09;a&#xff08;蓝色臂角度减小&#xff09;按键控制机械臂 注意&#xff1a;关闭计算机的杀毒软件&#xff0c;电脑管家&#xff0c;防火墙 1&#xff09;基于TCP服务器…

博途PLC 高速计数器复位功能块(HC_Reset)

高速计数器的使用和编码器应用请参考下面文章链接: 1、普通开关计米功能块(博途高速计数器应用) https://rxxw-control.blog.csdn.net/article/details/132354435https://rxxw-control.blog.csdn.net/article/details/1323544352、S7-1200PLC编码器转速测量功能块(高速计数…

含“AI”量上涨,智能模组SC208系列助力智慧零售全场景高质发展

AI正重塑智慧零售产业&#xff0c;加速零售在采购、生产、供应链、销售、服务等方面改善运营效率和用户体验。零售行业经历了从线下到线上再到全渠道融合发展过程&#xff0c;“提质、降本、增效、高体验”是亘古不变的商业化与智能化方向。含“AI”量逐渐上涨的智慧零售正经历…

Git——IDEA中的使用详解

目录 Git1、IDEA中配置Git2、将本地项目推送到远程仓库2.1、创建项目远程仓库2.2、初始化本地仓库2.3、连接远程仓库2.4、提交到本地仓库2.5、推送到远程仓库 3、克隆远程仓库到本地4、基本操作4.1、代码提交到暂存区4.2、暂存区代码提交到本地库4.3、推送到远程仓库4.4、撤销本…

leetCode刷题 18. 四数之和

目录 注意&#xff1a;正常提交后有问题。 1. 思路 2. 解题方法 2.1 排序数组 2.2 双指针遍历 3. 复杂度 4. Code 题目&#xff1a; 给你一个由 n 个整数组成的数组 nums &#xff0c;和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], …

TouchGFX之性能测量

TouchGFX Core开放了几个信号&#xff0c;可用于测量性能。 当这些信号在内部触发时&#xff0c;用户可在应用程序中同步触发单个GPIO&#xff0c;从而实现“渲染时间”和其他有用信号的可视化。 信号在GPIO.hpp中定义 /* 用于操作GPIO的接口类&#xff0c;以便在目标硬件上进…

力扣---完全平方数

思路&#xff1a; 还是比较好想的&#xff0c;g[i]定义为和为 i 的完全平方数的最少数量。那么递推关系式是g[i]min(g[i-1],g[i-4],g[i-9],...)1&#xff0c;数组初始化是g[0]0,g[1]1。注意这里要对g[0]初始化&#xff0c;&#xff08;举个例子&#xff09;因为在遍历到g[4]时&…

基于Springboot+Vue3的大学生毕业作业设计之—招投标分析系统

有系统开发需求可私信我提供帮助哦 ~ 部分 import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.conditions.query.L…

七、Java中SpringBoot组件集成接入【Minio文件服务器】

七、Java中SpringBoot组件集成接入【Minio文件服务器】 1.Minio介绍2.搭建Minio服务2.1Windows部署2.2Linux部署2.3docker部署 3.Minio可视化操作4.SpringBoot接入Minio1.添加maven依赖2.yaml配置文件3.配置类4.工具类5.控制类 5.常见问题6.其他参考文章 1.Minio介绍 对象存储…

解决vue3中刷新浏览器页面的axios请求状态变为canceled

最近在开发中要加一个悲观锁的功能&#xff0c;具体需求是&#xff1a;用户1和用户2不能同时打开一个模型进行编辑&#xff0c;用户1优先进入模型后&#xff0c;要对该模型进行上锁&#xff0c;关闭该模型或刷新页面时要进行解锁&#xff0c;此时在刷新页面时出现了问题。 刷新…

英伟达 GTC 2024大会不可错过的亮点!带你一窥AI和高性能计算的最新趋势!

会议之眼 快讯 2024年的英伟达GTC大会再次掀起了科技界的热潮&#xff0c;展示了该公司在人工智能、图形处理和高性能计算领域的最新突破。来自全球各地的科技领袖、工程师和创新者齐聚一堂&#xff0c;共同探讨未来技术的前沿。在这令人期待的盛会上&#xff0c;英伟达发布了一…

GraalVM详细安装及打包springboot、java、javafx使用教程(打包javafx项目篇)

前言 在当前多元化开发环境下&#xff0c;Java作为一种广泛应用的编程语言&#xff0c;其应用部署效率与灵活性的重要性日益凸显。Spring Boot框架以其简洁的配置和强大的功能深受开发者喜爱&#xff0c;而JavaFX则为开发者提供了构建丰富桌面客户端应用的能力。然而&#xff…

Windows东方通下载及使用

把安装包都拖到桌面来&#xff0c;可以拖一个解压包进去 下载东方通可以不用配环境变量 双击安装包 下一步 点击接受 选择版本&#xff0c;都可以 选择安装路径 下一步 点击安装 改端口号 移到桌面 把安装包里面的文件拖进去 过期了&#xff0c;记得改时间 点击时间面板&…

Google云计算原理与应用(四)

目录 七、海量数据的交互式分析工具Dremel&#xff08;一&#xff09;产生背景&#xff08;二&#xff09;数据模型&#xff08;三&#xff09;嵌套式的列存储&#xff08;四&#xff09;查询语言与执行&#xff08;五&#xff09;性能分析&#xff08;六&#xff09;小结 八、…

【考研数学】全年复习懒人包+资料分享

题主要真是能把这两样做透了&#xff0c;别说90&#xff0c;120都不是问题呀&#xff01;那么我们就先来说说你如何能把这1800做透吧。这可是人称考研数学路上最厚的一本习题册了。经常有人是做到一半就被劝退的&#xff01;假设你是挑题出来做&#xff0c;那也行&#xff0c;不…

Git Bash命令初始化本地仓库,提交到远程仓库

git init&#xff1a;初始化空仓库 // 初始化一个空仓库或者重新初始化一个存在的仓库 git init git remote // 为当前本地仓库添加一个远程仓库地址 git remote add origin https://gitee.com/xxx/demo.git git pull // 从设置好链接的远程仓库拉去已经存在的数据&#xff0c;…

centos7安装openGauss数据库

官网手册&#xff1a; https://opengauss.org/zh/download/ 操作系统选择centos&#xff0c;软件包类型选择极简版&#xff1a;https://opengauss.obs.cn-south-1.myhuaweicloud.com/5.0.1/x86/openGauss-5.0.1-CentOS-64bit.tar.bz2 硬件&#xff1a;2c4g 安装手册&#xf…

NBlog Java定时任务-备份MySQL数据

NBlog部署维护流程记录&#xff08;持续更新&#xff09;&#xff1a;https://blog.csdn.net/qq_43349112/article/details/136129806 为了避免服务器被攻击&#xff0c;给博客添加了一个MySQL数据备份功能。 此功能是配合博客写的&#xff0c;有些方法直接用的已有的&#xf…