Java EE|多线程之线程状态与线程安全

news2024/11/19 0:27:58

文章目录

    • 一、线程状态
      • 线程状态分类
      • 线程各状态剖析
      • 线程状态的转移
    • 二、线程安全
      • 线程不安全举例
      • 线程安全概念
      • 线程不安全的原因剖析
      • Java标准库中的线程安全类与不安全类
      • 线程不安全问题的规避
        • 方案一:加监视锁
        • 方案二:加volatile关键字
        • 方案三:使用wait 和notify/notifyAll方法
    • 参考

一、线程状态

线程状态分类

首先说结论,线程的状态是一个枚举类型Thread.State。

这个枚举类型可以取的值有以下六种

  • NEW:安排了工作,但是还没有开始行动
  • RUNNABLE:可工作的,可以分为正在工作中和即将开始工作
  • BLOCKED:表示排队等待其他事情
  • WAITING表示排队等待其他事情
  • TIMED_WAITING:表示排队等待其他事情
  • TERMINATED:工作已完成

我们可以通过下边这样一个遍历( 状态组成的数组) 进行验证。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6e4jyDbU-1673449758965)(F:\typora插图\image-20230105172658672.png)]

线程各状态剖析

1.NEW状态

NEW状态可以理解为初始状态。

对于已经存在的线程类,我们new一个实例并赋值给线程对象,这个线程就进入到了初始状态即NEW状态。

2.RUNNABLE状态

我们上边已经知道RUNNABLE状态可以分为即将工作的和正在工作的线程,所以我们不妨自己把它们理解成就绪的线程(ready)和正运行的状态(running),方便我们后边进行区分。

🎈对于ready的RUNNABLE状态,我们主要讨论处于就绪状态线程的来源。

  • 来源一:当前线程调用start方法,这个线程就进入就绪状态
  • 来源二:当前线程sleep()方法结束,其他线程join()结束,等待用户输入完毕,某个线程拿到对象锁,这些线程也将进入就绪状态。
  • 来源三:当前线程时间片用完了,调用当前线程的yield()方法,当前线程进入就绪状态。

另外,我们可以理解为锁池里的线程拿到对象锁,就进入了就绪状态。

🎈对于running的RUNNABLE状态,我们差不多只需要掌握。线程状态变成运行中方式即可。

那就是负责线程调度的程序从可运行池中选择一个线程进行运行。

补充:线程调度是按照特定机制为多个线程分配cpu资源的使用权。线程调度分为分时调度和抢占式调度两种。java程序中线程调度是由JVM负责的,而JVM默认是使用的抢占式调度机制。

3.BLOCKED状态

BLOCKED状态也可以理解成为阻塞状态。

一般而言,阻塞状态是线程阻塞进入synchronized关键字修饰的方法/代码块(获取锁)时的状态。

这个synchronized关键字非常重要,它是解决多线程不安全问题的重要思路之一,我们在后边会详细讨论。

4.WAITING状态

WAITING状态也可以理解为等待状态。

我们主要掌握处于此状态下线程的特点。

等待状态下的线程的特点:不会被分配cpu资源,他们需要被显示的唤醒,否则将无休止的处于等待状态。

5.TIMED_WAITING状态

TIMED_WAITING状态也可以理解为限时状态。

我们也是只知道处于此状态线程的特点即可。

限时等待状态下的线程的特点:不会被分配cpu资源,但是与WAITING 状态不同的是,他不需要无休止的等待,一定时间内,没人唤醒,就会自动苏醒。

6.TERMINATED状态

TERMINATED状态又叫做终止状态。

什么时候会进入终止状态?当线程的run方法完成时,或者主线程的main线程完成,此线程就进入terminated状态。

需要强调的是,或许这个线程对象是ALIVE 的,但是它已经不是一个可以独立执行的线程了。一个线程一旦终止不能复生。

假设在一个已经终止的线程上调用start方法,会抛异常java.lang.illegalThreadStateException异常。

总的来说,BLCKED、WAITING和TW都是干的等待的工作,NEW、RUNNABLE、TERMINATED三个构成一条主线,是一个线程出生、活着到死亡的全过程。

线程状态的转移

首先,我们不难画出来这个过程,先new完有了这个线程,才有可能进入runnable状态,运行完了才能进入terminated状态。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oanXAMr6-1673449758966)(F:\typora插图\image-20230105183517295.png)]

又因为状态的转换是需要条件的,所以我们这里再进行一些补充说明,加一些“反应条件”。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rY1DDo2u-1673449758967)(F:\typora插图\image-20230105183936540.png)]

然后主线完了,我们来看三个支线.其实剩余的三个状态都是在等待。只是造成的原因不同,或者说是“反应条件不同”。sleep方法导致的TW状态很好理解,因为我们在上一篇已经提到了sleep方法。而这里的WAITING是涉及到我们后边要说的wait、notify、notifyAll方法。同时,这里的BLOCKD状态涉及到后边的监视锁,相信学完后边的,我们就能更好的理解这些反应条件了。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fXcPqDYm-1673449758968)(F:\typora插图\image-20230111191640770.png)]

二、线程安全

线程不安全举例

我们来看这样一个多线程代码

class Counter {
    public int count = 0;
    void increase() {
        count++;
    }
}
public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException {
        final Counter counter = new Counter();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                counter.increase();
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter.count);
    }
}

根据之前所学知识预想,最终打印的值应该是100000才对,事实真的如此吗?我们多次运行程序我们会发现有下边的结果。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hiF3JgaA-1673449758969)(F:\typora插图\image-20230105192713943.png)]

显然,最终结果不到10_0000大概是在98900左右,这是什么原因呢?

其实这跟run方法里边的++操作有关。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JfaCNYJD-1673449758970)(F:\typora插图\image-20230111194839119.png)]

又因为线程是抢占式执行,随机调度的,所以,很有可能我们在执行++操作中的某一个指令时,cpu被调度走了,然后我们还没有把本次add后的结果写入,另一个已经load了,即加载的太早了,最终得到的结果错误。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U7Q5zJMF-1673449758971)(F:\typora插图\image-20230111194530987.png)]

除此上述图解情况以外,还有很多,我们根本没办法枚举完。比如下边的,如果只有前两种会得到正确的答案,但是显然是不太可能的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PZB2JwA0-1673449758971)(F:\typora插图\image-20230111195706580.png)]

事实上,这就是多线程编程带来的风险,线程不安全。那到底什么是线程安全?线程不安全又是什么原因导致的呢?我们有没有办法规避这样的风险呢?如果没有办法规避有没有什么办法尽量避免吗?

我们来正式讨论这些问题。

线程安全概念

由于线程安全没有确切的定义,所以这里只是给出一种可行的线程安全的判定规则。

如果多线程环境下代码运行的结果与单线程环境下结果相同,那么我们就可以认为此代码是线程安全的。

线程不安全的原因剖析

常见的造成线程不安全的原因主要有以下几种,我们首先全部给出,再逐个解释。

  • 修改共享数据
  • 修改操作不是原子性的
  • 抢占执行,随机调度(根本原因)
  • 指令重排序
  • 内存可见性问题

需要特别说明的是,除了这些原因,还可能有其他原因,我们 需要具体问题具体分析。再有就是,出现线程不安全是一个概率事件。不是说我们有上述行为,bug必然发生,也不是说不出现上述行为,bug不可能出现。

1.修改共享数据

修改共享数据,简单来说,其实也就是多个线程同时对一个变量进行修改。

比如,上边两个线程t1,t2同时对counter.count进行修改,又操作不是原子的,所以导致出bug。

2.修改操作不是原子性的

要理解好这个原因,我们就需要先明白什么是原子性。

什么是原子性?不可再拆分的基本单位。这种不可再分割的现象其实也可以叫做同步互斥,表示操作是互相排斥的。那既然这样,我们能否简单的认为一条java语句就是原子的?答案是否定的,java语句是高级语言,它会转成汇编语言最后转成机器语言,这也就意味着,一条java语句可能对应指令/机器语言。比如n++,其实这个操作是由三条指令组成的:①从内存把数据读到cpu②进行数据更新③把数据写回cpu。

具体就像上边例子一样,由于操作不是原子的,又因为多线程抢占执行、随机调度的,我们刚执行完第一个线程的load,cpu就被另一个线程抢走了,开始执行另一个线程的load,这样的话再写回去得到的结果肯定比线程安全的时候要小。

3.抢占执行,随机调度

这是根本原因,是由OS内核实现决定的。

目前我们广泛使用的、以及以后工作使用的OS基本都会有这个问题,所以我们这方面无能为力,只能接受。【不是技术问题】

4.指令重排序

本质上是编译器优化出了bug。

编译器是由大神实现的。当编译器觉得我们自己写的代码太low,就会对代码进行优化,保持逻辑不变的情况下,调整代码的执行顺序,从而加快程序的执行顺序。绝大多数时候是不会优化出错的,但是我们不排除特殊情况。

注意,上边例子中的++中几套load\add\save顺序的改变并不是指令重排序,那只是由于cpu被两个线程来回抢造成的结果。

5.内存可见性

与修改操作不是原子性的那个操作类似,我们要理解内存可见性对线程安全造成的影响,也需要首先弄明白可见性问题。

什么是可见性呢?可见性就是一个线程对共享变量值的修改,能够及时地被其他线程看到。

而对于一个变量而言,一个线程读另一个改,很可能是会出问题的,如下:

class MyCounter{
    public int flag=0;
}
public class Code15_ThreadMemoryVQ {
    public static void main(String[] args) {
        MyCounter myCounter=new MyCounter();
        Thread t1=new Thread(()->{
           while(myCounter.flag==0){

           }
            System.out.println("循环结束");
        });
        Thread t2=new Thread(()->{
            Scanner sc=new Scanner(System.in);
            System.out.println("请输入一个整数:");
            myCounter.flag=sc.nextInt();
        });
        t1.start();
        t2.start();
    }
}

我们预期的结果应该是t1处于RUNNABLE状态,t2线程启动后,输入整数非0值,对flag进行了修改,循环结束,打印“循环结束”字样,可是事实是这样吗?

在等待一段时间后,console控制台仍是这样

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-K7nRbwgB-1673449758972)(F:\typora插图\image-20230111214938206.png)]

显然,这是出bug了,只是什么原因呢?其实这是因为JVM/编译器对多线程代码错误判断导致的,这句话怎么理解呢?

while(myCounter.flag==0)这句话的二进制指令其实是两条,分别是load(把内存中的值加载到寄存器中)和cmp(比较)。正如前面我们提到的cpu对于寄存器和对内存访问速度的差异,所以v(load)<<v(cmp),所以jvm做了一个大胆决定,把这些load判定为无效,不再重复load,只读取一次。这其实是一种编译器优化失败的结果。

Java标准库中的线程安全类与不安全类

不安全的类:

  • ArrayList
  • LinkedList
  • HashMap
  • TreeMap
  • HashSet
  • TreeSet
  • StringBuilder

为了方便记忆,我们可以记成“最迷人的也最危险(ds七子)”。

因为这几个恰巧是我们前边数据结构重点实现或者使用的线性表的两个,Map&Set对应的四个,再加一个StringBuilder.

安全的类:

Vector(不推荐使用)、HashTable(不推荐使用)、ConcurrentHashMap、StringBuffer

这些标准库中类线程安全的原因就是使用了一些锁机制来控制。

除此以外,String没有加锁,但是由于是不可修改的(没有向外提供公开的接口),是天然线程安全的。

我们简单记成,VHCSS。V是Vector,H是Hashtable,C是ConcurrentHashMap,S是String,S是StringBuffer。

我们在这里很容易有问题,为什么线程安全的类不推荐使用?

因为线程安全是有代价的,我们这里利用锁机制和其他方法让它安全了,但同时也降低了代码的速度。

相较于线程安全的类,线程不安全的类,灵活性更高。

比如,我们在单线程环境下,使用这些线程不安全的、未被太多东西束缚的类效率更高;在多线程环境下,我们可以通过手动处理,尽可能的规避线程不安全的风险。

线程不安全问题的规避

前边已经提到,线程不安全是一个概率事件,所以我们不能完全肯定的说解决这个问题,只能说最大可能的避免。

方案一:加监视锁

可能很多人对线程安全问题中提到锁这个概念感觉一头雾水,其实我开始也是。不过我们一点一点剖析,就会发现好像也没那么难理解。

首先,我们明确加监视锁是为了解决什么问题。它是为了解决非原子性的问题的,通过加(监视)锁,我们就可以把非原子的操作转换成原子的操作。这么说或许你还觉得比较抽象。举个例子,你是一个古代人,要去做饭,那必须先要劈柴吧,你不能砍到一半说就不干了,你必须得把所有的做饭需要用的柴砍完了,才能够去做烧柴做饭后序操作吧,不然,你砍到一半,去烧了,诶,柴不够,火吧唧就灭了,你又去砍柴了,回来一看,饭凉了,又得重新烧火……最后做出来的饭很大几率就是不熟的。(忽略特殊情况,请勿杠)但是呢,假如,我们将人用个金钟罩箍到那,你必须砍完烧饭需要的柴,你才能离开去起火烧柴做饭,这个时候是不是很大可能降低了做出半生半熟的饭的几率。其实这个箍人就相当于加锁的动作,而这个金钟罩就是锁,也就相当于代码中的大括弧包起来的一团,锁对象就是人,这个时间内你到底做什么我不关心,但是你必须整套打下来,谁都不能把你带走。最终就达到降低错误几率的目的。

其次,监视锁,英文名叫monitor lock,实现的话就是通过synchronized关键字实现的。关于synchronized这个关键字,汉语意思就是同步的,很好理解,就是说被它括着代码必须一起执行,注意这里的一起不是同一时刻,而是说一部分执行了,另外一部分也必须执行,他们是一体的。

最后,我们可以理解为锁是一种权限,一种把锁对象圈到自己领地不被别人使用的权限,线程哥几个始终抢的都是锁对象,而不是锁本身,他们都在尝试对对象加锁这个行为,这个动作,这是一种权限,有这个权限的线程成功,没有权限的失败。所以我们后边会有其他线程获取锁对象/锁这两种说法,都是一个意思,一个是实体,一个是获得实体的权限,都是名词

下边,我们就来看看synchronized这个关键字的具体用法。

(一)synchronized关键字的使用方法及示例

synchronized是起到加锁作用,那么加锁肯定需要有对象。就像我们“绑”着那个人,不让他去干其他事情,我们假设其他事情成精了,如果需要被它做就需要站在金钟罩外边候着。(这几个事情等价于其他线程的run方法里,人相当于对象,被来回争)。它总体上有三种用法,修饰普通方法,修饰静态方法,修饰代码块。(或者也可以说是两种,修饰方法,方法中有普通方法和静态方法,以及修饰代码块)锁对象,对于普通方法而言,它是隐含了一个this引用,所以此时synchronied的加锁对象就是this;对于静态方法的话,虽然没有this引用,但是每个类有一个唯一的类对象,这时它的加锁对象就是类对象,也是隐含的;代码块的加锁对象,就需要我们显示的指定的了,视情况而定。反映到上代码一般就是从this、类名.class(类对象)和普通对象变量中三选一。

补充:类名.this是内部类调用外部类对象的相关用法

总而言之,synchronized必须搭配一个具体对象使用。下边就是几个用法

  • 直接修饰普通方法

    对于位置的话,我们可以理解成这是一个形容词,和public是一个级别的(虽然从含义上并不是),都是形容词,谁在前在后都是一样的。

    锁对象:this

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kagp7DFO-1673449758972)(F:\typora插图\image-20230111205740102.png)]

  • 修饰静态方法

    synchronied在public前后都是一样,这里就只写一种了

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-V6ol3yGR-1673449758973)(F:\typora插图\image-20230111205807218.png)]

  • 修饰代码块

    比如main方法里的代码块、add方法里的

    锁对象:target2

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RuMSrUlT-1673449758973)(F:\typora插图\image-20230111210039214.png)]

​ 锁对象:this[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZEw6Zo9-1673449758974)(F:\typora插图\image-20230111210306445.png)]

(二)synchronized关键字的特性/作用

  • 互斥性:

    其中一个线程已经拿到这把锁/这个被锁的对象了,其他线程也有尝试获取的机会,但是不好意思,无一例外,全部被拒绝,他们必须安静等待当前线程释放对象/锁,才能竞争。这也就相当于A线程拿到锁/对象,BCD线程尝试竞争,结果发现已经被占了,他们就必须进入对象/锁的阻塞队列中去,不可以强行开锁。或者说对于这几个BCD线程和A线程是磁石中NS级的关系

  • 可重入

    可重入就是同一个线程对于同一个对象,同时加锁两次或两次以上是否有问题。如果没问题,不产生bug,这把锁就是可重入的,反之这把锁就是不可重入的,这把锁也可以称为死锁。

    死锁这个概念非常重要,内容也是不少,我在这里单独总结了一下,有需要的可以参考(下一篇)。

总而言之,对于这个synchronized,我们需要记住它的用法、特性。另外它的本质其实就是把可能出bug的多线程从并行变成串行了。

方案二:加volatile关键字

首先,我们明确volatile关键字是为了解决哪个问题的。它是为了解决内存可见性问题。

其次,volatile中文意思是易变的,其实在这里也是给JVM提醒,在这个做标志的地方注意。

(一)volatile的使用方法

修饰变量。与synchronized位置一样,可放在public前边,可放在后边。

加上关键字后,相当于给编译器提了个醒。

(二)volatile的使用示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6KN1r1xG-1673449758974)(F:\typora插图\image-20230111222741930.png)]

(三)synchronized和volatile区别

synchronized保证原子性,volatile不保证原子性,保证内存可见性。

方案三:使用wait 和notify/notifyAll方法

由于线程抢占执行和随机调度是由OS内核实现决定的,所以这个我们没办法改变。但是我们可以通过对对象调用wait、notify/notifyAll方法来控制线程的执行顺序,尽可能的达到我们的目的。

(一)wait作用

使当前线程阻塞在调用wait方法对象的阻塞队列中。主动让出cpu资源。

(二)wait方法使用实例

wait方法的工作内容:①先释放锁②进行阻塞等待③收到通知,重新尝试获取锁

因此,我们需要注意,要对对象调用wait方法这句代码用synchronied包裹即加锁。

另外与其他有阻塞功能方法类似的是,wait也可以通过interrupt异常唤醒,因此我们这里需要进行异常处理,这里我们采取的是声明中断错误异常的方式。

public class Code17_ThreadWait {
    public static void main(String[] args) throws InterruptedException {
        Object object=new Object();
        synchronized (object) {
            System.out.println("对象调用wait之前");
            object.wait();
            System.out.println("对象调用wait之后");
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gC9T3CrO-1673449758975)(F:\typora插图\image-20230111223623398.png)]

(三)notify方法的作用

通知同对象的阻塞队列中的首元素醒醒,开始工作了!!

(四)notify方法使用示例

跟wait方法类似的是,notify方法的首个工作内容也是释放锁,所以必须进行加锁处理。

另外,我们必须保证wait所在的线程在notify所在线程前边执行,不然notify所在的线程先执行了,也就是无效通知了,但wait所在的线程不就陷入死等了吗?一直在阻塞,就会有问题。所以,这里我们为了尽可能保证t1在t2之前执行,我们在t1线程启动之后和t2线程中都sleep,降低上述事件发生的概率。

public class Code18_ThreadWN {
    public static void main(String[] args) throws InterruptedException {
        Object object1=new Object();
        Thread t1=new Thread(()->{
            //等待线程
            System.out.println("t1:wait之前");
            try {
                synchronized (object1) {
                    object1.wait();
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("t1:wait之后");
        });
        Thread t2=new Thread(()->{
            //优先线程,
            System.out.println("t2:notify之前");
            //为保证notify获得锁,我们这里加锁,同时等待,确保前边的线程进入了等待状态
            synchronized (object1) {
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                object1.notify();
            }
            System.out.println("t2:notify之后");
        });
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

(五)notifyAll方法

与notify功能类似,都是通知同对象的线程。但是略有区别的是,notify是随机唤醒一个同对象所在的阻塞线程(假设有多个线程),notifyAll是唤醒所有的,剩下的进行所竞争。

总而言之,这个风险还是比较大的,所以用的也是比较少。

❗wait和sleep的区别【面试题】

相同点:都可以让线程主动放弃线程执行(cpu资源争夺)一段时间

不同点:wait是用于线程之间通信的,sleep只是是让线程阻塞确定的时间。

其实没有可比性……

❗wait和join:obj.wait()和t.join()分析

obj.wait是让把当前线程放进obj对象的阻塞/等待队列中。【当前线程还活着】

t.join是让主线程等待t死亡。【主线程等t死亡】

参考

线程6种状态的理解

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

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

相关文章

Mybatis框架,以 paramDTO对象集合形式进行批量查询。

在日常工作中&#xff0c;我们时常遇到数据库表中以 (name code) 为唯一索引&#xff0c; 在数据查询时需要根据这一组条件查询出表中唯一1条数据。 有时&#xff0c;我们的入参是 List<DTO> &#xff0c;需要进行批量查询数据。那么可以使用以下的SQL来进行数据的查询…

崔宝秋国际开源经验在小米开花 | 开源英雄

【编者按】崔宝秋的开源人生&#xff0c;来源于内心的真正热爱&#xff0c;和一路的升级打怪。从少时与计算机结缘到成为自由软件信徒&#xff0c;从IBM、雅虎、LinkedIn 到小米这一路走来&#xff0c;从一个开源的追随者成长为开源的推动者&#xff0c;小米的开源文化、丰硕的…

kettle工具连接elasticsearch 7并导入MySQL表数据-简述

一、简述 工作中需要利用kettle开源ETL工具将MySQL数据库中表数据同步到elasticsearch集群中。为此特记录一下操作方式&#xff0c;留作记录和学习。 二、环境 kettle工具&#xff1a;kettle 9.2 elasticsearch集群&#xff1a;7.11.1 集群配置信息&#xff1a; 集群名称&…

Linux-终端命令行

终端(Terminal)命令行仅仅是一个工具&#xff0c;对于命令有许多&#xff0c;而且随着嵌入式领域不断开发&#xff0c;命令会越来越多&#xff0c;我们只需要去了解常用的即可。 对于创建或删除一个文件夹&#xff0c;清理终端&#xff0c;下载文件等等都可以在终端命令行输入…

Nginx 进阶篇

文章目录Nginx 进阶篇五、 服务配置1、 配置成系统服务2、 配置环境变量六、 部署静态资源1、 概述2、 配置指令2.1 listen2.2 server_name2.3 location2.4 root2.5 alias2.6 index2.7 error_page3、 静态资源优化配置语法4、 静态资源压缩模块4.1 Gzip 模块配置指令4.2 配置实…

《Linux运维实战:Centos7.6基于ansible一键离线部署redis6.2.8容器版之哨兵集群》

一、部署背景 由于业务系统的特殊性&#xff0c;我们需要针对不同的客户环境部署redis 哨兵集群&#xff0c;由于大都数用户都是专网环境&#xff0c;无法使用外网&#xff0c;为了更便捷&#xff0c;高效的部署&#xff0c;针对业务系统的特性&#xff0c;我这边编写了基于ans…

唤醒手腕 Go 语言开发学习笔记(基本简介、环境安装)

1. Go语言简介 Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译型语言。Go 语言语法与 C 相近&#xff0c;但功能上有&#xff1a;内存安全&#xff0c;GC&#xff08;垃圾回收&#xf…

Java中String类的常见问题

目录 Q1&#xff1a;为什么String类要定义成不可变类型&#xff1f; 方便String对象缓存HashCode值 字符串常量池的需要 衍生问题&#xff1a;String str new String("hello world") 创建了几个对象&#xff1f; 为了保障程序的安全性 Q2&#xff1a;String的…

2022年深度学习在时间序列预测和分类中的研究进展综述

时间序列预测的transformers的衰落和时间序列嵌入方法的兴起&#xff0c;还有异常检测、分类也取得了进步 2022年整个领域在几个不同的方面取得了进展&#xff0c;本文将尝试介绍一些在过去一年左右的时间里出现的更有前景和关键的论文&#xff0c;以及Flow Forecast [FF]预测…

解决Windows莫名其妙地从休眠状态唤醒的问题

因为经常写代码开很多个工作区&#xff0c;电脑经常就是合盖休眠&#xff0c;这样打开就能用&#xff0c;但是总是在我不经意的时候发现电脑风扇在狂转&#xff0c;不知道是哪个应用又在qj我的电源管理计划&#xff0c;今天午休发现电脑风扇狂转&#xff0c;打开发现是windows系…

c语言操作符(下)

前言 &#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏: &#x1f354;&#x1f35f;&#x1f32f; c语言初阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f349;本篇简介:>: 讲解c语言中有关操作符的知识. 金句分享: ✨✨✨行程…

我用python写了个小病毒,老板再也不敢扣我工资啦

名字&#xff1a;阿玥的小东东 学习&#xff1a;python正在学习c 主页&#xff1a;阿玥的小东东 今天做个什么呢&#xff0c;我的好朋友提议做一个关机的代码吧&#xff0c;我这一想&#xff0c;对吼&#xff0c;我以前还做过一个关机代码呢&#xff0c;于是乎&#xff0c;今天…

时序收敛技巧之寄存器复制

1、何时需要复制寄存器&#xff1f; 在设计中的关键路径发现某个寄存器具有高扇出和高延迟时&#xff0c;使用寄存器复制是个不错的实现时序收敛的方法。下图是一个典型的例子&#xff1a; 可以看出&#xff0c;第一个寄存器的 IC(InterConnect) 延迟为 1.476ns&#xff0c;该寄…

seo站内优化的方法有哪些(站内优化和站外优化)

站内部优化什么意思 网站优化公司的内部优化意味着什么&#xff1f;网站内部优化&#xff0c;就是网站关键词优化、内容优化、内链优化等等&#xff0c;网站要有一定的排名和流量&#xff0c;要做到内部优化&#xff0c;要做到最快&#xff0c;最快的优化排名&#xff1f;接下…

「认知」打工人的自我修养

都说认知以外的钱难搞&#xff0c;那认知内的呢&#xff1f; 01互联网内卷年代&#xff0c;作为不着调的普通选手&#xff1b; 在诸多花里胡哨的黑话中&#xff0c;个人最待见的就是"认知"这个词&#xff1b; 认知&#xff0c;有强烈的抽象感&#xff1b; 想要深刻…

Mr. Cappuccino的第37杯咖啡——Kubernetes资源管理

Kubernetes资源管理kubectl常用的命令陈述式管理方法1. 创建一个namespace2. 查询所有namespace3. 在namespace下运行一个pod4. 查询指定namespace下的pod5. 查看指定pod的详细信息6. 删除pod7. 删除namespace声明式管理方法命令式对象配置1. 创建yaml文件2. 基于yaml文件创建资…

【JavaEE】多线程(初阶)

目录 Thread 线程的创建 线程的常见属性 中断线程 等待线程 休眠线程 线程的状态 多线程相比于单线程的优势 Thread 在Java中操作多线程&#xff0c;最常用的类就是Thread。 Thread 类是 JVM 用来管理线程的一个类&#xff0c;换句话说&#xff0c;每个线程都有一个唯一…

一网打尽带环链表问题(手把着手带你理解,木有基础也能看懂!)

目录 0.前言 1.认识带环链表 2.带环链表OJ问题简述 3.判断是否为带环链表 4. 寻找入环节点&#xff08;法一&#xff1a;数学推理的魅力&#xff09; 5. 寻找入环节点&#xff08;暴力拆解带环链表的魄力&#xff09; 6.番外&#xff1a;判断是否为带环链表(fast和slow的…

树上的DP

ACACAC来的如此之快&#xff0c;让我以为还要再调试几个点(h)&#xff0c;本文涉及思路参考&#xff1a;参考文章 嗯,这个题和思路让我对跨父节点的树上链有了一个认识&#xff0c;我们来看一下&#xff1a; 如果说&#xff0c;我们要对这样的一条链进行dpdpdp操作&#xff0c…

[GXYCTF2019]BabysqliV3.0

发现我们随便上传一个以后返回的是 alert(Wrong pass); 密码错误 如果上传一个账号永真的话返回 Not this user! 没有这个用户&#xff0c;所以感觉可以使用盲注&#xff0c;对账号进行盲注 可是题目 ascii(substr((select database()),1,1))>1 也不对 最后才知道是…