对于下面的示例代码,如何从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个频繁运行的块或方法来避免频繁和长时间的锁定,并保护可能较长的I/O操作而改用java.util.concurrent.locks.ReentrantLock.

推荐答案

要将代码从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包装在另一个类中.

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

Avoiding client-side locking

访问对象的代码获取其上的锁的这种模式有时称为client-side locking,因为负责获取锁的是锁定对象的客户端,而不是锁定对象本身.由于一个重大缺陷,这种做法通常是discouraged:它依赖于对象的all次使用才能正确且一致地实现锁定策略.这使得很难推断类的线程安全性,因为只有在整个代码中考虑类的每次使用才能理解它.例如,这可能会导致问题:如果代码从synchronized更新为在一个地方使用ReentrantLock,但不在其他地方使用,则互斥属性将被 destruct .

作为另一种 Select ,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类中,还需要修改其他方法以根据需要获取锁.

Fine-grained vs. coarse-grained locking

在复合对象的情况下,其中一个对象(在本例中为TileManager)包含一个完全封装且不向其他类(在本例中为Tile)公开的内部对象的集合,在对内部集合进行并发操作时有一个 Select :您可以单独锁定集合中的每一项(细粒度锁定),也可以锁定整个集合(粗粒度锁定).这些都需要权衡取舍,最佳 Select 取决于具体的用例.细粒度锁定(如本例所示)允许多个线程并发访问集合的不同部分,这有助于避免长期运行的进程阻止其他线程访问当前未受影响的元素.需要注意的主要缺点是,如果有一个线程需要同时锁定多个元素,则存在死锁的风险.当竞争激烈时,也有可能出现不同的行为.processImage方法可能会被多次阻塞,等待获取单个磁贴上的锁.允许它获取单个粗粒度锁并完成tiles的迭代而不会有进一步阻塞的风险可能更可取.这实际上取决于应用程序的具体情况,并且必须与数据的其他用途的上下文一起考虑.

Java相关问答推荐

当切换javaFX场景时,stage的大小正在Minimize

scanner 如何在执行hasNextLine一次后重新读取整个文件?

方法没有用正确的值填充数组—而是将数组保留为null,'

使用标记时,场景大纲不在多个线程上运行

将不受支持的时区UT重写为UTC是否节省?

如何找到MongoDB文档并进行本地化?

为什么不应用类型推断?

获取字符串中带空格的数字和Java中的字符

如何在Cosmos DB(Java SDK)中增加默认响应大小

当构造函数创建一个新实例时,Java为什么需要&new";

如何在Jooq中获取临时表列引用?

Java中HashSet的搜索时间与TreeSet的搜索时间

如何在Record Java中使用isRecord()和RecordComponent[]?

Cucumber java-maven-示例表-未定义一步

让标签占用JavaFX中HBox的所有可用空间

获取401未经授权,即使在标头中设置了浏览器名称和cookie

Spring Mapstruct如何获取Lazy初始化实体字段的信息?

Java泛型方法重载

Cucumber中第二个网页的类对象未初始化

如何使用 Java 替换位于特定标记内的 XML 标记的 CDATA 内的值