同步错误-ArrayList被多个线程访问

Som*_*Guy 0 java multithreading image-processing thread-safety

这是在java中。我的程序的目的是每秒多次捕获计算机屏幕的屏幕截图,并找到具有特殊红色阴影的所有像素。然后,它找到所有红色像素的平均位置。

为了提高图像处理的效率,我创建了 3 个线程,每个线程处理 1/4 的像素。这些线程加上原始线程将因此处理所有像素。但是,我的 avgLocation() 方法中不断出现错误。这是一个空指针异常,我认为这是因为其他线程正在更改包含所有红色像素的列表的大小,这导致程序访问不存在的像素的数据。为了解决这个问题,我在 Thread2 的代码之后在 Thread1 和 Thread2 上使用了 .join,然后在 Thread3 的代码段之后使用了 .join。因此,在调用 avgLocation 方法之前应该连接所有线程,但每当屏幕上出现特定的红色阴影时,它仍然会给出 NullPointerException。这是堆栈跟踪

Exception in thread "main" java.lang.NullPointerException
    at Images.avgLocation(Images.java:151)
    at Images.processImage(Images.java:133)
    at Images.main(Images.java:169)
Run Code Online (Sandbox Code Playgroud)

151 号线是

                xSum += list.get(i)[0];

Run Code Online (Sandbox Code Playgroud)

第 133 行是这是我的代码:

import java.awt.AWTException;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;
import java.util.ArrayList;

public class Images {
    //takes in an input image and a target color and a bound to check within, returns Point to target
        public static Point processImage(BufferedImage img, Color color, int error) throws InterruptedException  {
            long start = System.nanoTime();
            //bounds to check on color components, make sure all within [0,255]
            int redLower = color.getRed() - error;
            int redHigher =  color.getRed() + error;
            int greenLower = color.getGreen() - error;
            int greenHigher =  color.getGreen() + error;
            int blueLower = color.getBlue() - error;
            int blueHigher =  color.getBlue() + error;
            
            //place all components within [0,255]
            if (redLower < 0) redLower = 0;
            if (redHigher > 255) redHigher = 255;
            if (greenLower < 0) greenLower = 0;
            if (greenHigher > 255) greenHigher = 255;
            if (blueLower < 0) blueLower = 0;
            if (blueHigher > 255) blueHigher = 255;
            
            //create final variables to use for thread
            int redLowerF = redLower;
            int redHigherF =  redHigher;
            int greenLowerF = greenLower;
            int greenHigherF =  greenHigher;
            int blueLowerF = blueLower;
            int blueHigherF =  blueHigher;
            
            //data of image
            int[] pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();
            int width = img.getWidth();
            int height = img.getHeight();
            
            //stores locations
            ArrayList <Integer[]> locations = new ArrayList <>();
            for (int i = 0; i < pixels.length/4; i++) {
                int p = pixels[i];
                              
                // get red
                int r = (p >> 16) & 0xff;
          
                // get green
                int g = (p >> 8) & 0xff;
          
                // get blue
                int b = p & 0xff;
                
                //check if all components within bounds
                if (r >= redLower && r<=redHigher && g>=greenLower && g <= greenHigher && b >= blueLower && b <= blueHigher) {              
                    Integer[] point = {i % height, i/height};
                    locations.add(point);
                }
            }
            Thread thread1 = new Thread( ()  -> {
                for (int i = pixels.length/4; i < pixels.length/2; i++) {
                    int p = pixels[i];
                                  
                    // get red
                    int r = (p >> 16) & 0xff;
              
                    // get green
                    int g = (p >> 8) & 0xff;
              
                    // get blue
                    int b = p & 0xff;
                    
                    //check if all components within bounds
                    if (r >= redLowerF && r<=redHigherF && g>=greenLowerF && g <= greenHigherF && b >= blueLowerF && b <= blueHigherF) {                
                        Integer[] point = {i % height, i/height};
                        locations.add(point);
                    }
                }
            });
            thread1.start();
            Thread thread2 = new Thread( ()  -> {
                for (int i = pixels.length/2; i < pixels.length*3/4; i++) {
                    int p = pixels[i];
                                  
                    // get red
                    int r = (p >> 16) & 0xff;
              
                    // get green
                    int g = (p >> 8) & 0xff;
              
                    // get blue
                    int b = p & 0xff;
                    
                    //check if all components within bounds
                    if (r >= redLowerF && r<=redHigherF && g>=greenLowerF && g <= greenHigherF && b >= blueLowerF && b <= blueHigherF) {                
                        Integer[] point = {i % height, i/height};
                        locations.add(point);
                    }
                }
            });
            thread2.start();
            thread1.join();
            thread2.join();
            Thread thread3 = new Thread( ()  -> {
                for (int i = pixels.length*3/4; i < pixels.length; i++) {
                    int p = pixels[i];
                                  
                    // get red
                    int r = (p >> 16) & 0xff;
              
                    // get green
                    int g = (p >> 8) & 0xff;
              
                    // get blue
                    int b = p & 0xff;
                    
                    //check if all components within bounds
                    if (r >= redLowerF && r<=redHigherF && g>=greenLowerF && g <= greenHigherF && b >= blueLowerF && b <= blueHigherF) {                
                        Integer[] point = {i % height, i/height};
                        locations.add(point);
                    }
                }
            });
            thread3.start();
            thread3.join();
            long end = System.nanoTime();
            System.out.println((end-start)/1000000);
            return avgLocation(locations);  
        }
        //given 2D array of locations, finds average location of set and returns as point
        public static Point avgLocation (ArrayList<Integer[]> list) {
            //if no points in list, return an impossible point on screen
            if (list.size() == 0) {
                return new Point (-100, -100);
            }
            //coordinates of average location (set to 100 to reveal bug easily)
            int avgX = 100;
            int avgY = 100;
            
            int xSum = 0;
            int ySum = 0;
            
            //loop through array
            for (int i = 0; i < list.size(); i++) {
                //for each location, add up coordinates
                xSum += list.get(i)[0];
                ySum += list.get(i)[1];
            }
            avgX = xSum/list.size();
            avgY = ySum/list.size();
            return new Point (avgX, avgY);
        }
        public static void main (String[] args) {
            Robot robot = null;
            try {
                robot = new Robot();
            } catch (AWTException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
            Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
            while (true) {
                try {
                    processImage(robot.createScreenCapture(new Rectangle(0,0,d.width,d.height)), new Color (255, 0, 0), 10);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } 
            }
        }
}
Run Code Online (Sandbox Code Playgroud)

Hol*_*ger 5

好吧,你的线程同步的问题在于你根本没有同步。ArrayList您尝试通过使用避免并发访问join()失败,因为您仍然同时thread1运行thread2。如果您阻止它们同时运行,则任何问题都会ArrayList消失,但是当然,您也不会从使用多个线程中获得任何好处。

\n

现在,您可以使用线程安全实现替换该列表,或者添加手动访问同步,但是\xe2\x80\x99s 不应该这样做。这种同步增加的开销可能大于并发计算的任何潜在收益。毕竟,每个像素执行的算术并不复杂。

\n

相反,您最好让所有线程使用它们自己的列表,它们可以无争用地添加到这些列表中。然后,在两个线程完成其工作后合并列表。

\n

为了使这项任务变得更容易,您应该重构代码,以使用一种具有参数的方法来控制要处理的范围,而不是使用四次相同的代码。

\n

此外,您可以替换Integer[]int[], 以避免将原始值装箱到对象中。

\n
// takes in an input image and a target color and a bound to check within,\n// returns Point to target\npublic static Point processImage(BufferedImage img, Color color, int error)\n    throws InterruptedException, ExecutionException  {\n\n    long start = System.nanoTime();\n    //bounds to check on color components, make sure all within [0,255]\n    int redLower = color.getRed() - error;\n    int redHigher =  color.getRed() + error;\n    int greenLower = color.getGreen() - error;\n    int greenHigher =  color.getGreen() + error;\n    int blueLower = color.getBlue() - error;\n    int blueHigher =  color.getBlue() + error;\n    \n    //place all components within [0,255]\n    if (redLower < 0) redLower = 0;\n    if (redHigher > 255) redHigher = 255;\n    if (greenLower < 0) greenLower = 0;\n    if (greenHigher > 255) greenHigher = 255;\n    if (blueLower < 0) blueLower = 0;\n    if (blueHigher > 255) blueHigher = 255;\n\n    //data of image\n    int[] pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();\n    int width = img.getWidth();\n    int height = img.getHeight();\n    \n    //stores locations\n    ArrayList <int[]> locations = processPixels(pixels, height, 0, pixels.length, 1,\n        redLower, redHigher, greenLower, greenHigher, blueLower, blueHigher);\n    long end = System.nanoTime();\n    System.out.println((end-start)/1000000);\n    return avgLocation(locations);  \n}\nprivate static ArrayList<int[]> processPixels(\n    int[] pixels, int height, int from, int to, int divide,\n    int redLower, int redHigher, int greenLower, int greenHigher,\n    int blueLower, int blueHigher)\n    throws InterruptedException, ExecutionException {\n\n    List<FutureTask<ArrayList<int[]>>> jobs = new ArrayList<>();\n    while(divide > 0 && to - from > 1) {\n        int divideF = --divide;\n        int mid = (from + to) >>> 1;\n        int jobFrom = from, jobTo = mid;\n        FutureTask<ArrayList<int[]>> f = new FutureTask<>(() -> processPixels(\n            pixels, height, jobFrom, jobTo, divideF,\n            redLower, redHigher, greenLower, greenHigher, blueLower, blueHigher));\n        new Thread(f).start();\n        jobs.add(f);\n        from = mid;\n    }\n    ArrayList<int[]> locations = new ArrayList<>();\n    for (int i = from; i < to; i++) {\n        int p = pixels[i];\n        // get red\n        int r = (p >> 16) & 0xff;\n        // get green\n        int g = (p >> 8) & 0xff;\n        // get blue\n        int b = p & 0xff;\n        //check if all components within bounds\n        if (r >= redLower && r<=redHigher && g>=greenLower && g <= greenHigher\n         && b >= blueLower && b <= blueHigher) {              \n            int[] point = {i % height, i/height};\n            locations.add(point);\n        }\n    }\n    for(FutureTask<ArrayList<int[]>> j: jobs) locations.addAll(j.get());\n    return locations;\n}\n//given 2D array of locations, finds average location of set and returns as point\npublic static Point avgLocation(ArrayList<int[]> list) {\n    //if no points in list, return an impossible point on screen\n    if (list.size() == 0) {\n        return new Point (-100, -100);\n    }\n    //coordinates of average location (set to 100 to reveal bug easily)\n    int avgX = 100, avgY = 100;\n    \n    int xSum = 0, ySum = 0;\n    \n    //loop through array\n    for (int i = 0; i < list.size(); i++) {\n        //for each location, add up coordinates\n        xSum += list.get(i)[0];\n        ySum += list.get(i)[1];\n    }\n    avgX = xSum/list.size();\n    avgY = ySum/list.size();\n    return new Point(avgX, avgY);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

它将processPixels完成处理指定范围的工作,但首先divide通过将范围减半并生成新线程来调用相同的方法,同时保留另一半。由于其他线程执行的方法也会根据 来创建新线程divide,因此将有 2 个divide线程在执行该作业。因此,0可以选择单线程、1两线程、2四线程、3八线程,依此类推。

\n

所有线程都将执行其工作,添加到本地列表,然后添加子任务的结果(如果有)。

\n
\n

但请注意,类似的概念已经在 J​​ava 中实现,并且动态适应可用 CPU 核心的数量和实际工作负载。使用时免费获得

\n
// takes in an input image and a target color and a bound to check within,\n// returns Point to target\npublic static Point processImage(BufferedImage img, Color color, int error) {\n    long start = System.nanoTime();\n    //bounds to check on color components, make sure all within [0,255]\n    int redLower = color.getRed() - error;\n    int redHigher =  color.getRed() + error;\n    int greenLower = color.getGreen() - error;\n    int greenHigher =  color.getGreen() + error;\n    int blueLower = color.getBlue() - error;\n    int blueHigher =  color.getBlue() + error;\n    \n    //place all components within [0,255]\n    if (redLower < 0) redLower = 0;\n    if (redHigher > 255) redHigher = 255;\n    if (greenLower < 0) greenLower = 0;\n    if (greenHigher > 255) greenHigher = 255;\n    if (blueLower < 0) blueLower = 0;\n    if (blueHigher > 255) blueHigher = 255;\n\n    //create final variables to use for function\n    int redLowerF = redLower;\n    int redHigherF =  redHigher;\n    int greenLowerF = greenLower;\n    int greenHigherF =  greenHigher;\n    int blueLowerF = blueLower;\n    int blueHigherF =  blueHigher;\n\n    //data of image\n    int[] pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();\n    int width = img.getWidth();\n    int height = img.getHeight();\n\n    List<int[]> locations = IntStream.range(0, pixels.length).parallel()\n        .filter(i -> {\n          int p = pixels[i];\n          // get red\n          int r = (p >> 16) & 0xff;\n          // get green\n          int g = (p >> 8) & 0xff;\n          // get blue\n          int b = p & 0xff;\n          //check if all components within bounds\n          return r >= redLowerF && r<=redHigherF && g>=greenLowerF && g <= greenHigherF\n              && b >= blueLowerF && b <= blueHigherF;\n        })\n        .mapToObj(i -> new int[] {i % height, i / height})\n        .collect(Collectors.toList());\n\n    //stores locations\n    long end = System.nanoTime();\n    System.out.println((end-start)/1000000);\n    return avgLocation(locations);  \n}\n//given 2D array of locations, finds average location of set and returns as point\npublic static Point avgLocation(List<int[]> list) {\n    //if no points in list, return an impossible point on screen\n    if (list.size() == 0) {\n        return new Point (-100, -100);\n    }\n    //coordinates of average location (set to 100 to reveal bug easily)\n    int avgX = 100, avgY = 100;\n    int xSum = 0, ySum = 0;\n\n    //loop through array\n    for (int i = 0; i < list.size(); i++) {\n        //for each location, add up coordinates\n        xSum += list.get(i)[0];\n        ySum += list.get(i)[1];\n    }\n    avgX = xSum/list.size();\n    avgY = ySum/list.size();\n    return new Point(avgX, avgY);\n}\n
Run Code Online (Sandbox Code Playgroud)\n

int[]我们可以进一步改进代码,因为当我们知道索引和坐标之间的关系时,我们不需要将点表示为数组:

\n
public static Point processImage(BufferedImage img, Color color, int error) {\n    long start = System.nanoTime();\n    //bounds to check on color components, make sure all within [0,255]\n    int redLower = color.getRed() - error;\n    int redHigher =  color.getRed() + error;\n    int greenLower = color.getGreen() - error;\n    int greenHigher =  color.getGreen() + error;\n    int blueLower = color.getBlue() - error;\n    int blueHigher =  color.getBlue() + error;\n    \n    //place all components within [0,255]\n    if (redLower < 0) redLower = 0;\n    if (redHigher > 255) redHigher = 255;\n    if (greenLower < 0) greenLower = 0;\n    if (greenHigher > 255) greenHigher = 255;\n    if (blueLower < 0) blueLower = 0;\n    if (blueHigher > 255) blueHigher = 255;\n\n    //create final variables to use for function\n    int redLowerF = redLower;\n    int redHigherF =  redHigher;\n    int greenLowerF = greenLower;\n    int greenHigherF =  greenHigher;\n    int blueLowerF = blueLower;\n    int blueHigherF =  blueHigher;\n\n    //data of image\n    int[] pixels = ((DataBufferInt)img.getRaster().getDataBuffer()).getData();\n    int width = img.getWidth();\n    int height = img.getHeight();\n\n    int[] locations = IntStream.range(0, pixels.length).parallel()\n        .filter(i -> {\n          int p = pixels[i];\n          // get red\n          int r = (p >> 16) & 0xff;\n          // get green\n          int g = (p >> 8) & 0xff;\n          // get blue\n          int b = p & 0xff;\n          //check if all components within bounds\n          return r >= redLowerF && r<=redHigherF && g>=greenLowerF && g <= greenHigherF\n              && b >= blueLowerF && b <= blueHigherF;\n        })\n        .toArray();\n\n    //stores locations\n    long end = System.nanoTime();\n    System.out.println((end-start)/1000000);\n    return avgLocation(locations, height);  \n}\nprivate static Point avgLocation(int[] locations, int height) {\n    if (locations.length == 0) {\n        return new Point (-100, -100);\n    }\n    //coordinates of average location (set to 100 to reveal bug easily)\n    int avgX = 100, avgY = 100;\n    int xSum = 0, ySum = 0;\n\n    //loop through array\n    for (int i: locations) {\n        int x = i % height, y = i / height;\n        //for each location, add up coordinates\n        xSum += x;\n        ySum += y;\n    }\n    avgX = xSum/locations.length;\n    avgY = ySum/locations.length;\n    return new Point(avgX, avgY);\n}\n
Run Code Online (Sandbox Code Playgroud)\n