如何在 Java 上针对循环数组从同步迁移到 ReentrantLock

Tug*_*cak 1 java concurrency

对于下面的示例代码,如何从 迁移synchronizedReentrantLock

下面是重构后的代码

public final class ControllerCell {

    public final int tableSize;
    private final ObjectCell cells[][];
    private final TGS_ShapeDimension<Integer> cellSize;
    private final float jpeg_quality;

    public static ControllerCell of(float jpeg_quality, int tableSize) {
        return new ControllerCell(jpeg_quality, tableSize);
    }

    private ControllerCell(float jpeg_quality, int tableSize) {
        this.jpeg_quality = jpeg_quality;
        this.tableSize = tableSize;
        cellSize = new TGS_ShapeDimension(0, 0);
        cells = new ObjectCell[tableSize][tableSize];
    }

    final public void processImage(ControllerInput cInput) {
        cellSize.width = cInput.screenSize.width / tableSize;
        cellSize.height = cInput.screenSize.height / tableSize;
        for (var i = 0; i < tableSize; i++) {
            for (var j = 0; j < tableSize; j++) {
                if (cells[i][j] == null) {
                    cells[i][j] = new ObjectCell(jpeg_quality);
                }
                var subw = (i == tableSize - 1) ? (cellSize.width + (cInput.screenSize.width % cellSize.width)) : cellSize.width;
                var subh = (j == tableSize - 1) ? (cellSize.height + (cInput.screenSize.height % cellSize.height)) : cellSize.height;
                var subimage = cInput.screenShot().getSubimage(i * cellSize.width, j * cellSize.height, subw, subh);
                synchronized (cells[i][j]) {
                    cells[i][j].update(subimage);
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

下面是原始代码。要下载请点击这里

类:TileManager

/**
 *
 * @author heic
 */
package hk.haha.onet.ajaxvnc;
import java.awt.image.*;
public class TileManager {
    
    private boolean DEBUG = true;
    
    public final int MAX_TILE = 10;
    private Tile tiles[][];
    private int numxtile;
    private int numytile;
    private int tilewidth;
    private int tileheight;
    private int screenwidth;
    private int screenheight;
    
    /** Creates a new instance of TileManager */
    public TileManager() {
        tiles = new Tile[MAX_TILE][MAX_TILE];
        numxtile = MAX_TILE;
        numytile = MAX_TILE;
        setSize(640, 480);
    }
    
    public void setSize(int sw, int sh) {
        screenwidth = sw;
        screenheight = sh;
        tilewidth = screenwidth / numxtile;
        tileheight = screenheight / numytile;
    }
    
    public void processImage(BufferedImage image, int x, int y)
    {
        BufferedImage subimage;
        int subw, subh;
        boolean changed;
        numxtile = x;
        numytile = y;
        setSize(screenwidth, screenheight);
        for (int i=0; i < numxtile; i++) {
            for (int j=0; j < numytile; j++) {
                if (tiles[i][j]==null) tiles[i][j] = new Tile();
                    if (i == numxtile-1) 
                        subw = tilewidth + (screenwidth % tilewidth);
                    else
                        subw = tilewidth;
                    if (j == numytile-1)
                        subh = tileheight + (screenheight % tileheight);
                    else
                        subh = tileheight;
                    subimage = image.getSubimage(i*tilewidth, j*tileheight, subw, subh);
            synchronized (tiles[i][j]) {
                changed = tiles[i][j].updateImage2(subimage);
                if (DEBUG) {
                    if (changed) System.out.println(getClass().getName() + ": [" + i + "," + j + "] Changed. ["+tiles[i][j].fileSize()+"]");
                }
            }
           }
         }    
    }
}
Run Code Online (Sandbox Code Playgroud)

类别: 瓷砖

/**
 *
 * @author heic
 */
package hk.haha.onet.ajaxvnc;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.ImageObserver;
import java.awt.image.PixelGrabber;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.util.zip.Adler32;
import java.util.zip.CheckedOutputStream;
import javax.imageio.ImageIO;
import javax.imageio.*;
import javax.imageio.stream.*;
import java.util.Iterator;
import javax.imageio.metadata.*;
public class Tile {
    
    private boolean DEBUG = false;
    
    private ByteArrayOutputStream stream;
    private Adler32 checksum;
    private long version;
    private boolean dirty;
    private int width;
    private int height;
    private static ImageWriter imgwriter;
    private static ImageWriteParam iwp;
    
    /** Creates a new instance of Tile */
    public Tile() {
        stream = new ByteArrayOutputStream();
        checksum = new Adler32();
        version = 0;
        dirty = true;
        width = 0;
        height = 0;
    // create image writer
    Iterator iter = ImageIO.getImageWritersByFormatName("jpeg");
    imgwriter = (ImageWriter)iter.next();
    iwp = imgwriter.getDefaultWriteParam();
    iwp.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
    iwp.setCompressionQuality(Config.jpeg_quality);   // an integer between 0 and 1
    
    
    }
    private IIOMetadata getIIOMetadata(BufferedImage image, ImageWriter imageWriter, ImageWriteParam param) {
        ImageTypeSpecifier spec = ImageTypeSpecifier.createFromRenderedImage(image);
        IIOMetadata metadata = imageWriter.getDefaultImageMetadata(spec,param);
        return metadata;
    }
    
    private void writeImage(BufferedImage image, OutputStream outs)
    {
        try {
/*            ImageIO.write(image, "JPG", outs);*/
            width = image.getWidth();
            height = image.getHeight();
        ImageOutputStream ios  = ImageIO.createImageOutputStream(outs);

        imgwriter.setOutput(ios);
        IIOMetadata meta = getIIOMetadata(image, imgwriter, iwp);
        imgwriter.write(meta, new IIOImage(image, null, meta), iwp);
        ios.flush();
    
        
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public boolean updateImage2(BufferedImage image)
    {
        long oldsum;
        oldsum = checksum.getValue();
        //calcChecksum(image);
        calcChecksum2(image);
        if (oldsum != checksum.getValue()) {
            if (DEBUG) System.out.println(getClass().getName() + ": Version changed [" + stream.size() +"]");
            stream.reset();
            writeImage(image, stream);
            version++;
            dirty = true;
            return true;
        }
        else {
            if (DEBUG) System.out.println(getClass().getName() + ": Version unchange");
            return false;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

动机是在 Java 21+ 中使用虚拟线程。去引用JEP 444: Virtual Threads

synchronized通过修改频繁运行的块或方法并保护潜在的长时间 I/O 操作来避免频繁且长期的固定java.util.concurrent.locks.ReentrantLock

Tim*_*ore 5

如果您可以修改被锁定的类,将代码从 转换为 的最直接方法就是synchronized向之前的对象添加一个成员。为了简化示例,给定一个这样的类:ReentrantLockReentrantLocksynchronizedTile

public class Tile {
    public void update() {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

以及它的用法如下:

synchronized (tile) {
    tile.update();
}
Run Code Online (Sandbox Code Playgroud)

可以这样修改:

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

public class Tile {
    public final Lock lock = new ReentrantLock();

    public void update() {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

并像这样使用:

tile.lock.lock();
try {
    tile.update();
} finally {
    tile.lock.unlock();
}
Run Code Online (Sandbox Code Playgroud)

如果Tile无法修改该类,您可以保留实例的并行数组,或者重构以将和一起ReentrantLock包装在另一个类中。TileReentrantLock

对于您的用例,您可能还需要考虑一些其他事项。

避免客户端锁定

这种访问对象并获取其锁定的代码模式有时称为客户端锁定,因为负责获取锁定的是锁定对象的客户端,而不是锁定对象本身。由于一个主要缺点,这种做法常常不被鼓励:它依赖于对象的所有用法来正确且一致地实现锁定策略。这使得很难推断类的线程安全性,因为只有通过考虑整个代码中类的每次使用才能理解它。作为一个如何导致问题的示例:如果代码从一个地方更新为在一个地方synchronized使用ReentrantLock,但在其他地方没有更新,则互斥属性将被破坏。

作为替代方案,该类Tile可以负责锁定自身,根据其属性而不是其实现来记录其线程安全保证:

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

public class Tile {
    private final Lock lock = new ReentrantLock();

    public void update() {
        lock.lock();
        try {
            // ...
        } finally {
            lock.unlock();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

然后可以以更直接的方式使用它:

tile.update(); // no need to lock here
Run Code Online (Sandbox Code Playgroud)

这是一个简化的示例,在实际的Tile类中,还需要修改其他方法来根据需要获取锁。

细粒度锁定与粗粒度锁定

在复合对象的情况下,其中一个对象(在本例中为TileManager)包含一组完全封装且不暴露给其他类(在本例中为Tile)的内部对象的集合,在对复合对象进行并发操作时需要做出选择。内部集合:您可以独立锁定集合中的每个项目(细粒度锁定),也可以锁定整个集合(粗粒度锁定)。这些都需要权衡,最佳选择取决于具体的用例。如本例所示,细粒度锁定允许多个线程同时访问集合的不同部分,这有助于避免长时间运行的进程阻止其他线程访问当前不受影响的元素。需要注意的主要缺点是,如果有一个线程需要同时锁定多个元素,则存在死锁的风险。当存在高度争用时,还可能出现不同的行为。该processImage方法可能会多次阻塞,等待获取各个图块上的锁。更理想的做法可能是允许它获取单个粗粒度锁并完成迭代,tiles而不会有进一步阻塞的风险。这实际上取决于应用程序的具体情况,并且必须结合数据的其他用途的上下文来考虑。