JMM与JUC

news2024/12/26 11:06:57

1.JMM

问题1:请你谈谈你对Volatile的理解

Volatile 是java虚拟机提供轻量级的同步机制

1. 保证可见性

2. 不保证原子性

3. 禁止指令重排

1.1、什么是JMM

JMM Java内存模型  不存在的东西,概念!约定 !

1.2、关于JMM的一些同步的约定:

1、线程解锁前,必须把共享变量立刻刷回主存,

2、线程枷锁前,必须读取主存中的最新值到工作内存中

3、加锁和解锁的是同一把锁

线程工作内存,主内存。store和write换一个位置

2.Volatile

2.1.保证可见性

package com.kuang.tvolatile;

import java.util.concurrent.TimeUnit;

public class Demo01 {
    //不加volatile ,程序就会陷入死循环,加了保证主内存的可见性
    public volatile static int num=0;

    public static void main(String[] args) {
        new Thread(()->{//线程对主内存的num值的变化是不知道的
            while (num==0){

            }
            System.out.println(num);
        }).start();


        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num=1;
        System.out.println(num);

    }
}

2.2不保证原子性

package com.kuang.tvolatile;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//不保证原子性
public class Demo02 {
  //volatile不保证原子性
    private volatile static int num=0;

//改变就会重新加载主存的值,不改变就存一次到工作内存,工作内存相当于线程私有的栈帧,先这么理解
    public  static void  add(){
            num++;
    }
//    public   void  add1(){
//        lock.lock();
//        try {
//            num++;
//        } catch (Exception e) {
//            e.printStackTrace();
//        } finally {
//            lock.unlock();
//        }
//    }

    public static void main(String[] args) {
        //理论上num结果应该为两万
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
//        Demo02 demo02 = new Demo02();
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                for (int j = 0; j < 1000; j++) {
//                  demo02.add1();
//                }
//            }).start();
//        }

        while (Thread.activeCount()>2){//main gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+"   "+num);

    }



}

如果不加lock和synchronized,怎么样保证原子性?

使用原子类,解决原子性问题

package com.kuang.tvolatile;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//不保证原子性
public class Demo02 {
  //volatile不保证原子性
//    private volatile static int num=0;
  public volatile static AtomicInteger num=new AtomicInteger();
//改变就会重新加载主存的值,不改变就存一次到工作内存,工作内存相当于线程私有的栈帧,先这么理解
    public  static void  add(){
            num.getAndIncrement();//AtomicInterger+1方法,CAS
    }
//    public   void  add1(){
//        lock.lock();
//        try {
//            num++;
//        } catch (Exception e) {
//            e.printStackTrace();
//        } finally {
//            lock.unlock();
//        }
//    }

    public static void main(String[] args) {
        //理论上num结果应该为两万
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
//        Demo02 demo02 = new Demo02();
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                for (int j = 0; j < 1000; j++) {
//                  demo02.add1();
//                }
//            }).start();
//        }

        while (Thread.activeCount()>2){//main gc
            Thread.yield();//Thread.yield() 方法是Java中的一个静态方法,用于提示调度程序当前线程愿意放弃其当前时间片。
            // 换句话说,它建议线程愿意暂停执行并为同一优先级的其他线程提供机会。
        }

        System.out.println(Thread.currentThread().getName()+"   "+num);

    }



}

这些类的底层都直接和操作系统直接挂钩 !在内存中修改值!!!Unsafe类是一个很特殊的存在!

2.3 、指令重排

什么是  指令重排: 你写的程序,计算机并不是按照你写的那样去执行的。

源代码->编译器优化的重排-->指令并行也可能会重排-->内存系统也会重排-->执行

volatile 是可以保持可见性,不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

问:volatile在哪里避免指令重排的现象用的最多,在单例模式!!!

3.彻底玩转单例模式

饿汉式,DCL懒汉式

饿汉式

package com.kuang.single;

//饿汉式
public class Hungry {

    private byte[] bytes1=new byte[1024*1024];
    private byte[] bytes2=new byte[1024*1024];
    private byte[] bytes3=new byte[1024*1024];
    private byte[] bytes4=new byte[1024*1024];


    private Hungry(){

    }

    private final static Hungry HUNGRY= new Hungry();


    public static  Hungry getHungry(){
        return HUNGRY;
    }

}

DCL懒汉式 DCL:双重检测模式

原子性
package com.kuang.single;
//懒汉式
public class LazyMan {
    private LazyMan(){
        System.out.println(Thread.currentThread().getName()+"ok");
    }

    //双重检测锁,volatile内存屏障,防止指令重排,
    private volatile static LazyMan lazyMan;

    //双重检测该模式的 懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan=new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 2和3的顺序,在指令重排的时候
                     */
                }
                //外面那一层判断空 是因为可以减少同步的时间,不会全部同步在锁当中,而一部分代码会因为不为空而直接获取对象。


            }
        }
        return lazyMan;
    }

    //多线程并发
    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            new Thread(()->{
                LazyMan.getInstance();
            }).start();
        }
    }




}

静态内部类

package com.kuang.single;
//静态内部类
public class Holder {
    private Holder(){
        
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
            private static final Holder HOLDER=new Holder();
    }




}

进阶

package com.kuang.single;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

//懒汉式
//道高一尺魔高一丈
public class LazyMan {

    private static boolean qingjiang=false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (qingjiang==false){
                qingjiang=true;
            }else {
                throw  new RuntimeException("不要尝试反射破坏单例模式");
            }


        }

    }

    //双重检测锁,原子性,volatile内存屏障,防止指令重排,
    private volatile static LazyMan lazyMan;

    //双重检测该模式的 懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan=new LazyMan();//不是一个原子性操作
                    /**
                     * 1.分配内存空间
                     * 2.执行构造方法,初始化对象
                     * 3.把这个对象指向这个空间
                     *
                     * 2和3的顺序,在指令重排的时候
                     */
                }
                //外面那一层判断空 是因为可以减少同步的时间,不会全部同步在锁当中,而一部分代码会因为不为空而直接获取对象。


            }
        }
        return lazyMan;
    }

    //多线程并发
//    public static void main(String[] args) {
//        for (int i = 0; i < 10; i++) {
//            new Thread(()->{
//                LazyMan.getInstance();
//            }).start();
//        }
//    }

    public static void main(String[] args) throws Exception {
//        LazyMan instance = LazyMan.getInstance();

        Field qingjiang = LazyMan.class.getDeclaredField("qingjiang");
        qingjiang.setAccessible(true);

        Constructor<? extends LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan instance2 = constructor.newInstance();
       qingjiang.set(constructor,false);
        LazyMan instance = constructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);


    }


}

 

 

 

 反射无法破坏枚举

枚举在运行时没有无参构造,但是有一个有参构造,反射拿无参,会抛没有更多的方法

反射拿有参构造,则会抛出,

throw new IllegalArgumentException("Cannot reflectively create enum objects");

 

4.深入理解CAS

Unsafe类

 

5.原子引用

ABA问题

package com.kuang.cas;

import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {


    //CAS   atomicInteger.compareAndSet()  CAS就是compareAndSet的缩写:比较并交换!
    public static void main(String[] args){
        AtomicInteger atomicInteger=new AtomicInteger(2020);

        //对于我们平时写的SQL:乐观锁!

        
        //expect期望 update更新
        //public final boolean compareAndSet(int expect, int update)
        //如果我期望的值达到了,那么就更新,否则,就不更新,CAS 是CPU的并发原语!
        //捣乱的线程
        System.out.println(atomicInteger.compareAndSet(2020, 2021));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2021, 2020));
        System.out.println(atomicInteger.get());

//        atomicInteger.getAndIncrement();

        //期望的线程
        System.out.println(atomicInteger.compareAndSet(2020, 6666));
        System.out.println(atomicInteger.get());
    }



}

6.各种锁的理解

 

package com.kuang.cas;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo {


    //解决ABA问题,引入原子引用
    //CAS   atomicInteger.compareAndSet()  CAS就是compareAndSet的缩写:比较并交换!
    public static void main(String[] args){
//        AtomicInteger atomicInteger=new AtomicInteger(2020);
        AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1, 1);

        new Thread(()->{
            int stamp = atomic.getStamp();//获取当前版本号
            System.out.println("a1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a2=>"+atomic.compareAndSet(1, 2, atomic.getStamp(), atomic.getStamp() + 1));
            System.out.println("a3=>"+atomic.compareAndSet(2, 1, atomic.getStamp(), atomic.getStamp() + 1));


            System.out.println("a4=>"+atomic.getStamp());
        },"a").start();


        //乐观锁原理是一样的

        new Thread(()->{
            int stamp = atomic.getStamp();//获取当前版本号
            System.out.println("b1=>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("b1=>"+(atomic.compareAndSet(1, 6, stamp, stamp + 1)));


            System.out.println("b2=>"+atomic.getStamp());

        },"b").start();







        //
//        //对于我们平时写的SQL:乐观锁!
//
//
//        //expect期望 update更新
//        //public final boolean compareAndSet(int expect, int update)
//        //如果我期望的值达到了,那么就更新,否则,就不更新,CAS 是CPU的并发原语!
//        //捣乱的线程
//        System.out.println(atomicInteger.compareAndSet(2020, 2021));
//        System.out.println(atomicInteger.get());
//
//        System.out.println(atomicInteger.compareAndSet(2021, 2020));
//        System.out.println(atomicInteger.get());
//
        atomicInteger.getAndIncrement();
//
//        //期望的线程
//        System.out.println(atomicInteger.compareAndSet(2020, 6666));
//        System.out.println(atomicInteger.get());
    }



}

对应的思想就是:乐观锁

6.1、公平锁与非公平锁

6.2、可重入锁

package com.kuang.reelock;

//Synchronized
public class Demo01 {
    public static void main(String[] args) {

        Phone phone = new Phone();

        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone{

    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        call();//这里也有锁

    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");

    }


}

 

package com.kuang.reelock.reelock;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

//Synchronized
public class Demo02{
    public static void main(String[] args) {

        Phone1 phone = new Phone1();

        new Thread(()->{
            phone.sms();
        },"A").start();
        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone1{

    Lock lock =new ReentrantLock();
    public  void sms(){

        lock.lock();
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName()+"sms");
            call();//这里也有锁
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
            lock.unlock();
        }

    }

    public  void call(){
        lock.lock();

        try {
            System.out.println(Thread.currentThread().getName()+"call");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();

        }
    }


}

 

6.3、自旋锁

自定义锁

package com.kuang.spinlock;

import java.util.concurrent.atomic.AtomicReference;

//自旋锁
public class SpinLockDemo {


    /**
     * int 0
     * Thread null
     */
     AtomicReference<Thread> atomicReference=new AtomicReference<>();

    //加锁
    public void lock(){
        Thread thread = Thread.currentThread();

        System.out.println("加锁的是"+thread.getName());

        //第一次进来 为null 成功改变,while条件不成立,不自旋,但是条件没有释放,第二次进来,他不是null了。返回false但是取反为true
        //自旋等待解锁
        while (!atomicReference.compareAndSet(null,thread)){

        }

    }



    //解锁
    public void unlock(){

        Thread thread = Thread.currentThread();
        System.out.println("解锁的是"+thread.getName());
        atomicReference.compareAndSet(thread,null);
    }


    //


}

自定义锁测试

package com.kuang.spinlock;

import java.util.concurrent.TimeUnit;

public class TestDemo {
    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(()->{

            spinLockDemo.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"执行");
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinLockDemo.unlock();
            }


        },"A").start();

        new Thread(()->{

            spinLockDemo.lock();
            try {
                System.out.println(Thread.currentThread().getName()+"执行");
                TimeUnit.SECONDS.sleep(1);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                spinLockDemo.unlock();
            }


        },"B").start();
    }
}

 

6.4、死锁

死锁是多线程或多进程并发执行时可能遇到的一种问题,它具有以下四个必要条件,也被称为死锁的四个特性:

1. 互斥条件(Mutual Exclusion):至少有一个资源被多个线程或进程竞争使用,且一次只能被一个线程或进程占用。当一个线程或进程占用了资源,其他线程或进程必须等待该资源释放。

2. 请求与保持条件(Hold and Wait):线程或进程在持有至少一个资源的同时,又请求其他线程或进程持有的资源。即线程或进程在等待其他资源时,仍然保持已经占有的资源。

3. 不可剥夺条件(No Preemption):已经被一个线程或进程占用的资源不能被其他线程或进程强制性地抢占,只能由占有者主动释放。

4. 循环等待条件(Circular Wait):存在一个线程或进程的资源请求序列,使得每个线程或进程都在等待下一个线程或进程所持有的资源。形成一个闭环,导致循环等待。

当以上四个条件同时满足时,就可能发生死锁。在死锁发生时,线程或进程无法继续执行,导致系统无法正常运行。因此,死锁的四个特性是死锁问题的根本原因,需要通过合理的资源分配和调度策略来避免死锁的发生。

package com.kuang.sisuo;

import java.util.concurrent.TimeUnit;

public class DeadLockDemo {
    public static void main(String[] args) {
        String A="A";
        String B="B";

        new Thread(new MyThread(A,B)).start();
        new Thread(new MyThread(B,A)).start();



    }

}


class MyThread implements Runnable {

    private String A;
    private String B;

    public MyThread(String a, String b) {
        A = a;
        B = b;
    }

    @Override
    public void run() {
        synchronized (A){
            System.out.println(A+"获取"+B);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (B){
                System.out.println(B+"获取"+A);

            }

        }




    }
}

如何解决问题 

栈:jstack

堆:  jprofile

调优 

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

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

相关文章

python使用mitmproxy和mitmdump抓包之拦截和修改包(四)

我认为mitmproxy最强大的地方&#xff0c;就是mitmdump可以结合python代码&#xff0c;灵活拦截和处理数据包。 首先&#xff0c;mitmdump的路径如下&#xff1a;&#xff08;使用pip3 install mitmproxy安装的情况&#xff0c;参考我的文章python使用mitmproxy和mitmdump抓包…

【C++数据结构】二叉树搜索树【完整版】

目录 一、二叉搜索树的定义 二、二叉搜索树的实现&#xff1a; 1、树节点的创建--BSTreeNode 2、二叉搜索树的基本框架--BSTree 3、插入节点--Insert 4、中序遍历--InOrder 5、 查找--Find 6、 删除--erase 完整代码&#xff1a; 三、二叉搜索树的应用 1、key的模型 &a…

Arthas学习(1)

1.Arthas作用 Arthas是Alibaba开源的Java诊断工具。 作用&#xff1a;当遇到以下类似问题时&#xff0c;可以帮助我们解决&#xff1a; 1.这个类从哪个jar包加载的&#xff1f;为什么会报各种类相关的Exception? 2.我改的代码为什么没有执行到&#xff1f;难道是我没提交&am…

浅谈AVL树

文章目录 1.介绍1.1定义1.2来源1.3概念1.特性2.平衡因子[ Balance Factor-- _bf ] 2.BST>AVL1.示例分析2.情况分类3.代码剖析3.1左左型-右单旋3.2右右型-左单旋3.3左右型-左右旋3.4右左型:右左旋3.5总图 3.完整代码3.1AVLTree.h3.2Test.cpp 1.介绍 1.1定义 AVL树 – 平衡二…

CVE-2020-11978 Apache Airflow 命令注入漏洞分析与利用

简介 漏洞软件&#xff1a;Apache Airflow影响版本&#xff1a;< 1.10.10 环境 Vulhub 漏洞测试靶场 复现步骤 进入 /root/vulhub/airflow/CVE-2020-11978/ 目录运行以下命令启动环境 # 初始化数据库 docker compose run airflow-init # 开启服务 docker compose up -…

字符串常量池位于JVM哪里

Java6 和6之前&#xff0c;常量池是存放在方法区&#xff08;永久代&#xff09;中的。Java7&#xff0c;将常量池是存放到了堆中。Java8 之后&#xff0c;取消了整个永久代区域&#xff0c;取而代之的是元空间。运行时常量池和静态常量池存放在元空间中&#xff0c;而字符串常…

web:[ACTF2020 新生赛]Upload

题目 点进页面&#xff0c;是一个文件上传&#xff0c;能联想到getshell 先尝试随便上传一个文件试试 显示上传的文件以jpg、png、gif结尾的图片 构造一句话木马&#xff0c;再将文件后缀改为jpg <?php eval($_POST[1234]);?> 显示上传成功&#xff0c;但是显示无法…

MySQL学习笔记24

MySQL的物理备份&#xff1a; xtrabackup备份介绍&#xff1a; xtrabackup优缺点&#xff1a; 优点&#xff1a; 1、备份过程快速、可靠&#xff08;因为是物理备份&#xff09;&#xff1b;直接拷贝物理文件。 2、支持增量备份&#xff0c;更为灵活&#xff1b; 3、备份…

【数据结构】——顺序表详解

大家好&#xff01;当我们学习了动态内存管理后&#xff0c;就可以写一个管理数据的顺序表了&#xff01;&#xff01;&#xff01; 顺序表的理解&#xff1a; 线性表是最基本、最简单、也是最常用的一种数据结构。线性表&#xff08;linear list&#xff09;是数据结构的一种…

self-attention、transformer、bert理解

参考李宏毅老师的视频 https://www.bilibili.com/video/BV1LP411b7zS?p2&spm_id_frompageDriver&vd_sourcec67a2725ac3ca01c38eb3916d221e708 一个输入&#xff0c;一个输出&#xff0c;未考虑输入之间的关系&#xff01;&#xff01;&#xff01; self-attention…

CSS详细基础(三)复合选择器

前两章介绍了CSS中的基础属性&#xff0c;以及一些基础的选择器&#xff0c;本贴开始介绍复合选择器的内容~ ​ 在 CSS 中&#xff0c;可以根据选择器的类型把选择器分为基础选择器和复合选择器&#xff0c;复合选择器是建立在基础选择器之上&#xff0c;对基本选择器进行组合形…

c语言练习70:反转两次的数字

反转两次的数字 题⽬描述&#xff1a; 反转 ⼀个整数意味着倒置它的所有位。 例如&#xff0c;反转 2021 得到 1202 。反转 12300 得到 321 &#xff0c;不保留前导零 。 给你⼀个整数 num &#xff0c;反转 num 得到 reversed1 &#xff0c;接着反转 reversed1 得到 revers…

使用KEIL自带的仿真器仿真遇到问题解决

*** error 65: access violation at 0x40021000 : no read permission 修改debug选项设置为下方内容。

Java之多线程的生产者消费者问题的详细解析

3.生产者消费者 3.1生产者和消费者模式概述【应用】 概述 生产者消费者模式是一个十分经典的多线程协作的模式&#xff0c;弄懂生产者消费者问题能够让我们对多线程编程的理解更加深刻。 所谓生产者消费者问题&#xff0c;实际上主要是包含了两类线程&#xff1a; 一类是生产者…

MJ 种的摄影提示词关键字

景别 Front view photo 正面照 Front view photo of a Boston Terrier with smileSide view photo 侧身照 Side view photo of a Boston Terrier with smileBack view photo 背影照 Back view photo of a Boston TerrierFull body 全身照 Full body photo of a Boston Ter…

electron之快速上手

前一篇文章已经介绍了如何创建一个electron项目&#xff0c;没有看过的小伙伴可以去实操一下。 接下来给大家介绍一下electron项目的架构是什么样的。 electron之快速上手 electron项目一般有两个进程&#xff1a;主进程和渲染进程。 主进程&#xff1a;整个项目的唯一入口&…

2.物联网射频识别,RFID通信原理,RFID读写器与标签无线交互方式、数据反馈方式,RFID调制与解调、编码方式,不同RFID标签与读写器

一。RFID无线识别的原理 1.RFID系统无线通信基本原理 如下图所示&#xff0c;左边是读写器&#xff08;刷卡器&#xff09;&#xff0c;右边是标签&#xff08;卡&#xff09;&#xff0c;中间通过无线通信方式。 标签&#xff1a;&#xff08;卡&#xff09; 读写器&#xff…

Sound/播放提示音, Haptics/触觉反馈, LocalNotification/本地通知 的使用

1. Sound 播放提示音 1.1 音频文件: tada.mp3&#xff0c; badum.mp3 1.2 文件位置截图: 1.3 实现 import AVKit/// 音频管理器 class SoundManager{// 单例对象 Singletonstatic let instance SoundManager()// 音频播放var player: AVAudioPlayer?enum SoundOption: Stri…

python二维码识别tesseract

window安装tesseract 下载路径&#xff1a; https://digi.bib.uni-mannheim.de/tesseract/ 选择 双击安装在D:\sore\teeseract-OCR后&#xff1a; 配置环境变量 配置环境变量Path&#xff1a;D:\sore\teeseract-OCR 配置语言包的环境变量TESSDATA_PREFIX&#xff1a; D:\s…

搭建自己的搜索引擎之五

一、前言 接上文 搭建自己的搜索引擎之四&#xff0c;下面继续介绍茴香豆茴字的另外两种写法。 二、Jest Jest是ES的Java Http Rest客户端&#xff0c;它主要是为了弥补以前ES自有API缺少HttpRest接口客户端的不足&#xff0c;但因为现在ES官方已经提供了RestClient ,该项目已…