线程的深度剖析

news2024/11/24 5:21:52

线程和进程的区别和联系:

1.进程可以处理多并发编程的问题,但是进程太重,主要指它的资源分配和资源回收上,进程消耗资源多,速度慢,其创建、销毁、调度一个进程开销比较大。

2.线程的出现有效地解决了这一问题,既可以处理并发编程的问题,又因为它可以实现资源共享(主要指的是内存和文件描述符表),内存共享是指线程1new的对象在线程2,3,4里面可以直接用,文件描述符表共享指的是线程1打开的文件在线程2、3、4里面可以直接使用,第一个进程开销比较大,以后的进程都可以和第一个进程共享资源,这样就减少了资源的消耗,又提高了速度

 举一个比较生动的例子:

有一个人要做一百只鸡,为了加快速度我们可以考虑一下再来一个人一起做,给每个人安排一间屋子,一个桌子

但是这样就比较耗费资源,这就是多进程。但是如果我们让两个人在同一间屋子同一个桌子上做,这样可以减少资源的开销了,屋子桌子共享

这就是 多线程。

3.进程里面包含多个线程,每个线程对应一个PBC,同一个进程里的PCB之间pid相同,同一个进程里的内存指针和文件描述符号表一样。

4.进程里面有多个线程,每个线程独立在CPU上调度,每个线程有自己的执行逻辑,线程操作是系统调度执行的基本单位。

5.增加线程的数量也不意味着可以一直提高速度,CPU的核心数量有限。

6.线程安全问题:因为线程之间可以资源共享,容易产生资源争夺的问题,会导致线程的异常问题,进而带走整个进程。

一.线程的创建:

// 定义一个Thread类,相当于一个线程的模板
class MyThread01 extends Thread {
    // 重写run方法// run方法描述的是线程要执行的具体任务@Overridepublic void run() {
        System.out.println("hello, thread.");
    }
}
 

public class Thread_demo01 {
    public static void main(String[] args) {
        // 实例化一个线程对象
        MyThread01 t = new MyThread01();
        // 真正的去申请系统线程,参与CPU调度
        t.start();
    }
}
// 创建一个Runnable的实现类,并实现run方法
// Runnable主要描述的是线程的任务
class MyRunnable01 implements Runnable {
    @Overridepublic void run() {
        System.out.println("hello, thread.");
    }
}

public class Thread_demo02 {
    public static void main(String[] args) {
        // 实例化Runnable对象
        MyRunnable01 runnable01 = new MyRunnable01();
        // 实例化线程对象并绑定任务
        Thread t = new Thread(runnable01);
        // 真正的去申请系统线程参与CPU调度
        t.start();
    }
}
* 通过Thread匿名内部类的方法创建一个线程

public class Thread_demo03 {public static void main(String[] args) {
        Thread t = new Thread(){
            // 指定线程任务
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        };
        // 真正的申请系统线程参与CPU调度
        t.start();
    }
}
* 通过Runnable匿名内部类创建一个线程

public class Thread_demo04 {public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            // 指定线程的任务
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());                
            }
        });
        // 申请系统线程参与CPU调度
        t.start();
    }
}
* 通过Lambda表达式的方式创建一个线程

public class Thread_demo05 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            // 指定任务:任务是循环打印当前线程名
            while (true) {
                System.out.println(Thread.currentThread().getName());
                try {
                    // 休眠1000ms
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 申请系统线程参与CPU调度
        t.start();
    }
}

二.线程的执行:

1.线程之间是并发执行的,并且是抢占式调度的。

public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                //System.out.println("hello");
                System.out.println("1");

            }
        });
        thread.start();
        //System.out.println("world");
        System.out.println("2");

    }

像这段代码我们无法确定是先打印“1”还是先打印“2”

2.start 方法和run()方法的区别:

run()方法里面是要执行的任务,我们创建了一个thread,只是把任务梳理好了,start方法才真正去创建线程,让内核创建一个PCB,此时PCB才表示一个真正的线程,去执行run()方法里面的任务

3.线程一旦执行完,内核里的pcb就会释放,操作系统里面的线程也就没了

public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                //System.out.println("hello");
                System.out.println("1");

            }
        });
        thread.start();
        //System.out.println("world");
        System.out.println("2");

    }

像这段代码执行main方法的线程在执行完打印“2”这条语句后就没了,像thread线程执行完run方法,也就没了,但thread这个对象还存在,当它不指向任何对象时,就会被GC回收,之所以PCB消亡,而代码中thread对象还存在,是因为java中的对象的生命周期,自有其规则,这个生命周期和系统内核里的线程并非完全一致,内核里的线程释放的时候,无法保证java代码中thread对象也立即释放,因此此时需要通过特定的状态,来把thread对象标识成’无效‘,也是不能重新start的,一个线程,只能start一次。

4.isAlive()方法用来判断系统里面的线程是不是真正创建好了。

Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
            }
        });
        thread.start();

            try {
                Thread.sleep(1000);
                System.out.println(thread.isAlive());
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }

    }

如果thread的run还没跑,此时thread.isAlive()返回的是false,如果thread的run正在跑,此时返回的是true,如果thread里的run跑完了,此时返回的是false,此时内核里的pcb就释放了,操作系统里的线程也就没了。

5.public static void sleep(long millis)

让线程休眠,本质上就是让这个线程不参与调度了,不去(CPU)上执行了。

public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                int i = 0;
                for (i = 0; i < 3; i++) {


                    System.out.println("hello");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }



        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("world");
    }

三.线程的终止:

我们通常使用interrupt()方法来终止线程,但是终不终止还是线程说了算

public static void main4(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                while(!Thread.currentThread().isInterrupted())
                {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    System.out.println(1);
                }

            }
        });
        thread.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread.interrupt();
        //System.out.println(2);
    }

 在这段代码中Thread.currentThread()表示来获取当前的线程,isInterrupted()表示是否受到了要终止的通知。

一开始的时候isInterrupted()是false,进入到while循环里面,当执行main函数的线程,执行到thread.interrupt()的时候thread线程此时被通知要终止,此时isInterrupted()被设置为true,按照正常情况而言此时while循环是进不去的,但是如果线程在进行sleep,就会触发异常,把刚才的isInterrupted()再设置回false,此时还可以进行循环,这时要不要终止线程,其实取决于我们,如果加上一个break,跳出循环,那么线程也就执行完了。

线程的等待:控制两个线程的结束顺序:

 Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
               int i=0;
               for(i=0;i<8;i++)
               {
                   System.out.println(1);
               }
            }
        });
        thread.start();
        System.out.println(3);
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(2);
    }

原本main函数的线程在执行完start语句的时候,main函数线程要去执行下边的代码,tthread这个线程要去执行run()方法里面的任务,但是当main函数线程执行到hread.join()这条语句表示执行main函数的线程在这里等待一下,先等thread这个线程执行完,再执行。

四.操作系统的内核:

就绪队列:

这个链表里的PCB都是''随叫随到的状态'' ,就绪状态

阻塞队列:

是指当线程调佣sleep时,此时这个线程会进入休眠的状态,那么它会进入到阻塞队列中

这个链表的PCB,都是阻塞状态,不参与CPU的调度执行。PCB是使用链表来组织的,并不是简单的链表。一旦这个线程被唤醒,不再处于休眠的状态,这个PCB会回到就绪队列,但是不会马上就会被调度,要考虑到调度的开销,比如调用sleep(1000),对应的线程PCB就要在阻塞队列中待1000ms这么久,当这个PCB回到了就绪队列,会被立刻调度吗?虽然是sleep(1000),但是实际上要考虑到调度的开销,对应的线程是无法在唤醒之后就立即执行的,实际上的时间间隔大概率要大于1000ms。

五.线程的状态

反映的是当前线程的调度情况。

 

1.NEW  创建了Thread对象,但是还没调用start(内核里还没创建对应的PCB)

public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
            }
        });
        System.out.println(thread.getState());
        thread.start();

    }

2.TERMINATED  表示内核中的pcb已经执行完了,但是Thread对象还在。

 public static void main(String[] args) {
        Thread thread=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(1);
            }
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(thread.getState());

    }
}

3.RUNNABLE 可运行的包括两部分,正在CPU上执行的或者在就绪队列上的,随时可以去CPU上执行

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //System.out.println(1);
                int i = 0;
                for (i = 0; i < 1000000; i++) {
                    ;
                }
            }
        });
        System.out.println("开始前"+thread.getState());
        thread.start();

            System.out.println("进行中"+thread.getState());
        try {
            thread.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("结束了"+thread.getState());

    }



}

4.WAITING

5.TIMED_WAITING

public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                //System.out.println(1);
                try {
                    Thread.sleep(1000000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        thread.start();
        for (int i = 0; i < 100; i++) {
            System.out.println(thread.getState());
        }
    }

6.BLOCKED

4 5  6 这三种状态都是阻塞状态,是由不同原因阻塞而成的。

五.多线程的意义:

我们可以通过代码来感受一下,单个线程和多个线程之间,执行速度的区别。

比如让实现一个变量自增200亿的操作。单个线程:

public static void main(String[] args) {
        long a=0;
        long i=0;
        long start=System.currentTimeMillis();
        for(i=0;i<200_0000_0000L;i++)
        {
            a++;
        }
        long end=System.currentTimeMillis();
        System.out.println(end-start);

    }

它的执行时间为: 

而如果我们让两个线程去完成的话,一个线程干自增100亿的活,那么时间会不会缩短呢?

public static void main(String[] args) {

        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                long d=0;
               long i=0;
               for(i=0;i<100_0000_0000L;i++) {
                   d++;


               }
            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {

                long c=0;
                long k=0;
                for(c=0;c<100_0000_0000L;c++)
                {
                    k++;
                }

            }
        });
        long beg=System.currentTimeMillis();
        thread1.start();
        thread2.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end=System.currentTimeMillis();
        System.out.println(end-beg);



    }

我们来看一下它执行完的时间:

 

我们可以看到两个线程一起完成这个自增操作确实要比一个线程完成这个自增操作花费的时间少,可能有人会问为什么两个线程执行的时间不是一个线程执行的时间的一半呢?首先呢多线程之所以块是因为它可以充分利用多核心CPU的资源,但是因为这两个线程在实际调度的过程中,这些次调度,有些是并发执行的(在一个核心上),有些是并行执行的(正在在两个CPU)上,我们没法报证这些次调度都是并行的,到底是多少次并发,多少次并行,取决于系统的配置。也取决于当前程序的运行环境。

下面我们再来看一下,当代码中出现两个join的时候,实际上的一个调度情况

  long beg=System.currentTimeMillis();
        thread1.start();
        thread2.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        long end=System.currentTimeMillis();
        System.out.println(end-beg);



    }

 

 我们看到当主函数main的线程执行完thread1.start这条语句时,thread1这个线程被真正创建,然后它去干它的活,main的线程执行thread2.start后,thread2这个线程也去干它的活,然后当main线程执行到thread1.join时,main函数线程停下来,等待thread1这个线程干完活,等thread1这个线程干完活后,main函数这个线程继续执行,这时候又遇到了thread2.join,需要等待thread2干完活,再继续执行,这里会存在一种情况,就是当thread2比thread1先干完活,那么main线程只需等待thread1干完活后执行就可以了,不需要再等待thread2了。多个线程同时执行,最终的时间,就是最慢的线程的时间,另外在谁中调用join,就是让谁这个线程等待,比如main调用t1,join就是让main来等待t1,如果是t2调用t1.join,就是让t2等待t1.

六.线程安全

多线程带来的风险,线程安全,根本原因是多线程的抢占式执行,带来的随机性!!!

我们来看一下一个线程安全问题的代码:

public class count {
    public static int count=0;
    public static void add()
    {
        count++;
    }
    public static void main(String[] args) {
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                for(i=0;i<10000;i++)
                {
                    add();
                }

            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                for(i=0;i<10000;i++)
                {
                    add();
                }
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count);

    }
}

像这段代码,thread1和thread2分别对count执行了一万次自增操作,理论上最终打印出来count的值应该是两万。

但是我们看一下运行结果:

 

我们看到结果不是两万,下面我们详细地来介绍一下原因。

首先呢?我们先了解一下++操作本质上要分成三步:1.先把内存中的值,读取到CPU的寄存器中,也就是load操作2.把CPU寄存器里的数值进行+1运算   add  3.把得到的结果写到内存中,这三个操作,就是CPU上执行的三个操作,视为是机器语言。

如果是两个线程并发的执行count++,此时就相当于两组load add save进行执行,此时不同的线程调度顺序 就可能会产生一些结果上的差异,

 

这是一种可能的调度顺序,由于线程之间是随机 调度的,导致此处的调度顺序充满其他的可能性。

 

 

 

 

自增两次结果为2,这种情况是正确的,没有线程安全问题!!! 

但是如果是这种情况:

 

 

这时我们进行了两次自增,count确为1,预期与实际不符,这时候线程是不安全的。 

线程安全问题的原因:

1.根本原因:抢占式执行,随机调度

2.优化结构:多个线程同时修改一个变量,一个线程修改一个变量,多个线程读取同一个变量,没事,多个线程修改多个不同的变量,也没事。

3.原子性:

如果修改操作是非原子性的,容易出现线程安全问题,count++可以拆分成load、add、save三个操作,如果++操作是原子性的,此时线程安全问题,也就解决了。

4.内存可见性问题

5.指令重排序

如何从原子性入手,解决线程安全问题。加锁!!!!通过加锁,把不是原子的,转成原子的。

加锁,说是保证原子性,其实不是说让这里的三个操作一次完成,也不是这三步操作过程中不进行调度,而是想让其他也想操作的线程阻塞等待了。

加锁的关键字为synchronized

 public static int count=0;
    public synchronized static void add()
    {
        count++;
    }
    public static void main(String[] args) {
        Thread thread1=new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                for(i=0;i<10000;i++)
                {
                    add();
                }

            }
        });
        Thread thread2=new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                for(i=0;i<10000;i++)
                {
                    add();
                }
            }
        });
        thread1.start();
        thread2.start();
        try {
            thread1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        try {
            thread2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(count);

    }
}

如果我们在add()方法前面加上synchronized,此时我们再运行。

此时打印出来的就是20000,符合预期。 

 

 

 

 

 

 


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

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

相关文章

面向对象的个人理解(使用JAVA代码描述)

前言 功能分类 类&#xff08;class&#xff09;的第一个功能是隔离&#xff0c;起到边界的作用&#xff0c;使得不同功能的代码互不干扰。 干扰的起源 在非面向对象的语言中&#xff0c;我们主要定义结构和函数来实现功能。下边用C语言来举个例子。 某程序员写了宠物模拟…

Ajax学习:nodejs安装+express框架介绍

ajsx应用中&#xff0c;需要安装nodejs环境 基于Chrome V8引擎&#xff08;和浏览器上的谷歌的解析引擎一样&#xff09;JavaScript运行环境 (31条消息) Node.js_安装_哇嘎123的博客-CSDN博客 查看安装是否完成 express框架介绍--为了创建一个web服务器 (31条消息) Express…

Spark系列之Spark体系架构

title: Spark系列 第四章 Spark体系架构 4.1 Spark核心功能 Alluxio 原来叫 tachyon 分布式内存文件系统Spark Core提供Spark最基础的最核心的功能&#xff0c;主要包括&#xff1a; 1、SparkContext 通常而言&#xff0c;DriverApplication的执行与输出都是通过SparkC…

STM32G491RCT6,STM32H743BIT6规格书 32bit IC MCU

STM32G4系列将强大的ArmCortex-M4加上FPU和DSP能力与丰富和先进的模拟外设相结合。它引入了两种新的数学加速器(Cordic和Filtering)&#xff0c; CAN-FD (Flexible Datarate)&#xff0c;USB Type-C接口的功率传输&#xff0c;包括物理层(PHY)和先进的安全功能。&#xff08;图…

Day38——Dp专题

DP专题 动态规划五部曲&#xff1a; 确定dp数组以及下标的含义 确定递推公式 dp数组如何初始化 确定遍历顺序 举例推导dp数组 1.斐波那契数 题目链接&#xff1a;509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; 思路&#xff1a;做dp类题目&#xff0c;根据…

Meta 内部都在用的 FX 工具大起底:利用 Graph Transformation 优化 PyTorch 模型

PyTorch 中的 graph mode 在性能方面表示更为出色&#xff0c;本文介绍 Torch.FX 这个强大工具&#xff0c;可以捕捉和优化 PyTorch 程序 graph。 一、简介 PyTorch 支持两种执行模式&#xff1a;eager mode 和 graph mode。 eager mode 中&#xff0c;模型中的运算符在读取时…

JUC并发编程

目录JUC概述什么是JUC进程与线程的概念线程的状态线程的方法并发与并行管程(Monitor)用户线程和守护线程Lock接口Synchronized 关键字Synchronized 作用范围Synchronized 实现卖票例子Lock接口什么是LockLock与synchronized的区别可重入锁&#xff08;ReentrantLock&#xff09…

剑桥大学哪些专业需要IB物理成绩?

如果IB学生申请目标是剑桥大学&#xff0c;那么申请哪些专业需要学习IBDP物理课程&#xff1f;要不要学习IBDP物理课程呢&#xff1f;剑桥大学&#xff1a;IB成绩要求高&#xff0c;招录竞争激烈 在申请英国大学之前&#xff0c;理性的申请者都会提前查看一下目标大学的学术成绩…

Python破解WIFI源代码,测试成功(暴力跑字典)

目录 1&#xff0c;先安装Python环境(这个不用多说了吧) 2&#xff0c;安装PyWifi 3,自己手工整理高频弱口令&#xff0c;不建议程序生成的字典&#xff0c;生成的字典成功率实在太低。 4&#xff0c;自己生成字典的算法&#xff1a; 5&#xff0c;破解WIF代码第一种&#…

NetCore多租户开源项目,快速后台开发企业框架,赚钱就靠她了

今天给大家推荐一个开源项目&#xff0c;基于.NetCore开发的、多租户的企业开发框架。 文章目录项目简介技术架构项目结构系统功能代码生成器部分功能截图项目地址项目简介 这是一个基于.Net和Layui、基于多数据库的多租户&#xff0c;敏捷开发优选框架。系统自带权限功能、数…

面向移动支付过程中网络安全的研究与分析

基础防护系统设计 4.1.1入侵监测系统 入侵监测系统&#xff08;IDS&#xff09;的部署主要是防治外界非法人员对银行网络进行攻击&#xff0c;及时发现非法人员的入侵行为&#xff0c;以确保能够立刻采取网络阻止措施。在银行网络中的关键部位部署入侵检测系统&#xff0c;可…

springboot中controller层代码优雅写法

在基于spring框架的项目开发中&#xff0c;必然会遇到controller层&#xff0c;它可以很方便的对外提供数据接口服务&#xff0c;也是非常关键的出口&#xff0c;所以非常有必要进行规范统一&#xff0c;使其既简洁又优雅。 controller层的职责为负责接收和响应请求&#xff0c…

快手资讯|快手推出多档世界杯相关节目

1、快手直播间上线“相亲角”功能 近日&#xff0c;快手直播间上线了“相亲角”功能&#xff0c;可为主播打造相亲功能。 此外&#xff0c;快手还在“热门活动”中推出了“婚庆”频道&#xff0c;主要展示“婚礼现场”、“婚纱照”等短视频内容。企查查App显示&#xff0c;北京…

博主常用的 idea 插件,建议收藏!!!

一、Key Promoter X **快捷键提示工具&#xff1a;**每次操作&#xff0c;如果有快捷键&#xff0c;会提示用了什么快捷键。 二、Maven Helper maven 助手&#xff1a;展示 jar 包依赖关系 三、Lombok 只需加上注解 什么get set 什么toString 等等方法都不需要写 四、MyBati…

Postman下载安装注册登录简介登录后界面简介

一、为什么选择Postman? 如今&#xff0c;Postman的开发者已超过1000万(来自官网)&#xff0c;选择使用Postman的原因如下:1、简单易用 - 要使用Postman&#xff0c;你只需登录自己的账户&#xff0c;只要在电脑上安装了Postman应用程序&#xff0c;就可以方便地随时随地访问…

小程序上新(2022.11.15~11.28)

20221115 小程序基础库 2.27.3 更新 更新 框架 设备 VoIP 能力授权更新 框架 支持 worker 代码打包到小程序&小游戏分包 详情更新 组件 scroll-view 接近全屏尺寸时默认开启点击回到顶部更新 API createVKSession 在不需要用到摄像头的时候不再发起摄像头授权 详情修复 框…

elasticsearch7.6.2和logstash安装和初步

一、linux安装 参考以下链接&#xff1a; Linux&#xff08;centos7&#xff09;如何部署ElasticSearch7.6.2单节点跟集群&#xff08;es部署指南&#xff09; 二、window安装 参考下文更加详细&#xff1a;windows ElasticSearch 7.6.0集群搭建 2.1 下载elasticsearch7.6.…

开源多商户商城源码代码分析

如今&#xff0c;互联网几乎普及到了所有地区&#xff0c;同时也推动了传统行业发展。目前&#xff0c;越来越多的线下商家开始搭建多商户商城系统&#xff0c;打造属于自己的淘宝、天猫电商服务平台。什么是多商户商城系统呢&#xff1f;想必大部分人并不是很了解&#xff0c;…

多线程基本概念

多线程多线程基本概念线程控制创建终止等待分离线程安全基本概念实现互斥互斥锁死锁同步线程应用生产者与消费者模型线程池单例模式多线程基本概念 线程是进程中一个执行流程&#xff0c;是 CPU 进行执行调度的基本单元&#xff1b; 进程是系统进行资源分配的基本单元。 Linu…

SpringBoot很熟?那手撕一下自定义启动器吧

一. 前言 哈喽&#xff0c;大家好&#xff0c;不知道你有没有想辉哥呢&#xff1f;我可是很想你们哟&#xff01;最近金九银十&#xff0c;又有不少小伙伴私信辉哥&#xff0c;说自己在面试时被问到SpringBoot如何自定义启动器&#xff0c;结果自己不知道该怎么回答。那么今天…