【问题标题】:How to synchronize access to a folder using java?如何使用java同步对文件夹的访问?
【发布时间】:2012-12-30 11:04:48
【问题描述】:

我的应用程序中的情况如下我有2个线程并行运行,其中一个线程的目的是捕获屏幕截图,第二个线程的目的是重命名已保存在特定文件夹中的屏幕截图通过第一个线程 - 应用程序的代码如下 -:

CapturingAndRenamingSimultaneously.java

/**
 * Created with IntelliJ IDEA.
 * User: AnkitSablok
 * Date: 15/1/13
 * Time: 1:03 PM
 * To change this template use File | Settings | File Templates.
 */

package com.tian.screenshotcapture;

import java.io.File;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class CapturingAndRenamingSimultaneously {
    public static void main(String[] args) {

        // we use the linked blocking queue here to resolve the concurrency issues
        final BlockingQueue<File> queue = new LinkedBlockingQueue<File>(1024);

        new Thread(new Runnable() {
            @Override
            public synchronized void run() {
                try {
                    System.out.println("In the capture thread now");
                    CaptureScreenshots.captureScreenshots(queue);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public synchronized void run() {
                try {
                    while (true) {
                        System.out.println("In the rename thread now");
                        RenameScreenShots.renameScreenshots(queue);
                        Thread.sleep(5000);
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

CaptureScreenshots.java

/**
 * Created with IntelliJ IDEA.
 * User: AnkitSablok
 * Date: 15/1/13
 * Time: 12:35 PM
 * To change this template use File | Settings | File Templates.
 */

// this code is used to capture the screenshots

package com.tian.screenshotcapture;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.Toolkit;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
import java.io.File;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class CaptureScreenshots {

    // this code is used to capture the screen shots
    public static void captureScreenshots(BlockingQueue<File> queue) throws Exception {

        String fileName = "C:\\Users\\ankitsablok\\Desktop\\Screenshots";
        int index = 0;

        for (; ; ) {
            ++index;
            Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
            Rectangle screenRectangle = new Rectangle(screenSize);
            Robot robot = new Robot();
            BufferedImage image = robot.createScreenCapture(screenRectangle);
            ImageIO.write(image, "jpg", new File(fileName + "\\i" + index + ".jpg"));
            queue.put(new File(fileName + "\\i" + index + ".jpg"));

            Thread.sleep(1000);
        }
    }

}

重命名屏幕截图.java

/**
 * Created with IntelliJ IDEA.
 * User: AnkitSablok
 * Date: 15/1/13
 * Time: 12:49 PM
 * To change this template use File | Settings | File Templates.
 */

package com.tian.screenshotcapture;

import java.io.*;
import java.util.concurrent.BlockingQueue;

public class RenameScreenShots {
    public static void renameScreenshots(BlockingQueue<File> queue) throws IOException, InterruptedException {

        for (int i = 0; i < queue.size(); ++i) {

            File sourceFile = queue.take();
            System.out.println("The filename is : " + sourceFile.getName());

            if (sourceFile.getName().contains("sent")) {
            } else {
                System.out.println("The modified name of the source file is :" + sourceFile.getName().substring(0,
                        sourceFile.getName().indexOf('.'))
                        + "sent" + ".jpg");

                File newFile = new File(sourceFile.getParent() + "/" + sourceFile.getName().substring(0,
                        sourceFile.getName().indexOf('.'))
                        + "sent" + ".jpg");

                byte[] buffer = new byte[1024];

                FileInputStream fis = new FileInputStream(sourceFile);
                FileOutputStream fos = new FileOutputStream(newFile);

                int length;

                while ((length = fis.read(buffer)) > 0) {
                    fos.write(buffer, 0, length);
                }

                System.out.println("The file was deleted successfully : " + sourceFile.delete());

                fis.close();
                fos.close();
            }
        }
    }

}

我希望同步对文件夹的访问,即当一个进程将图像写入文件夹之后,另一个进程应该能够重命名文件夹中的图像。但是我无法同步对写入和读取屏幕截图的文件夹的访问,在某些时候使用上面的代码会给出 FileNotFoundException 错误,其他进程正在使用该文件。我该如何解决这个问题。

提前致谢。

【问题讨论】:

    标签: java multithreading


    【解决方案1】:

    如果动作是同步的,也就是说它们是连续运行的,那么你真的不应该有两个单独的线程。

    在同一个线程中按顺序写入和重命名,无需同步。

    【讨论】:

    • 我需要 2 个线程分别独立访问文件夹,这就是我需要多线程的原因
    • 为什么?你有两个操作,一个必须在另一个之后发生。使用单独的线程没有任何好处,因为没有什么可以并行完成。如果您仍然使用多线程(即使它没有用),请考虑使用java.util.concurrent.BlockingQueue 之类的东西。线程 1 完成它需要做的事情并将文件名推送到队列中。线程 2 从队列中取出并处理文件。手动同步应始终是您的最后手段。
    【解决方案2】:

    你为什么没有Mutex 并基于 Mutex 控制线程访问文件夹的行为??

    【讨论】:

    • 你能给我举个例子或者举一个例子来说明它在java中是如何使用的以及如何在这种情况下使用它
    • 这个想法很简单,如果监视器可用,两个线程都会查找监视器状态,然后它们将锁定监视器并完成(访问文件夹的过程),一旦工作完成释放监视器.但是,这也会导致其他线程等待
    • 这个Answser ??
    【解决方案3】:

    在两个线程LinkedBlockingQueue之间创建一个共享队列。

    从线程CaptureScreenshots 将新创建的File 对象放入此队列。

    从线程RenameScreenShots顺序读取这个队列准备好的File对象并处理它们。

    UPD:如果您担心数十亿的图像文件会被它们的File 描述符占用大量内存,您可以应用这样的算法增强:

    1. 在包含您的图像文件的文件夹中创建子文件夹,并将图像文件放入该子文件夹中。

    2. 用整数名称命名这些子文件夹:123、...、89

    3. 人为限制每个子文件夹中的文件数。当文件数量达到限制时,只需增加子文件夹的名称编号,创建一个新的并继续。

    4. 不是将每个图像文件的File 描述符放到LinkedBlockingQueue 中,而是将Integer 对象放在那里,其中每个对象将对应于具有相同名称的填充子文件夹。

    5. RenameScreenShots 中从LinkedBlockingQueue 中获取新元素,将此元素视为子文件夹名称,并处理此子文件夹中的所有文件。

    UPD-2 在我的UPD-1 中引入的方案可以更容易地使用某个整数值的共享synchornized getter 来实现,这将对应于已处理子文件夹的最后一个数字。

    【讨论】:

    • 如果 LinkedBlockingQueue 占用内存空间,那么这不是一个好的解决方案,因为所有文件都会占用主内存中的空间,如果有数十亿张图像,那么这肯定不会感觉
    • File object 只是某个文件的简单描述符(简而言之,将其视为包装器,仅包含它的名称和磁盘上的路径)。它不会吃掉很多内存。
    • 好吧,链接的阻塞队列肯定会将文件描述符存储为引用,所以如果我使用十亿个引用,我做了什么好事,仍然会有内存问题,而且你告诉创建子文件夹的解决方法会让我的工作复杂化而不是放松。
    • man 我在代码中添加了 LinkedBlockingQueue 功能,我想问题现在已经解决了,但是我在第一次使用这个功能时找不到代码中的漏洞,你能指出可以对代码进行的一些改进。
    • @Coder,你好!我不知道与此解决方案相关的任何漏洞。你能解释得更详细一点吗,你的意思是什么?我会尽我所能为您提供帮助。
    【解决方案4】:

    也许您可以尝试文件锁定。每个文件都可以是一个文件锁。

    RandomAccessFile file = new RandomAccessFile(some_file, "rw");
    FileChannel fc = file.getChannel();
    FileLock lock = fc.tryLock();
    ....
    lock.release()
    

    当您将 screanshot 写入 A.shot 等文件时,您会创建文件 A.shot,并持有 A.shot 的文件锁,然后将数据写入其中。文件完成后,释放文件锁。

    重命名过程应该首先尝试获取文件锁,如果成功,然后进行重命名工作。 如果无法获得文件锁(因为写线程还没有释放锁),则等待。

    希望它有用。 :-)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-04-05
      • 1970-01-01
      • 2016-03-04
      • 1970-01-01
      • 2013-05-30
      • 1970-01-01
      • 2016-11-24
      • 2020-10-03
      相关资源
      最近更新 更多