如何从synchronized迁移到Java上的ReentrantLock以实现循环数组

6qftjkof  于 2023-09-29  发布在  Java
关注(0)|答案(2)|浏览(89)

对于下面的示例代码,如何从synchronized迁移到ReentrantLock
下面是重构的代码

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);
                }
            }
        }
    }
}

下面是原始代码。下载click here
类别: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()+"]");
                }
            }
           }
         }    
    }
}

分类:瓷砖

/**
 *
 * @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;
        }
    }
}

我们的动机是利用Java 21+中的虚拟线程。引用JEP 444: Virtual Threads
通过修改频繁运行的synchronized块或方法来避免频繁和长期的固定,并使用java.util.concurrent.locks.ReentrantLock来保护潜在的长I/O操作。

e0uiprwp

e0uiprwp1#

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

public class Tile {
    public void update() {
        // ...
    }
}

它的用法是这样的

synchronized (tile) {
    tile.update();
}

可以这样修改:

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

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

    public void update() {
        // ...
    }
}

这样使用:

tile.lock.lock();
try {
    tile.update();
} finally {
    tile.lock.unlock();
}

如果不能修改Tile类,您可以保留ReentrantLock示例的并行数组,或者重构以将TileReentrantLock Package 在另一个类中。
对于您的用例,您可能需要考虑一些其他事项。

避免客户端锁

这种代码访问对象并获取其上的锁的模式有时被称为 * 客户端锁定 *,因为是锁定对象的客户端负责获取锁,而不是锁定对象本身。这种做法通常是discouraged由于一个主要的缺点:它依赖于对象的 * 所有 * 使用来正确且一致地实现锁定策略。这使得很难推断类的线程安全性,因为只有在代码中考虑类的每次使用时才能理解。举个例子,说明这可能会导致问题:如果在一个地方将代码从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();
        }
    }
}

它可以以更简单的方式使用:

tile.update(); // no need to lock here

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

细粒度对比粗粒度锁定

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

rjee0c15

rjee0c152#

如果您可以修改ObjectCell类的源代码,请参阅Answer by Tim Moore。如果没有,请继续阅读。
如果您必须单独保护每个单元格/图块而不是一组,那么解决方案似乎很明显:
👉🏽当你填充ObjectCell[][] cells时,你也填充了一个并行的ReentrantLock[][] locks

if (cells[i][j] == null) {
    cells[i][j] = new ObjectCell(jpeg_quality);
    locks[i][j] = new ReentrantLock();
}

您的代码:

synchronized (cells[i][j]) 
{
    cells[i][j].update(subimage);
}

...变成:

locks[i][j].lock();
try
{
    cells[i][j].update(subimage);
}
finally
{
    locks[i][j].unlock();
}

相关问题