多线程与高并发——并发编程(1)

文章目录

  • 并发编程
  • 一、线程的基本概念
    • 1 基础概念
      • 1.1 进程和线程
      • 1.2 多线程
      • 1.3 串行、并行、并发
      • 1.4 同步异步、阻塞非阻塞
    • 2 线程的创建
      • 2.1 继承Thread类,重写run方法
      • 2.2 实现Runnable接口,实现run方法
      • 2.3 实现Callable接口,实现call方法,配合FutureTask
      • 2.4 基于线程池构建线程
    • 3 线程的使用
      • 3.1 线程的使用
      • 3.2 线程常用的方法
        • 3.2.1 获取当前线程
        • 3.2.2 线程的名字
        • 3.2.3 线程的优先级
        • 3.2.4 线程的让步
        • 3.2.5 线程的休眠
        • 3.2.6 线程的抢占
        • 3.2.7 线程守护
        • 3.2.8 线程的等待和唤醒
      • 3.3 线程的结束方式
        • 3.3.1 stop 方法(不用)
        • 3.3.2 使用共享变量(很少使用)
        • 3.3.3 interrupt 方法

并发编程

一、线程的基本概念

1 基础概念

1.1 进程和线程

什么是进程?

  • 进程是指运行中的程序。比如我们使用的钉钉、浏览器,需要启动这个程序,操作系统会给这个程序分配一定的资源(占用内存资源)。

什么是线程?

  • 线程是CPU调度的基本单位,每个线程执行的都是某一个进程的代码的某个片段。

举个例子:人和房子

比如,现在有一个 100 平米的房子,这个房子就可以看做是一个进程。房子里有人,人就可以看做是一个线程。人在房子里做一些事,比如:吃饭、学习、睡觉,这个就好像现场在执行某个功能的代码。

  • 所谓进程就是线程的容器,需要线程利用进程的一些资源,处理一个代码/指令,最终实现进程所预期的结果。

进程和线程的区别?

  • 本质不同:进程是操作系统分配的资源,而线程是CPU调度的基本单位。
  • 资源不同:统一个进程下的线程共享进程的一些资源,线程同时拥有自身的独立存储空间,进程之间的资源通常是独立。
  • 数量不同:进程就是一个进程,而线程是依附于某个进程的,而且一个进程中至少有一个或多个线程。
  • 开销不同:毕竟进程和线程不是一个级别的内容,线程的创建和终止时间是比较短的。而且线程之间的切换比进程之间的切换速度要快很多。而且进程之间的通讯很麻烦,一般要借助内核才可以实现;而线程之间通信,相当方便。

1.2 多线程

什么是多线程?

  • 多线程是指,单个进程中同时运行多个线程。
  • 多线程的目的就是为了提高CPU的利用率,可以通过避免一些网络IO或磁盘IO等需要等待的操作,让CPU去调度其他线程。这样可以大幅度提升程序的效率,提高用户的体验。

比如:Tomcat就可以做并行处理,提升处理的效率,而不是一个一个排队。

再比如:要处理一个网络等待操作,开启一个线程去处理需要网络等待的任务,让当前业务线程可以继续往下执行逻辑,效率可以得到大幅度提升。

多线程的局限性?

  • 如果线程数量特别多,CPU在切换上下文时,会额外造成很大的消耗。
  • 任务的拆分需要依赖业务场景,有一些异构化的任务,很难对任务拆分,并不是多线程处理就更好。
  • 线程安全问题:虽然多线程带来了一定的性能提升,但是在做一个操作时,多线程如果操作临界资源,可能会发生一些数据不一致的问题,甚至涉及到锁的操作时,会造成死锁的问题。

1.3 串行、并行、并发

什么是串行?

  • 串行就是一个一个排队,第一个做完,第二个才上。

什么是并行?

  • 并行就是同时处理(一起上!!)

什么是并发?

  • 这里的并发并不是三高中的高并发问题,这里是指多线程的并发概念(CPU调度线程的概念)。CPU在极短的时间内,反复切换执行不同的线程,看似好像是并行,但只是CPU的高速切换。
  • 并行囊括并发,并行就是多核CPU同时调度多个线程,是真正的多个线程同时执行。单核CPU无法实现并行效果,单核CPU是并发。

1.4 同步异步、阻塞非阻塞

同步与异步?

  • 执行完某个功能后,被调用者是否会主动反馈信息。

阻塞与非阻塞?

  • 执行完某个功能后,调用者是否需要一直等待结果的反馈。

同步阻塞:比如用锅烧水,水开后不会主动通知你;烧水开始执行后,需要一直等待水烧开。

同步非阻塞:比如用锅烧水,水开后不会主动通知你;烧水开始执行后,不需要一直等待水烧开,可以去执行一下其他的操作,但是需要时不时查看水开了没。

异步阻塞:比如用锅烧水,水开后会主动通知你水烧开了;烧水开始执行后,需要一直等待水烧开。

异步非阻塞:比如用锅烧水,水开后会主动通知你水烧开了;烧水开始执行后,不需要一直等待水烧开,可以去执行一下其他的操作。这个效果是最好的,平时开发时,提升效率最好的方式就是采用异步非阻塞的方式处理一些多线程的任务。

2 线程的创建

线程的创建分为三种方式:

2.1 继承Thread类,重写run方法

  • 启动线程时,调用 start() 方法,这样会创建一个新的线程,并执行线程的任务。
  • 如果直接调用 run() 方法,这样仅仅只会让当前执行 run 方法中的业务逻辑。
public class MyTest {public static void main(String[] args) {MyJob t1 = new MyJob();t1.start();for (int i = 0; i < 100; i++) {System.out.println("main:" + i);}}
}
class MyJob extends Thread {@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println("MyJob:" + i);}}
}

2.2 实现Runnable接口,实现run方法

public class MyTest {public static void main(String[] args) {MyRunnable runnable = new MyRunnable();Thread t1 = new Thread(runnable);t1.start();for (int i = 0; i < 1000; i++) {System.out.println("main:" + i);}}
}
class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("MyRunnable:" + i);}}
}

最常用的方式:

// 匿名内部类方式:
Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 1000; i++) {System.out.println("匿名内部类:" + i);}}
});
// Lambda 方式:
Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {System.out.println("Lambda:" + i);}
});

2.3 实现Callable接口,实现call方法,配合FutureTask

  • Callable 一般用于有返回值的非阻塞的执行方法——同步非阻塞
public class MyTest {public static void main(String[] args) throws ExecutionException, InterruptedException {// 1. 创建 MyCallableMyCallable callable = new MyCallable();// 2. 创建 FutureTask,传入CallableFutureTask task = new FutureTask<>(callable);// 3. 创建 Thread 线程Thread t1 = new Thread(task);t1.start();// 4. 启动线程t1.start();// 5. 做一些操作...// 6. 获取结果Object count = task.get();System.out.println("总和为:" + count);}
}
class MyCallable implements Callable {@Overridepublic Object call() throws Exception {int count = 0;for (int i = 0; i < 100; i++) {count += i;}return count;}
}

2.4 基于线程池构建线程

  • 底层其实是一种,实现 Runnable。

3 线程的使用

3.1 线程的使用

网上对线程状态的描述有很多,有说 5 种的、6中的、7种的,都可以接受。

  • 5 种状态:一般是针对传统的线程状态来说(操作系统层面)

在这里插入图片描述

  • Java中给线程定义了 6 种状态:

在这里插入图片描述

  • NEW:Thread 对象被创建出来了,但是还没有执行start方法。
  • RUNNABLE:Thread 对象调用了 start 方法,就为 RUNNABLE 状态(CPU调度/没有调度)。
  • BLOCKED、WAITING、TIMED_WAITING:都可以理解为是阻塞、等待状态,因为处于这三种状态下,CPU都不会调度当前线程。
    • BLOCKED:是 synchronized 没有拿到同步锁,被阻塞的情况;
    • WAITING:调用 wait 方法就会处于 WAITING 状态,需要被手动唤醒;
    • TIMED_WAITING:调用 sleep 方法或者 join 方法,会被自动唤醒,无需手动唤醒。
  • TERMINATED:run方法执行完毕,线程生命周期到头了。

代码演示

  • NEW:
public static void main(String[] args) {Thread t1 = new Thread(() -> {});System.out.println(t1.getState());
}
  • RUNNABLE:
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (true) {}});t1.start();Thread.sleep(500);System.out.println(t1.getState());
}
  • BLOCKED:
public static void main(String[] args) throws InterruptedException {Object obj = new Object();Thread t1 = new Thread(() -> {// t1 线程拿不到锁资源,导致变为 BLOCKED状态synchronized (obj) {}});// main线程拿到obj的锁资源synchronized (obj) {t1.start();Thread.sleep(500);System.out.println(t1.getState());}
}
  • WAITING:
public static void main(String[] args) throws InterruptedException {Object obj = new Object();Thread t1 = new Thread(() -> {synchronized (obj) {try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();Thread.sleep(500);System.out.println(t1.getState());
}
  • TIMED_WAITING:
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}});t1.start();Thread.sleep(500);System.out.println(t1.getState());
}
  • TERMINATED:
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}});t1.start();Thread.sleep(1000);System.out.println(t1.getState());
}

3.2 线程常用的方法

3.2.1 获取当前线程

  • Thread 的静态方法,获取当前线程对象
public static void main(String[] args) throws InterruptedException {Thread currentThread = Thread.currentThread();System.out.println(currentThread);// "Thread[" + getName() + "," + getPriority() + "," + group.getName() + "]";// Thread[main,5,main]
}

3.2.2 线程的名字

  • 在构建 Thread 对象完毕后,一定要设置一个有意义的名称,方便后期排查错误
public static void main(String[] args) {Thread t1 = new Thread(() -> {System.out.println(Thread.currentThread().getName());});t1.setName("模块-功能-计数器");t1.start();
}

3.2.3 线程的优先级

  • 其实就是 CPU 调度线程的优先级,Java 中给线程设置的优先级有 10 个级别,从 1 ~ 10 任取一个整数,如果超出这个范围,会报出参数异常的错误。
public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 1000; i++) {System.out.println("t1: " + i);}});Thread t2 = new Thread(() -> {for (int i = 0; i < 1000; i++) {System.out.println("t2:" + i);}});t1.setPriority(1);t2.setPriority(10);t2.start();t1.start();
}

3.2.4 线程的让步

  • 可以通过 Thread 的静态方法 yield,让当前线程从运行状态转变为就绪状态。
public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 100; i++) {if (i == 50) {Thread.yield();}System.out.println("t1:" + i);}});Thread t2 = new Thread(() -> {for (int i = 0; i < 100; i++) {System.out.println("t2:" + i);}});t2.start();t1.start();
}

3.2.5 线程的休眠

  • Thread 的静态方法,让线程从运行状态变为等待状态。
  • sleep 有两个方法重载:
    • 第一个就是 native 修饰的,让线程转为等待状态的效果;
    • 第二个是可以传入毫秒和一个纳秒的方法(如果纳秒值大于等于 0.5ms,就给休眠的毫秒值+1;如果传入的毫秒值为0,纳秒值不为0,就休眠1ms)
  • sleep会抛出一个 InterruptedException
public static void main(String[] args) throws InterruptedException {System.out.println(System.currentTimeMillis());Thread.sleep(1000);System.out.println(System.currentTimeMillis());
}

3.2.6 线程的抢占

  • Thread 的非静态方法 join,需要再某一个线程下去调用这个方法。
  • 如果main线程中调用了 t1.join(),那么 main 线程会进入到等待状态,需要等待 t1 线程全部执行完毕,再恢复到就绪状态等待 CPU 调度。
  • 如果main线程中调用了 t1.join(2000),那么 main 线程会进入到等待状态,需要等待 t1 执行 2s 后,恢复到就绪状态等待 CPU 调度。如果在等待期间,t1 已经结束了,那么 main 线程自动变为就绪状态等待CPU调度。
public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println("t1:" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.start();for (int i = 0; i < 10; i++) {System.out.println("main:" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}if (i == 1) {try {t1.join(2000);} catch (InterruptedException e) {e.printStackTrace();}}}
}

3.2.7 线程守护

  • 默认情况下,线程都是非守护线程。JVM 会在程序没有非守护线程时,结束掉当前 JVM。
  • 主线程默认是非守护线程,如果主线程执行结束,需要查看当前 JVM 内是否还有非守护线程,如果没有 JVM 会直接停止。
public static void main(String[] args) {Thread t1 = new Thread(() -> {for (int i = 0; i < 10; i++) {System.out.println("t1:" + i);try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});t1.setDaemon(true);t1.start();
}

3.2.8 线程的等待和唤醒

  • 可以让获取 synchronized 锁资源的线程,通过 wait 方法进入到锁的等待池,并且会释放锁资源。
  • 可以让获取 synchronized 锁资源的线程,通过 notify 或者 notifyAll 方法,将等待池中的线程唤醒,添加到锁池中。
  • notify 会随机的唤醒等待池中的一个线程到锁池中,notifyAll 会将等待池中的全部线程都唤醒,并添加到锁池中。
  • 在调用 wait 方法和notify、notifyAll 方法时,必须在 synchronized 修饰的代码块或者方法内部才可以,因为要操作基于某个对象的锁的信息维护。
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {sync();}, "t1");Thread t2 = new Thread(() -> {sync();}, "t2");t1.start();t2.start();Thread.sleep(12000);synchronized (MyTest.class) {MyTest.class.notifyAll();}
}private static synchronized void sync() {try {for (int i = 0; i < 10; i++) {if (i == 5) {MyTest.class.wait();}Thread.sleep(1000);System.out.println(Thread.currentThread().getName());}} catch (InterruptedException e) {e.printStackTrace();}
}

3.3 线程的结束方式

  • 线程结束的方式很多,最常用的是让线程的 run 方法结束,无论是 return 结束,还是抛异常结束,都可以。

3.3.1 stop 方法(不用)

  • 强制让线程结束,无论你在干嘛,不推荐使用这种方式,但是,它确实可以把线程干掉。
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}});t1.start();Thread.sleep(500);t1.stop();System.out.println(t1.getState());
}

3.3.2 使用共享变量(很少使用)

  • 这种方式用的也不多,有的线程可能会通过死循环来保证一直运行,这个时候可以通过修改共享变量来破坏死循环,让线程退出循环,结束 run 方法。
static volatile boolean flag = true;public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (flag) {// 处理任务}System.out.println("任务结束");});t1.start();Thread.sleep(500);flag = false;
}

3.3.3 interrupt 方法

  • 共享变量方式
public static void main(String[] args) throws InterruptedException {// 线程默认情况下,interrupt标记位:falseSystem.out.println(Thread.currentThread().isInterrupted());// 执行interrupt之后,再次查看打断信息Thread.currentThread().interrupt();// interrupt标记为:trueSystem.out.println(Thread.currentThread().isInterrupted());// 返回当前下线程,并归位为 false,interrupt标记为:true// 已经归位了System.out.println(Thread.currentThread().isInterrupted());// ====Thread t1 = new Thread(() -> {while (!Thread.currentThread().isInterrupted()) {// 处理业务}System.out.println("t1结束");});t1.start();Thread.sleep(500);t1.interrupt();
}
  • 通过打断 WAITING 或者 TIMED_WAITING 状态的线程,从而抛出异常自行处理,这种停止线程的方式是最常用的一种,在框架和 JUC 中也是最常见的。
public static void main(String[] args) throws InterruptedException {Thread t1 = new Thread(() -> {while (true) {// 获取任务// 拿到任务,执行任务// 没有任务了,让线程休眠try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();System.out.println("基于打断形式结束当前线程");return;}}});t1.start();Thread.sleep(500);t1.interrupt();
}

wait 和 sleep 的区别?

  • 单词不一样;
  • sleep 属于 Thread 类中的静态方法,wait 属于 Object 类的方法;
  • sleep 数据 TIMED_WAITING,自动被唤醒,wait 属于 WAITING,需要手动唤醒;
  • sleep 方法在持有锁时,执行后不会释放锁资源,wait 在执行后会释放锁资源;
  • sleep 可以在持有锁或者不持有锁时执行,wait 方法必须在持有锁时才可以执行。

wait 方法会将持有锁的线程从owner扔到WaitSet集合中,这个操作是在修改ObjectMonitor对象,如果没有持有synchronized锁的话,是无法操作ObjectMonitor对象的。

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

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

相关文章

STL之vector(讲解迭代器失效,拷贝构造函数等现代版写法)

还是老规矩&#xff0c;直接上代码&#xff1a; #pragma once #include "riterator.hpp" #include <iostream> #include <assert.h> #include <set> #include <map> using namespace std; namespace cc {template<class T>class vect…

Node基础--Node简介以及安装教程

1.Node简介 Node.js发布于2009年5月&#xff0c;由Ryan Dahl开发&#xff0c;是一个基于Chrome V8引擎的JavaScript运行环境&#xff0c;使用了一个事件驱动、非阻塞式I/O模型&#xff0c;让JavaScript 运行在服务端的开发平台&#xff0c;它让JavaScript成为与PHP、Python、Pe…

静态代码扫描工具 Sonar 配置及使用

概览 Sonar 是一个用于代码质量管理的开放平台。通过插件机制&#xff0c;Sonar 可以集成不同的测试工具&#xff0c;代码分析工具&#xff0c;以及持续集成工具。与持续集成工具&#xff08;例如 Hudson/Jenkins 等&#xff09;不同&#xff0c;Sonar 并不是简单地把不同的代…

B树和B+树MySQL为什么用B+树?

文章目录 B树和B树B树B树的定义B树的插入操作删除操作 B树B树的定义B树的插入操作删除操作 B树和B树的区别?MySQL数据库为啥用B树作为索引&#xff0c;而不用B树? B树和B树 原文链接&#xff1a;https://blog.csdn.net/jinking01/article/details/115130286 B树 B树的定义…

变色树脂T-46CC详解

一、产品介绍 Tulsimer T-46 CC 是一款专门研制的、优质的、强酸型的聚苯乙烯架构的阳离子交换树脂&#xff0c;具有核子级磺酸官能团&#xff0c; 并同时拥有绝佳的物理及化学稳定品质&#xff0c;因此广泛应用于水处理当中。 Tulsimer T-46 CC 主要用于水净化处理&#xff…

智慧党建VR虚拟3D数字化展厅发展和传承传统文化

三维全景虚拟现实技术应用在虚拟展馆中&#xff0c;主要是通过全景照片的虚拟与建模&#xff0c;营造出三维虚拟仿真的场景&#xff0c;从而结合展馆展示的需求&#xff0c;营造出更加有效的氛围&#xff0c;起到优化展示效果的作用。 三维全景虚拟现实技术的应用&#xff0c;能…

Java【手撕双指针】LeetCode 611. “有效三角形个数“, 图文详解思路分析 + 代码

文章目录 前言一、有效三角形个数1, 题目2, 思路分析1, 从左往右 or 从右往左?3, 代码展示 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: &#x1f4d5; JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 &#x1…

BootstrapBlazor组件使用:数据注解

文章目录 前言BB数据注解数据注解源码数据注解简介注解简单实例[BB 编辑弹窗](https://www.blazor.zone/edit-dialog)[ValidateForm 表单组件](https://www.blazor.zone/validate-form)使用简介 前言 BootstrapBlazor(一下简称BB)是个特别好用的组件&#xff0c;基本上满足了大…

jupyter notebook出现ERR_SSL_VERSION_OR_CIPHER_MISMATCH解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

软件测试架构师在实际工作中都做哪些事情呢?

软件测试架构师在实际工作中&#xff0c;都做哪些事情呢&#xff1f; 一家业务体系庞大、复杂的公司的测试架构师的职责主要有五个&#xff1a; 1、测试团队的技术带头人 测试架构师会关注整个团队的技术提升&#xff0c;包括技术难题的攻关&#xff0c;团队遇到的技术难题&…

(6)(6.3) 自动任务中的相机控制

文章目录 前言 6.3.1 概述 6.3.2 自动任务类型 6.3.3 创建合成图像 前言 本文介绍 ArduPilot 的相机和云台命令&#xff0c;并说明如何在 Mission Planner 中使用这些命令来定义相机勘测任务。这些说明假定已经连接并配置了相机触发器和云台(camera trigger and gimbal ha…

ORB-SLAM2报错集合(数据集测试系列1)

目录 错误1 错误2 错误3 错误4 错误5 错误6 错误7 错误8 TUM-RGBD测试 KITTI测试 EuRoC测试 写在前面~ ORB-SLAM2 github链接&#xff1a;GitHub - electech6/ORB_SLAM2_detailed_comments: Detailed comments for ORB-SLAM2 with trouble-shooting, key formula …

QChart:数据可视化(用图像形式显示数据内容)

1、数据可视化的图形有&#xff1a;柱状/线状/条形/面积/饼/点图、仪表盘、走势图&#xff0c;弦图、金字塔、预测曲线图、关系图、数学公式图、行政地图、GIS地图等。 2、在QT Creator的主页面&#xff0c;点击 欢迎》示例》右侧输入框 输入Chart&#xff0c;即可查看到QChar…

回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测(多指标,多图)

回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09; 目录 回归预测 | MATLAB实现SSA-RF麻雀搜索优化算法优化随机森林算法多输入单输出回归预测&#xff08;多指标&#xff0c;多图&#xff09;…

数据结构—排序

8.排序 8.1排序的概念 什么是排序&#xff1f; 排序&#xff1a;将一组杂乱无章的数据按一定规律顺序排列起来。即&#xff0c;将无序序列排成一个有序序列&#xff08;由小到大或由大到小&#xff09;的运算。 如果参加排序的数据结点包含多个数据域&#xff0c;那么排序往…

Ubuntu18.04 交叉编译curl-7.61.0

下载 官方网址是&#xff1a;curl 安装依赖库 如果需要curl支持https协议&#xff0c;需要先交叉编译 openssl,编译流程如下&#xff1a; Ubuntu18.04 交叉编译openssl-1.1.1_我是谁&#xff1f;&#xff1f;的博客-CSDN博客 解压 # 解压&#xff1a; $tar -xzvf curl-7.61.…

【Go】Goland项目配置运行教程

Golang项目配置运行教程 1.安装Golang下载安装包安装 2.Goland配置2.1 环境2.2 goland配置2.2.1 没有makefile的情况2.2.2 有makefile的情况 3.跨平台项目4.补充 注意&#xff0c;本项目描述的是git clone下来的Golang项目配置运行教程&#xff0c;并不是从头创建一个Golang项目…

redis常用五种数据类型详解

目录 前言&#xff1a; string 相关命令 内部编码 应用场景 hash 相关命令 内部编码 应用场景 list 相关命令 内部编码 应用场景 set 相关命令 内部编码 应用场景 Zset 相关命令 内部编码 应用场景 渐进式遍历 前言&#xff1a; redis有多种数据类型&…

Cookie 和 Session 的工作流程

目录 一、Cookie是什么&#xff1f; 二、Session是什么? 三、Cookie的工作流程 四、Session的工作流程 五、Session和Cookie的区别和联系 一、Cookie是什么&#xff1f; Cookie是一种在网站和用户之间交换信息的机制。它是由Web服务器发送给用户浏览器的小型文本文件&#xff…

【leetcode 力扣刷题】反转链表+递归求解

反转链表递归求解 206. 反转链表解法①&#xff1a;取下一个节点在当前头节点前插入解法②&#xff1a;反转每个节点next的指向解法③&#xff1a;递归 92.反转链表Ⅱ反转left到right间节点的next指向 234.回文链表解法①&#xff1a;将链表元素存在数组中&#xff0c;在数组上…