java 实战 多线程无法实现的问题

我已经解决了冲突问题,但是我发现多线程并没有缩短时间啊,和我设想的不一样呢?
单线程和双线程、四线程运行的时间都一样,想哭了!
我的目标是5s左右识别一张1920*1080大小的图片,这样我才能用到识别结果,否则光是识别图片就用了20来秒太慢了。

img

img

我在学习多线程时仿照例子把单线程改为多线程,却始终无法成功。
在示例中是可以实现多线程结果共享的,但是在实战例子中就不行了。小女子刚学多线程,求指点一下具体怎么改。
以下是例子:


class  hello implements  Runnable {
    public  void  run() {
        for  ( int  i = 0 ; i < 7 ; i++) {
            if  (count > 0 ) {
                System.out.println( Thread.currentThread().getName() + "count= "  + count--);
            }
        }
    }

    public  static  void  main(String[] args) {
        hello he= new  hello();
        new  Thread(he, "A").start();
        new  Thread(he, "B").start();

    }

    private  int  count = 5 ;
}

例子结果:

img

这是我在修改的实战例子:


public class imageUrl implements  Runnable {

    public void run(){
        BytePointer outtext;

        TessBaseAPI api = new TessBaseAPI();

        api.Init("tessdata", "chi_sim") ;//chi_sim在tessdata中位置

        File file = new File("tupian/3.png");//图片地址

        PIX image = pixRead(file.getAbsolutePath());

        api.SetImage(image);

        int[] blockIds = {};

        long starttime = System.currentTimeMillis();

        BOXA boxes = api.GetComponentImages(RIL_WORD, true, null, blockIds);


        for (int i = 0; i < boxes.n(); i++) {

            BOX box = boxes.box(i);

            api.SetRectangle(box.x(), box.y(), box.w(), box.h());

            outtext = api.GetUTF8Text();

            String ocrresult = outtext.getString();
            //去空格
            ocrresult = ocrresult.replace(" ","");

            int conf = api.MeanTextConf();

            String boxinformation = String.format("Box[%d]:x=%d,y=%d,w=%d,h=%d,confidence:%d,text:%s", i, box.x(),
                    box.y(), box.w(), box.h(), conf, ocrresult);
            System.out.println(boxinformation);

            outtext.deallocate();
        }
        api.End();
        pixDestroy(image);
        long invertaltime = System.currentTimeMillis() - starttime;
        System.out.println("识别用时:" + invertaltime);
    }


    public static void main(String[] args) {

        imageUrl ds1 = new imageUrl();

        imageUrl ds2 = new imageUrl();

        new  Thread(ds1,"A").start();

        new  Thread(ds2,"B").start();

    }

}

这是实例运行结果:

img

这个结果不是我想要的,我想要的是BOX识别多线程共享,而不是识别两次。

根据你的代码进行修改的

import org.bytedeco.javacpp.BytePointer;
import org.bytedeco.leptonica.BOX;
import org.bytedeco.leptonica.BOXA;
import org.bytedeco.leptonica.PIX;
import org.bytedeco.tesseract.TessBaseAPI;

import java.io.File;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;

import static org.bytedeco.leptonica.global.lept.pixDestroy;
import static org.bytedeco.leptonica.global.lept.pixRead;
import static org.bytedeco.tesseract.global.tesseract.RIL_WORD;

public class imageUrl implements  Runnable {
    private AtomicInteger index = new AtomicInteger(0);
    private  BOXA boxes;
    private  TessBaseAPI api;
    private CountDownLatch latch;

    public  AtomicBoolean flag = new AtomicBoolean(true);
    public imageUrl(){}
    public imageUrl(CountDownLatch latch){
        this.latch = latch;
        init();
    }

    /**
     *  初始化处理
     */
    public void init(){
        api = new TessBaseAPI();
        //chi_sim在tessdata中位置
        api.Init("tessdata", "chi_sim") ;
        //图片地址
        File file = new File("tupian/3.png");
        PIX image = pixRead(file.getAbsolutePath());
        api.SetImage(image);
        int[] blockIds = {};
        boxes = api.GetComponentImages(RIL_WORD, true, null, blockIds);
    }

    /**
     *  执行任务
     */
    @Override
    public void run(){
        long startTime = System.currentTimeMillis() ;
        int num = 0;
        //cas 原子操作
        while ( (num =index.getAndAdd(1)) < boxes.n()) {
            BOX box = boxes.box(index.get());
            if (box == null){
                continue;
            }
            int conf;
            String ocrresult;
            synchronized(imageUrl.class){
                api.SetRectangle(box.x(), box.y(), box.w(), box.h());
                BytePointer outtext = api.GetUTF8Text();
                conf = api.MeanTextConf();
                ocrresult = outtext.getString();
                outtext.deallocate();
            }
            
            //去空格
            ocrresult = ocrresult.replace(" ","");
            String boxinformation = String.format("Box[%d]:x=%d,y=%d,w=%d,h=%d,confidence:%d,text:%s", num, box.x(),
                    box.y(), box.w(), box.h(), conf, ocrresult);
            System.out.println(boxinformation);

        }

        //结束 防止重复结束
        if (flag.getAndSet(false)){
            end();
        }
        long totalTime = System.currentTimeMillis() - startTime;
        System.out.println("====》识别用时:" + totalTime);
        latch.countDown();
    }

    /**
     *  结束处理
     */
    public void end(){
        api.End();
        PIX image = new PIX();
        pixDestroy(image);
        System.out.println("结束释放资源");
    }


}

class Test{
    public static void main(String[] args) throws InterruptedException {
       CountDownLatch latch = new CountDownLatch(2);
        imageUrl ds = new imageUrl(latch);
        new  Thread(ds,"A").start();
        new  Thread(ds,"B").start();
        latch.await();
    }
}

如果是为了提升识别速度,可以做数据切割识别,多个线程分不同的起点开始识别,最后可以合并识别结果,


public static void main(String[] args) throws ExecutionException, InterruptedException {
        long l = System.currentTimeMillis();
        // 切分数
        int submitCount = 3;
        ExecutorService executorService = Executors.newFixedThreadPool(submitCount);
        List<Future<List<String>>> futures = new ArrayList<>();
        for (int i = 0; i < submitCount; i++) {
            int finalI = i;
            Future<List<String>> submit = executorService.submit(new Callable<List<String>>() {
                @Override
                public List<String> call() throws Exception {
                    TessBaseAPI api = new TessBaseAPI();
                    api.Init("tessdata", "chi_sim");//chi_sim在tessdata中位置
                    File file = new File("tupian/3.png");//图片地址
                    PIX image2 = pixRead(file.getAbsolutePath());
                    api.SetImage(image2);
                    int[] blockIds = {};
                    BOXA boxes = api.GetComponentImages(RIL_WORD, true, null, blockIds);
                    int nalloc = boxes.nalloc();
                    List<String> list = new ArrayList<>();
                    int index = 0;
                    while (index < nalloc / submitCount) {
                        BOX box = boxes.box(nalloc / submitCount * finalI + index);
                        api.SetRectangle(box.x(), box.y(), box.w(), box.h());
                        BytePointer outtext = api.GetUTF8Text();
                        String ocrresult = outtext.getString();
                        //去空格
                        ocrresult = ocrresult.replace(" ", "");
                        int conf = api.MeanTextConf();
                        String boxinformation = String.format("Box[%d]:x=%d,y=%d,w=%d,h=%d,confidence:%d,text:%s", index, box.x(),
                                box.y(), box.w(), box.h(), conf, ocrresult);
                        list.add(boxinformation);
                        outtext.deallocate();
                        index++;
                    }
                    api.End();
                    PIX image = new PIX();
                    pixDestroy(image);
                    return list;
                }
            });
            futures.add(submit);
        }
        for (Future<List<String>> future : futures) {
            List<String> list1 = future.get();
            for (String s : list1) {
                System.out.println(s);
            }
        }
        executorService.shutdown();
        System.out.println(System.currentTimeMillis() - l);
    }

可以比较一下下面两段代码,一个是你写的,一个是例子,会发现例子是将同一个对象传给了两个线程,你的例子是将两个对象,传给了两个线程。就相当于,在多线程中处理每个线程各自的任务,而不是将同一个对象让多个线程进行处理。按照这个思路,修改一下你的代码就可以了


 public static void main(String[] args) {
 
        imageUrl ds1 = new imageUrl();
 
        imageUrl ds2 = new imageUrl();
 
        new  Thread(ds1,"A").start();
 
        new  Thread(ds2,"B").start();
 
    }

 public  static  void  main(String[] args) {
        hello he= new  hello();
        new  Thread(he, "A").start();
        new  Thread(he, "B").start();
 
    }


你的两个runnable做的同一件事情,并没有起到该有的作用
你应该把

img


这个放到runnable,其他的读取图片获取box的动作放到主线程,然后两个runnable去操作这个for循环识别的任务

多线程处理循环可以考虑用线程池,?%ra=linkhttps://blog.csdn.net/m0_37729339/article/details/113179192?spm=1001.2014.3001.5502

public static void main(String[] args) {
 
        imageUrl ds1 = new imageUrl();
 
        imageUrl ds2 = new imageUrl();
 
        new  Thread(ds1,"A").start();
 
        new  Thread(ds2,"B").start();
 
    }

就比如你这段代码可以改为

 imageUrl ds1 = new imageUrl();
 ExecutorService executorService = Executors.newFixedThreadPool(10);
 executorService.submit(ds1);

主要是共享资源和线程间协调的问题
以下实现不知道行不行:

public class ImageUrl implements  Runnable {

    ConcurrentLinkedDeque<BOX> deque = new ConcurrentLinkedDeque<>();
    AtomicInteger atomicInteger;
    AtomicInteger timer = new AtomicInteger(0);
    TessBaseAPI api;
    PIX image;

    public ImageUrl(){
        api = new TessBaseAPI();

        api.Init("tessdata", "chi_sim") ;//chi_sim在tessdata中位置

        File file = new File("tupian/3.png");//图片地址

        image = pixRead(file.getAbsolutePath());

        api.SetImage(image);

        int[] blockIds = {};

        BOXA boxes = api.GetComponentImages(RIL_WORD, true, null, blockIds);

        for (int i = 0; i < boxes.n(); i++) {
            BOX box = boxes.box(i);
            deque.add(box);
        }
        atomicInteger = new AtomicInteger(blockIds.length);
    }

    public void run(){
        BytePointer outtext;
        long starttime = System.currentTimeMillis();

        while (true) {
            int i = 0;
            synchronized (Object.class) {
                BOX box = deque.pop();
                if (box == null && atomicInteger.get() == 0) {
                    api.End();
                    pixDestroy(image);
                    break;
                }
                if (box == null) {
                    continue;
                }
                i = atomicInteger.decrementAndGet();
            }
            
            api.SetRectangle(box.x(), box.y(), box.w(), box.h());

            outtext = api.GetUTF8Text();

            String ocrresult = outtext.getString();
            //去空格
            ocrresult = ocrresult.replace(" ","");

            int conf = api.MeanTextConf();

            String boxinformation = String.format("Box[%d]:x=%d,y=%d,w=%d,h=%d,confidence:%d,text:%s", i, box.x(),
                    box.y(), box.w(), box.h(), conf, ocrresult);
            System.out.println(boxinformation);

            outtext.deallocate();
        }
        long invertaltime = System.currentTimeMillis() - starttime;
        timer.addAndGet((int) invertaltime);
    }


    public static void main(String[] args) {

        ImageUrl ds = new ImageUrl();

        new  Thread(ds,"A").start();

        new  Thread(ds,"B").start();

        System.out.println("识别用时:" + ds.timer.get());

    }

}

我把整个idea项目压缩包放在网盘里了可以帮我看看具体怎么回事吗?完整的追赏[](链接:https://pan.baidu.com/s/1NITCu74S7vsGUNWq4qog8w
提取码:0000)

img


想楼上说的,你要做多线程共享变量,但是你这new了2个实例,每个线程都是操作的不同的对象;相当于2个人互不干扰的干了2件一模一样的事

我也不会,还没学到多线程,我倒是仔细看了一下实例,应该是把for那段代码run()吧,而不是整体run()了,你的代码相当于把图片识别整体分入两个线程跑了两遍。得到两次结果也就不怪了

有没有想过,多线程包的是分支任务,而不是整个工程。
区别是这样的:
多个线程干一件事,每个人都会干一遍。
多个线程分发一堆事,各自干各自的部分。这也是楼主的目标。

多线程一般需要配合锁来实现相关的共享的,学习锁相关的知识会对你的进一步理解多线程是非常有帮助的