java8 第七章-----多线程

news2025/1/19 17:24:07

7.1、线程基本知识

进程与线程:

进程(Process)是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。

线程(Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行。

在 windows 下查看进程的命令:

  • 查看进程:tasklist
  • 杀死进程:taskkill /pid <PID>

在 linux 下查看进程的命令:

  • 查看进程:ps -fe
  • 查看线程:top -H -p <PID>
  • 杀死进程:kill <PID>

在 java 中查看进程的命令:

  • 查看Java进程:jps
  • 查看Java线程:jstack <PID>
  • 图形化的工具:jconsole

并行与并发:

单核 CPU 下,线程实际还是 串行执行 的。操作系统中有一个组件叫做任务调度器,将 CPU 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 CPU 在线程间的切换非常快,人类感觉是同时运行的 。总结为一句话就是:微观串行,宏观并行,一般会将这种线程轮流使用 CPU 的做法称为并发(Concurrent)。

多核 CPU下,每个核都可以调度运行线程,这时候线程可以是并行(Parallel)的。

同步与异步:

以调用方角度来讲:

  • 需要等待结果返回后才能继续运行就是同步。
  • 不需要等待结果返回就能继续运行就是异步。

7.2、线程创建方式 

  • 方式一:
Thread thread = new Thread(){
    @Override
    public void run() {
        System.out.println("thread run ...");
    }
};

thread.start();

      简化后:

Thread thread = new Thread(() -> System.out.println("thread run ..."));

thread.start();
  • 方式二:
Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("runnable run ...");
    }
});

thread.start();

      简化后:

Thread thread = new Thread(() -> System.out.println("runnable run ..."));

thread.start();
  • 方式三:
Callable<Integer> callable = new Callable() {
    @Override
    public Object call() throws Exception {
        System.out.println("callable run ...");
        return 521;
    }
};
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();

      简化后:

Thread thread = new Thread(new FutureTask(() -> {
    System.out.println("callable run ...");
    return 521;
}));

thread.start();

7.3、线程基本方法

注意:标黄色的方法代表是 static 方法,可直接类名调用,无需创建对象。

 

 7.4、线程安全问题

两个线程对初始值为 0 的静态变量一个做自增,一个做自减,各做 5000 次,结果是 0 吗?

public class Main {
    static int counter = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter++;
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                counter--;
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

以上的结果可能是正数、负数、零。

一个程序运行多个线程本身是没有问题的,问题出现在多个线程访问共享资源:多个线程读共享资源其实也没有问题,问题本质在于多个线程对共享资源读写操作时发生指令交错,就会出现问题。

一段代码块内,如果存在对共享资源的多线程读写操作,称这段代码块为临界区(Critical Section)。

例如,下面代码中的临界区:
 

static int counter = 0;

static void increment()
// 临界区
{
	counter++;
}

static void decrement()
// 临界区
{
	counter--;
}

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件(Race Condition)。

为了避免临界区的竞态条件发生,有多种手段可以达到目的。

  • 阻塞式的解决方案:synchronized,Lock
  • 非阻塞式的解决方案:原子变量

我们使用阻塞式的解决方案:synchronized,来解决上述问题,即俗称的【对象锁】,它采用互斥的方式让同一时刻至多只有一个线程能持有【对象锁】,其它线程再想获取这个【对象锁】时就会阻塞住。这样就能保证拥有锁的线程可以安全的执行临界区内的代码,不用担心线程上下文切换,以此避免线程安全问题。

虽然 Java 中互斥和同步都可以采用 synchronized 关键字来完成,但它们还是有区别的:

  • 互斥是保证临界区的竞态条件发生,同一时刻只能有一个线程执行临界区代码
  • 同步是由于线程执行的先后、顺序不同、需要一个线程等待其它线程运行到某个点

synchronized的语法:
 

synchronized(对象)
{
	临界区
}

synchronized改进后:

public class Main {
    static int counter = 0;
    static final Object room = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter++;
                }
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (room) {
                    counter--;
                }
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(counter);
    }
}

synchronized 实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

方法上的 synchronized

class Test{
    public synchronized void test() {
    }
}
等价于
class Test{
    public void test() {
        synchronized(this) {
        }
    }
}
class Test{
    public synchronized static void test() {
    }
}
等价于
class Test{
    public static void test() {
        synchronized(Test.class) {
        }
    }
}

成员变量和静态变量是否线程安全?

  • 如果它们没有共享,则线程安全
  • 如果它们被共享了,根据它们的状态是否能够改变,又分两种情况

       1、   如果只有读操作,则线程安全
        2、  如果有读写操作,则这段代码是临界区,需要考虑线程安全


方法上(内)局部变量是否线程安全?

  • 局部变量是线程安全的
  • 但局部变量引用的对象则未必

        1、 如果该对象没有逃离方法的作用访问,它是线程安全的
        2、 如果该对象逃离方法的作用范围,需要考虑线程安全

7.5、线程并发工具

7.5.1、ReentrantLock

锁介绍

ReentrantLock 相对于 synchronized,它具备如下特点:

  • 可中断
  • 可以设置超时时间
  • 可以设置为公平锁
  • 支持多个条件变量
  • 与 synchronized 一样,都支持可重入

基本语法:

// 创建锁
ReentrantLock reentrantLock = new ReentrantLock();
// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}

可重入

可重入是指同一个线程,如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住。

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    public static void method1() {
        lock.lock();
        try {
            System.out.println("execute method1");
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        lock.lock();
        try {
            System.out.println("execute method2");
            method3();
        } finally {
            lock.unlock();
        }
    }

    public static void method3() {
        lock.lock();
        try {
            System.out.println("execute method3");
        } finally {
            lock.unlock();
        }
    }
}
execute method1
execute method2
execute method3

可打断

ReentrantLock 支持可打断,如果是不可中断模式,即使使用了 interrupt 也不会让等待中断。

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1启动...");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("t1等锁的过程中被打断");
                return;
            }
            try {
                System.out.println("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("main获得了锁");
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
            System.out.println("main执行打断");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
main执行打断
t1等锁的过程中被打断
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.company.Main.lambda$main$0(Main.java:12)

锁超时

public class Main {
    public static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("t1启动...");
            if (!lock.tryLock()) {
                System.out.println("t1获取失败,返回");
                return;
            }
            try {
                System.out.println("t1获得了锁");
            } finally {
                lock.unlock();
            }
        }, "t1");
        lock.lock();
        System.out.println("main获得了锁");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
main获得了锁
t1启动...
t1获取失败,返回

公平锁

ReentrantLock 默认是不公平的,改为公平锁语法如下,公平锁一般没有必要,会降低并发度。

ReentrantLock lock = new ReentrantLock(true);

多条件

synchronized 中也有条件变量,就是 waitSet 休息室,当条件不满足时进入 waitSet 等待,ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息,而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒。

使用要点:

  • await 前需要获得锁
  • await 执行后,会释放锁,进入 conditionObject 等待
  • await 的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
  • 竞争 lock 锁成功后,从 await 后继续执行
     
public class Main {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition waitCigaretteQueue = lock.newCondition();
    public static Condition waitbreakfastQueue = lock.newCondition();
    public static volatile boolean hasCigrette = false;
    public static volatile boolean hasBreakfast = false;

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasCigrette) {
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("小明等到了它的烟");
            } finally {
                lock.unlock();
            }
        },"小明").start();
        new Thread(() -> {
            try {
                lock.lock();
                while (!hasBreakfast) {
                    try {
                        waitbreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("小红等到了它的早餐");
            } finally {
                lock.unlock();
            }
        },"小红").start();
        Thread.sleep(1000);
        sendBreakfast();
        Thread.sleep(1000);
        sendCigarette();
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            System.out.println("送烟来了");
            hasCigrette = true;
            waitCigaretteQueue.signal();
        } finally {
            lock.unlock();
        }
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            System.out.println("送早餐来了");
            hasBreakfast = true;
            waitbreakfastQueue.signal();
        } finally {
            lock.unlock();
        }
    }
}

小测验

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现?

class AwaitSignal extends ReentrantLock {
    private int loopNumber;// 循环次数

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void start(Condition first) {
        this.lock();
        try {
            first.signal();
        } finally {
            this.unlock();
        }
    }

    public void print(String str, Condition current, Condition next) {
        for (int i = 0; i < loopNumber; i++) {
            this.lock();
            try {
                current.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                this.unlock();
            }
        }
    }
}

public class Main {
    public static void main(String[] args) {
        AwaitSignal as = new AwaitSignal(5);
        Condition aWaitSet = as.newCondition();
        Condition bWaitSet = as.newCondition();
        Condition cWaitSet = as.newCondition();
        new Thread(() -> {
            as.print("a", aWaitSet, bWaitSet);
        }).start();
        new Thread(() -> {
            as.print("b", bWaitSet, cWaitSet);
        }).start();
        new Thread(() -> {
            as.print("c", cWaitSet, aWaitSet);
        }).start();
        as.start(aWaitSet);
    }
}
abcabcabcabcabc

7.5.2、ReentrantReadWriteLock

当读操作远远高于写操作时,这时候使用 读写锁 让 读-读 可以并发,提高性能。

加解读锁

rw.readLock().lock();
rw.readLock().unlock();

加解写锁

rw.writeLock().lock();
rw.writeLock().unlock();

7.5.3、StampedLock

该类自 JDK 8 加入,是为了进一步优化读性能,它的特点是在使用读锁、写锁时都必须配合【戳】使用。

加解读锁

long stamp = lock.readLock();
lock.unlockRead(stamp);

加解写锁

long stamp = lock.writeLock();
lock.unlockWrite(stamp);

7.5.4、Semaphore

Semaphore信号量,用来限制能同时访问共享资源的线程上限。

场景:我们现在有6个线程,但是我们每次要求只有2个线程运行,这时候可以使用信号量,来限制能同时访问共享资源的线程上限。

7.5.5、CountDownLatch

CountdownLatch用来进行线程同步协作,等待所有线程完成倒计时。

其中构造参数用来初始化等待计数值,await() 用来等待计数归零,countDown() 用来让计数减一。

7.5.6、CyclicBarrier


CyclicBarrier循环栅栏,用来进行线程同步协作,等待线程满足某个计数。

构造时设置『计数个数』,每个线程执行到某个需要“同步”的时刻调用 await() 方法进行等待,当等待的线程数满足『计数个数』时,继续执行。他可以用于多线程计算数据,最后合并计算结果的场景。CountDownLatch 是一次性的,CyclicBarrier 是可循环利用的。
 

7.5.7、ThreadLocal


ThreadLocal(线程局部变量),在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况,ThreadLocal就比较好的解决了这个问题。


7.6线程组的创建

ThreadGroup是Java提供的一种对线程进行分组管理的手段,可以对所有线程以组为单位进行操作,如设置优先级、守护线程等。

class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int x = 0; x < 5; x++) {
            System.out.println(Thread.currentThread().getThreadGroup() + ":" + Thread.currentThread().getName() + ":" + x);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // ThreadGroup(String name)
        ThreadGroup tg = new ThreadGroup("线程组");
        // Thread(ThreadGroup group, Runnable target, String name)
        Thread t1 = new Thread(tg, new MyRunnable(), "张三");
        Thread t2 = new Thread(tg, new MyRunnable(), "李四");
        // 获取组名并在控制台输出
        System.out.println("t1的线程组名称:" + t1.getThreadGroup().getName());
        System.out.println("t2的线程组名称:" + t2.getThreadGroup().getName());
        // 表示该组线程是后台线程
        tg.setDaemon(true);
        // 表示该组线程的优先级5
        tg.setMaxPriority(5);
        // 启动线程组中的所有线程
        t1.start();
        t2.start();
        // 打印该组活跃线程的信息
        tg.list();
    }
}

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

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

相关文章

cobaltstrike的安装与基础使用

数据来源 本文仅用于信息安全学习&#xff0c;请遵守相关法律法规&#xff0c;严禁用于非法途径。若观众因此作出任何危害网络安全的行为&#xff0c;后果自负&#xff0c;与本人无关。 Cobalt Strike是什么? Cobalt Strike&#xff08;简称CS&#xff09;是一款团队作战渗…

Python学习笔记——列表

Python列表函数&方法函数&#xff1a;len(list)&#xff1a;列表元素个数max(list)&#xff1a;返回列表元素最大值min(list)&#xff1a;返回列表元素最小值list(seq)&#xff1a;将元组转换为列表方法list.append(obj)&#xff1a;在列表末尾添加新的对象list.count(obj)…

字典树总结

字典树 一、概念 字典树 (Trie) 是一种用于实现字符串快速存储和检索的多叉树结构&#xff1b; Trie 的每个节点都拥有若干个字符指针&#xff0c;若在插入或检索字符串时扫描到一个字符 ccc &#xff0c;就沿着当前节点的 ccc 字符指针&#xff0c;走向该指针指向的节&…

支持向量机(SVM)入门(六,解决遗留问题)

但也出现了如下结果&#xff0c;看来任务没完没了&#xff08;svm深似海&#xff1f;&#xff09;&#xff0c;还得下功夫&#xff1a;前面我们的支持向量机到此便止步了&#xff0c;一晃两年&#xff0c;这几天有空&#xff0c;顺便研究了一下&#xff0c;问题有进展&#xff…

基于yolov5-v7.0开发构建汽车车损实例分割检测识别分析系统

在之前的文章中我们已经做了很多基于yolov5完成实例分割的项目&#xff0c;感兴趣的话可以自行移步阅读&#xff1a;《基于YOLOv5-v7.0的药片污染、缺损裂痕实例分割检测识别分析系统》《基于yolov5-v7.0开发构建裸土实例分割检测识别模型》《基于yolov5-v7.0开发实践实例分割模…

NSThead的进阶使用和简单探讨

概述 NSThread类是一个继承于NSObjct类的轻量级类。一个NSThread对象就代表一个线程。它需要管理线程的生命周期、同步、加锁等问题&#xff0c;因此会产生一定的性能开销。 使用NSThread类可以在特定的线程中被调用某个OC方法。当需要执行一个冗长的任务&#xff0c;并且不想…

文件minio进阶 分页查询

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、minio使用二、代码1.mino版本是最新的,那么pom中也要最新的2. 部分代码总结前言 就是现在通过minio管理文件,然后不需要其他信息,所以我也就没有用传统方式…

探究视频标注如何支持自动驾驶汽车

自动驾驶汽车技术有望使我们的道路更安全&#xff0c;同时提高运输和交付服务的效率。安全性和可靠性是自动驾驶汽车广泛采用所必需的关键因素。为了保证汽车在现实复杂的交通条件下导航能够有效运行&#xff0c;必须使用标注数据来训练基于计算机视觉的模型&#xff0c;这些数…

verilog学习笔记- 10)按键控制 LED 灯实验

目录 简介&#xff1a; 实验任务&#xff1a; 硬件设计&#xff1a; 程序设计&#xff1a; 下载验证 &#xff1a; 总结与反思&#xff1a; 简介&#xff1a; 按键开关是一种电子开关&#xff0c;属于电子元器件类。我们的开发板上有两种按键开关&#xff1a;第一种是本实…

NeRF制作数据集最后的踩坑

之前对数据集渲染的时候一直出现模糊的问题&#xff1a; 学长说可能是摄像机和poses没有对应。但是为什么会这样呢 后来我发现&#xff0c;确实在制作的过程中&#xff0c;某一步出了问题&#xff0c;导致下标的数字&#xff0c;跟理想的不一样&#xff1a; 原来的12&#xf…

基于yolov5-v7.0开发构建工业机械齿轮瑕疵实例分割检测识别分析系统

在之前的文章中我们已经做了很多基于yolov5完成实例分割的项目&#xff0c;感兴趣的话可以自行移步阅读&#xff1a;《基于YOLOv5-v7.0的药片污染、缺损裂痕实例分割检测识别分析系统》《基于yolov5-v7.0开发构建裸土实例分割检测识别模型》《基于yolov5-v7.0开发实践实例分割模…

深度学习PyTorch 之 DNN-回归(多变量)

深度学习&PyTorch 之 DNN-回归中使用HR数据集进行了实现&#xff0c;但是HR数据集中只有一个变量&#xff0c;这里我们使用多变量在进行模拟一下 流程还是跟前面一样 #mermaid-svg-LN8ayy7UjtqZ6dSj {font-family:"trebuchet ms",verdana,arial,sans-serif;font…

计算机原理三_进程管理

目录儿四、进程管理4.1 什么是进程4.1.1 进程的结构4.1.2 进程的特征4.1.3 进程与线程4.1.4 线程的实现方式用户级线程内核支持线程组合线程的调度4.2 进程是怎么运行的4.2.1 进程状态4.2.2 进程控制4.2.2.1 原语的概念4.2.2.2 挂起与激活4.2.3 进程调度4.2.3.1 调度层次4.2.3.…

CSS入门一、初识

零、文章目录 文章地址 个人博客-CSDN地址&#xff1a;https://blog.csdn.net/liyou123456789个人博客-GiteePages&#xff1a;https://bluecusliyou.gitee.io/techlearn 代码仓库地址 Gitee&#xff1a;https://gitee.com/bluecusliyou/TechLearnGithub&#xff1a;https:…

【BP靶场portswigger-服务端9】服务端请求伪造SSRF漏洞-7个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

Episode 02 对称密码基础

一、从文字密码到比特序列密码 1、使用对称密钥进行加密 为了使原来的明文无法被推测出来&#xff0c;就要尽可能地打乱密文&#xff0c;这样才能达到加密的目的。密文打乱的是比特序列&#xff0c;无论是文本&#xff0c;图片还是音乐&#xff0c;只要能够将数据转换比特序列…

MSF后渗透持续后门

持续后门 ○ 利用漏洞取得的meterpreter shell运行于内存中&#xff0c;重启失效 ○ 重复exploit漏洞可能造成服务崩溃 ○ 持久后门保证漏洞修复后仍可远程控制 Meterpreter后门 run metsvc -A #删除-r use exploit/multi/handler set PAYLOAD windows/metsvc_bind_tcp se…

[22]. 括号生成

[22]. 括号生成题目算法设计&#xff1a;回溯算法设计&#xff1a;空间换时间题目 传送门&#xff1a;https://leetcode.cn/problems/generate-parentheses/ 算法设计&#xff1a;回溯 括号问题可以分成俩类&#xff1a; 括号的合法性判断&#xff0c;主要是用栈括号的合法生…

【自然语言处理】Word2Vec 词向量模型详解 + Python代码实战

文章目录一、词向量引入二、词向量模型三、训练数据构建四、不同模型对比4.1 CBOW4.2 Skip-gram 模型4.3 CBOW 和 Skip-gram 对比五、词向量训练过程5.1 初始化词向量矩阵5.2 训练模型六、Python 代码实战6.1 Model6.2 DataSet6.3 Main6.4 运行输出一、词向量引入 先来考虑一个…

IDEA远程快速部署SpringBoot项目到Docker环境

1.LInux上先安装docker环境 https://blog.csdn.net/YXWik/article/details/128643662 2.配置Docker远程连接端口 1. vim /usr/lib/systemd/system/docker.service 2. 找到ExecStar 在后面添加 -H tcp://0.0.0.0:2375 3. 退出编辑界面&#xff1a;先按esc&#xff0c;然后"…