Java 多线程:hava中基于资源的动态对象锁定

Java 多线程:hava中基于资源的动态对象锁定,java,multithreading,java.util.concurrent,Java,Multithreading,Java.util.concurrent,这里需要专家的眼睛 我在一个poc应用程序上,我正在FTP服务器上上传文件 在FTP服务器中有多个文件夹。根据输入响应,我从文件夹中读取文件并移动到另一个文件夹 应用程序一次可以通过多个线程访问 所以问题是: 假设FTP有一个文件夹和一个文件夹 现在文件夹有10个文件。 一个线程从FTP读取了10个文件并开始计算, 它一个接一个地计算,然后移动到一个文件夹 假设它成功地将5个文件从文件夹_A移动到了文件夹A_A_ 然后另一个线程来了,它会挑选剩下的5个文件,因为它们被线程1处理不足,所以线程2也

这里需要专家的眼睛

我在一个poc应用程序上,我正在FTP服务器上上传文件

在FTP服务器中有多个文件夹。根据输入响应,我从文件夹中读取文件并移动到另一个文件夹

应用程序一次可以通过多个线程访问

所以问题是:

假设FTP有一个文件夹和一个文件夹 现在文件夹有10个文件。 一个线程从FTP读取了10个文件并开始计算, 它一个接一个地计算,然后移动到一个文件夹 假设它成功地将5个文件从文件夹_A移动到了文件夹A_A_ 然后另一个线程来了,它会挑选剩下的5个文件,因为它们被线程1处理不足,所以线程2也会开始处理这5个文件

所以这里有重复文件的问题

void m1(String folderName) {
// FTP related code
}
我已经通过使用synchronized关键字解决了这个问题

现在一切都同步了,所有的处理都很好

synchronized void m1(String folderName) {
// code
}
folderName决定需要处理哪个文件夹

现在我开始面临性能问题

因为该方法是同步的,所以所有线程都将等待,直到处理线程未完成其任务

我可以通过以下步骤对此进行改进:

在讨论解决方案之前,这里有一些关于这个问题的故事

正如我提到的m1方法的folderName参数决定将处理哪个文件夹, 假设我在Ftp服务器中有4个文件夹A,B,A,T,B,T,2个文件夹是需要从A和B中读取数据的文件夹, 和2个文件夹是数据将在其中移动A\u T和B\u T的文件夹

这里不需要考虑A和B,因为它们对于每个文件夹A和B都是唯一的 因此,如果该方法将从A中读取数据,那么它将把它移动到A\T,与B移动到B\T相同

现在:

假设m1方法有4个线程,文件夹A有3个线程,文件夹B有1个线程 如果方法基于fileName参数同步了请求,那么我可以提高性能,意味着1个线程将在另一个线程上工作,2个线程将被阻止,因为它们的文件名相同,所以它们将等待直到第一个线程未完成它的任务,其中线程4将并行工作,而不执行任何锁定过程,因为它的文件名不同

那么,如何在代码级别实现文件名的同步呢

注意:我知道我可以使用资源的静态锁定列表打破这个逻辑,然后锁定文件名资源 e、 g:

但这种方法的问题是文件夹可以动态添加,所以我不能这样做


需要你们的帮助

一种方法是为每个目录维护一个锁:

public class DirectoryTaskManager {
    public static void main(String[] args) throws IOException {
        DirectoryTaskManager manager = new DirectoryTaskManager();
        manager.withDirLock(new File("Folder_A"), () -> System.out.println("Doing something..."));
    }

    public void withDirLock(File dir, Runnable task) throws IOException {
        ReentrantLock lock = getDirLock(dir);
        lock.lock();
        try {
            task.run();
        } finally {
            lock.unlock();
        }
    }

    private Map<File, ReentrantLock> dirLocks = Collections.synchronizedMap(new HashMap<>());

    public ReentrantLock getDirLock(File dir) throws IOException {
        // Resolve the canonical file here so that different paths 
        // to the same file use the same lock
        File canonicalDir = dir.getCanonicalFile();
        if (!canonicalDir.exists() || !canonicalDir.isDirectory()) {
            throw new FileNotFoundException(canonicalDir.getName());
        }
        return dirLocks.computeIfAbsent(canonicalDir, d -> new ReentrantLock());
    }
}

一种方法是为每个目录维护一个锁:

public class DirectoryTaskManager {
    public static void main(String[] args) throws IOException {
        DirectoryTaskManager manager = new DirectoryTaskManager();
        manager.withDirLock(new File("Folder_A"), () -> System.out.println("Doing something..."));
    }

    public void withDirLock(File dir, Runnable task) throws IOException {
        ReentrantLock lock = getDirLock(dir);
        lock.lock();
        try {
            task.run();
        } finally {
            lock.unlock();
        }
    }

    private Map<File, ReentrantLock> dirLocks = Collections.synchronizedMap(new HashMap<>());

    public ReentrantLock getDirLock(File dir) throws IOException {
        // Resolve the canonical file here so that different paths 
        // to the same file use the same lock
        File canonicalDir = dir.getCanonicalFile();
        if (!canonicalDir.exists() || !canonicalDir.isDirectory()) {
            throw new FileNotFoundException(canonicalDir.getName());
        }
        return dirLocks.computeIfAbsent(canonicalDir, d -> new ReentrantLock());
    }
}

感谢@teppic和@OlegSklyar的指导 最后是完整的工作示例

FolderImpl->have方法名调用,可被多个线程访问

我曾经使用过ConcurrentHashMapReads,当使用锁完成写操作时,读操作会发生得非常快。哪个比synchronizedMap快 它将保存文件夹名和可重入锁定,因此锁定将对文件夹名起作用

public class FolderImpl {
    private FolderImpl(){
        System.out.println("init................");
    }

    private ConcurrentHashMap<String, ReentrantLock> concurrentHashMap= new ConcurrentHashMap();
    private static final FolderImpl singleTon = new FolderImpl();
    public static FolderImpl getSingleTon() {
        return singleTon;
    }

    public void call(String name) throws Exception{
        ReentrantLock getDirLock = getDirLock(name);
        getDirLock.lock();
        try {
        for (int i = 0; i < 100; i ++) {
            System.out.println(i+":"+name+":"+Thread.currentThread().getName());
            try {
                Thread.sleep(30);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }}finally {
            getDirLock.unlock();
        }

    }

    public ReentrantLock getDirLock(String site)  {
        return concurrentHashMap.computeIfAbsent(site, d -> new ReentrantLock());
    }
}

感谢@teppic和@OlegSklyar的指导 最后是完整的工作示例

FolderImpl->have方法名调用,可被多个线程访问

我曾经使用过ConcurrentHashMapReads,当使用锁完成写操作时,读操作会发生得非常快。哪个比synchronizedMap快 它将保存文件夹名和可重入锁定,因此锁定将对文件夹名起作用

public class FolderImpl {
    private FolderImpl(){
        System.out.println("init................");
    }

    private ConcurrentHashMap<String, ReentrantLock> concurrentHashMap= new ConcurrentHashMap();
    private static final FolderImpl singleTon = new FolderImpl();
    public static FolderImpl getSingleTon() {
        return singleTon;
    }

    public void call(String name) throws Exception{
        ReentrantLock getDirLock = getDirLock(name);
        getDirLock.lock();
        try {
        for (int i = 0; i < 100; i ++) {
            System.out.println(i+":"+name+":"+Thread.currentThread().getName());
            try {
                Thread.sleep(30);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }}finally {
            getDirLock.unlock();
        }

    }

    public ReentrantLock getDirLock(String site)  {
        return concurrentHashMap.computeIfAbsent(site, d -> new ReentrantLock());
    }
}

您可以使用ConcurrentHashMap,其中键是文件名,值是ne CountDownLatch1的实例。执行移动的一个线程控制闩锁并将其倒计时,其他线程仅在闩锁未倒计时且仅为其文件名时才等待闩锁。试一试,如果你没有成功,我会给你写一个例子你使用FTP是一个基本问题,因为在FTP下,没有办法知道文件传输已经完成。传输可能会中途失败,然后重新启动或从停止的位置继续,并且传输完成时没有可用的信号或标志。您永远无法100%确定服务器上载目录中的内容是否可用。您应该改为迁移到rsync,rsync可以配置为写入临时文件,并仅在收到所有数据时创建最终文件。@JimGarrison我感谢您的回答,但这里的主要问题不是FTP,我已经实现了FTP的流程,这也不关我的事,这里的重点是多线程控制这种场景。因为在很多情况下,在多线程的情况下可能会发生这种问题。所以我很好奇如何用多线程处理这个问题env@OlegSklyar我已经在下面添加了完全有效的解决方案,请您检查一下。是否有任何建议我需要任何额外的工作围绕它从你这边,将是可观的。非常感谢。您可以使用ConcurrentHashMap,其中键是文件名,值是
东北倒计时锁1。执行移动的一个线程控制闩锁并将其倒计时,其他线程仅在闩锁未倒计时且仅为其文件名时才等待闩锁。试一试,如果你没有成功,我会给你写一个例子你使用FTP是一个基本问题,因为在FTP下,没有办法知道文件传输已经完成。传输可能会中途失败,然后重新启动或从停止的位置继续,并且传输完成时没有可用的信号或标志。您永远无法100%确定服务器上载目录中的内容是否可用。您应该改为迁移到rsync,rsync可以配置为写入临时文件,并仅在收到所有数据时创建最终文件。@JimGarrison我感谢您的回答,但这里的主要问题不是FTP,我已经实现了FTP的流程,这也不关我的事,这里的重点是多线程控制这种场景。因为在很多情况下,在多线程的情况下可能会发生这种问题。所以我很好奇如何用多线程处理这个问题env@OlegSklyar我已经在下面添加了完全有效的解决方案,请您检查一下。是否有任何建议我需要任何额外的工作围绕它从你这边,将是可观的。非常感谢。砰的一声:@teppic谢谢你的指导。我已经在下面添加了一个完整的工作示例,请您回顾一下,这是一个很好的答案。使用Collections.synchronizedMap而不是ConcurrentHashMap有什么原因吗?@Sneh:在大多数情况下,我更喜欢Collections.synchronizedMap。ConcurrentHashMap牺牲了读取速度的一致性,并且具有更复杂的锁定语义。我明白你的观点,但syncronizedMap的性能确实受到对象级锁定的影响。由于只调用ComputeFabsenr,所以它与这个答案没有多大区别。这是一个很好的阅读提示:@teppic谢谢你的指导。我已经在下面添加了一个完整的工作示例,请您回顾一下,这是一个很好的答案。使用Collections.synchronizedMap而不是ConcurrentHashMap有什么原因吗?@Sneh:在大多数情况下,我更喜欢Collections.synchronizedMap。ConcurrentHashMap牺牲了读取速度的一致性,并且具有更复杂的锁定语义。我明白你的观点,但syncronizedMap的性能确实受到对象级锁定的影响。由于只调用ComputeFabsenr,所以它与这个答案没有多大区别。这是一个很好的阅读选择,锁定机制可以起到很大的作用。阅读此@Sneh谢谢,blog很富有,但这里的StampedLock或ReentrantReadWriteLock不满足我的要求,它们可以,但对于这种情况,它们效率不高。在这些锁定策略中,最好允许多个读线程同时持有读锁,只要没有写入程序,而且写锁是独占的。顺便说一下,锁定机制的选择会带来很大的不同。阅读此@Sneh谢谢,blog很富有,但这里的StampedLock或ReentrantReadWriteLock不满足我的要求,它们可以,但对于这种情况,它们效率不高。在这些锁定策略中,最好允许多个读线程同时持有读锁,只要没有写入程序,并且写锁是独占的。
public class TestExecution {

    public static void main(String[] args) {
        TaskCaller testThreadCC = new TaskCaller("A_FOLDER");
        TaskCaller testThreadCC2 = new TaskCaller("A_FOLDER");
        TaskCaller testThreadCC3 = new TaskCaller("B_FOLDER");
        TaskCaller testThreadCC4 = new TaskCaller("C_FOLDER");
        TaskCaller testThreadCC5 = new TaskCaller("C_FOLDER");
        TaskCaller testThreadCC6 = new TaskCaller("C_FOLDER");
        TaskCaller testThreadCC7 = new TaskCaller("A_FOLDER");
        TaskCaller testThreadCC8 = new TaskCaller("A_FOLDER");
        TaskCaller testThreadCC9 = new TaskCaller("B_FOLDER");
        TaskCaller testThreadCC10 = new TaskCaller("B_FOLDER");

        testThreadCC.start();
        testThreadCC2.start();
        testThreadCC3.start();
        testThreadCC4.start();
        testThreadCC5.start();
        testThreadCC6.start();
        testThreadCC7.start();
        testThreadCC8.start();
        testThreadCC9.start();
        testThreadCC10.start();

    }

}