JAVA在线预览功能之多线程优化版本-----多进程、逻辑优化、并发问题处理

1 篇文章 0 订阅

优化版本在线预览Demo:https://pan.baidu.com/s/1jpOjmiTsilgThBmOKNRuoQ

一、上一个实现在线预览功能的想法与网上大多数在线预览功能一样

1)思路:

1.利用 OpenOffice 以及 jodconverter 转换各种office文件为pdf格式

2.设置response的contentType为application/pdf,直接用IO将pdf文件输出就可以了(缺点:若用户使用ie浏览器则是下载而不是预览)

2)弊端:

多个用户请求过来了,我要一个一个的转换,每来一次请求我就转换一次,并且转换稍大文件速度慢,用户体验不好,并发访问请求转换同一个文件还会报错

3)优化思路:

1.利用多进程--利用SpringMVC一个用户请求就有一个新的线程为此服务,但转换的openoffice进程只有一个,所以就算使用多线程还是相当于排队等一个转好了,下一个才会去转换。所以这里使用多进程(这里没有使用Process类)。

2.所有文件只转换一次--因为转换的pdf文件都放在某个目录下(以Windows为里,这里放在了E:pdfFile/),所以只要我先判断对应的目录下有无pdf文件,若有就直接取出,若没有就去转换。

3.用锁处理并发转换问题--有这么一种情况,两个用户同时请求预览同一个文件,同时发现文件还为转换为pdf,同时去转换导致转换异常,这时可以考虑加锁去处理,如果有一方正在转换了,另一方就直接等待转换好去现成的就可以了。

二、优化实现

优化点1)利用多进程实现:

1.开启多个端口不同的openOffice进程,并将这些进程放在一个阻塞队列中(这里用的并发包中的BlockingQueue),这样来了一个请求我就从这个阻塞队列中取出一个openOffice进程去处理,处理完之后我再把这个openOffice进程放回去,当这个队列空了,其他的线程就只能等待别的线程转换完之后放回去了才能再次转换了,这里端口放在了*.properties文件中,方便修改,这里选用了 9001 、9002、 9003、 9004 这四个端口,开启了四个openoffice进程待程序使用。

openOfficeHome=C:/Program Files (x86)/OpenOffice 4
ports=9001,9002,9003,9004
windowsCommand=/program/soffice.exe -headless -accept=\"socket,host=127.0.0.1,port=#Placeholder;urp;\"
converted_pdf_home=E:/pdfFile/

再启动服务器的时候就把有的进程都启动好,并且都放入阻塞队列中

 private final static String PORTS = "ports";
    private final static String PROPERTIES_FILE_NAME = "openOffice.properties";
    private final static String[] ports =
            PropertiesUtil.getPropertyByFileAndName(PROPERTIES_FILE_NAME,PORTS).split(",");
    private final static String OPPEN_OFFICE_HOME = "openOfficeHome";
    private final static String CONVERTED_PDF_HOME = "converted_pdf_home";
    private static OfficeManager OfficeManager = null;
    public static BlockingQueue<OfficeManager> OfficeManagerQueue = new LinkedBlockingDeque<OfficeManager>();
    private static  Lock lock = new ReentrantLock();
    private static Map<String,String> fileMap = new ConcurrentHashMap<>(16);

    @PostConstruct
    public  void initOpenOfficeService() {
        DefaultOfficeManagerBuilder builder = new DefaultOfficeManagerBuilder();
        builder.setOfficeHome(getOfficeHome());
        for(String port : ports){
            builder.setPortNumbers(Integer.parseInt(port));
            OfficeManager = builder.build();
            try {
                OfficeManager.start();
                System.out.println("##############officeManager start !");
            } catch (OfficeException e) {
                //打印日志
                System.out.println("start openOffice Fail!");
                e.printStackTrace();
            }
            try {
                //都放入阻塞队列中
                OfficeManagerQueue.put(OfficeManager);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

在关闭的时候将所有进程停止

   @PreDestroy
    public void destroyOpenOfficeService(){
        for(OfficeManager manager : OfficeManagerQueue){
            try {
                System.out.println("close all officeManager");
                manager.stop();
            } catch (OfficeException e) {
                System.out.println("officeManager stop fail!"+e.getMessage());
                e.printStackTrace();
            }
        }
    }

优化点2)所有文件只转换一次实现逻辑

这里再请求的时候做一个简单的逻辑判断,如果请求预览的文件本来就是pdf就直接返回回去了,否则就判断这个office文件是否被转换过,因为转换后的文件都存放在目录E:/pdfFile/中去了,这里只要判断这个目录下是否存在对应的pdf文件即可,最后如果又不是pdf文件,又没有被转换过就真正的去转换了

    private String fileHandler(String fileName){
        String fileSuffix = StringUtil.getFileSuffix(fileName);
        String result = null;
        System.out.println(fileSuffix);
        if("pdf".equals(fileSuffix))
        {
            System.out.println("file is pdf type");
            result = BASE_PATH + fileName;
            return result;
        }
        else  if(new File("E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf")).exists())
        {
            System.out.println("file has been converted");
            result =  "E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf");
        }
        else
        {
            System.out.println("file start conveted");
            openOfficeServicel.openOfficeToPDF(BASE_PATH+fileName);
            result =  "E:/pdfFile/"+fileName.replaceAll("\\."+ StringUtil.getFileSuffix(fileName),".pdf");
        }
        return result;
    }

优化点3)用锁处理并发转换问题实现

失败的例子1)想到一个办法,就是利用 java.io.File这个原生的类中的 rename 方法,也就是说如果 if(!sourceFile.renameTo(sourceFile)) 为 true,就表明这个文件正在被操作,那我就只要判断转换后文件存放的目录中是否存在转换好的pdf文件即可,如果有了就直接把锁释放了,然后return。

    public static void convertFile(File sourceFile,
                                   String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
        File outputFile = new File(after_convert_file_path);
        if(!outputFile.getParentFile().exists()){
            //如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
            outputFile.getParentFile().mkdirs();
        }
        OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
        //加锁
        lock.lock();
        if(!sourceFile.renameTo(sourceFile)){
            System.out.println("文件" + sourceFile.getName() + "正在被使用");
        while(true){
            if(new File("E:/pdfFile/"+StringUtil.converSuffixToPdf(sourceFile.getName())).exists()){
                    System.out.println("文件有了");
                    try {
                        OfficeManagerQueue.put(officeManager);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //释放锁并return
                    lock.unlock();
                    return;
                }
            }
        }
        //释放锁
        lock.unlock();
        converter.convert(sourceFile,outputFile);
        try {
            OfficeManagerQueue.put(officeManager);
            System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());

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

后面测试发现这种方式有很多问题:

1、它并不能处理并发问题,只能解决两个请求先后请求转换同一个office文件,第一个正在转换的时候,第二个不会去转换。

2、如果有三个请求,前面两个请求转换同一office文件(比如就叫 "java面试题.doc"),第三个请求转换另外一个office文件(比如是 "记录.xlsx"),那么第一个请求进入这个方法的时候先拿到锁,然后继续往下走把锁释放了调用convert方法去转换了。第二个请求过来拿到了锁,然后一直持有着,直到第一个转换完了,对应目录下有转换好的pdf文件才把锁释放,导致第三个请求就一直阻塞在这里

3.并发的时候没有达到目的,并发的时候请求预览同一office文件的两个线程同时到了这个方法,第一个线程拿到了锁然后发现没有人在操作这个文件把锁释放了,准备去转换为pdf文件了。就在锁释放的一瞬间,第二个线程拿到了锁,判断这个这个office文件有没有被操作,结果第一个线程还没有来得及操作,第二个线程已经得到判断结果(没有人操作这个文件),然后把锁释放,也去转换这个文件去了,就发生了转换异常。

 

失败的例子2)最开始我想到的解决办法就是加锁处理,两个文件转换同一office文件,肯定要操作这个office源文件,很简单,对这个在转换的方法中,对要操作的文件加一个文件锁(FileLock),操作文件之前给这个文件上把文件锁,等我操作完之后再释放不就解决了吗,顺着这个思路

   public  void convertFile(File sourceFile,
                             String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
        File outputFile = new File(after_convert_file_path);
        if(!outputFile.getParentFile().exists()){
            //如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
            outputFile.getParentFile().mkdirs();
        }
        OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
        RandomAccessFile raf = new RandomAccessFile(sourceFile, "rw");

          FileChannel fc = raf.getChannel();

        try {
            FileLock fl = fc.tryLock();
             if (fl== null) {
            System.out.println("获得文件锁失败");
        }
        } catch (IOException e) {
            Sytem.out.println();
        }
        converter.convert(sourceFile,outputFile);
        try {
            fl.release();
        } catch (IOException e) {
            e.printStackTrace();
        }
        try {
            OfficeManagerQueue.put(officeManager);
            System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());

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

具体代码有点忘记了,最终导致失败的最大问题就是,对文件加锁导致后面真正去转换的时候要操作文件直接报 could not open file 不能打开文件错误。

成功例子)思路就是,一开始先创建一个静态的ConcurrentHashMap,在真正调用转换方法之前先上锁,然后判断这个map中是否包含待转换的文件的名字,如果有就先解锁,然后判断对应存放转换后的文件目录是否包含文件,否则在map中put对应的待转换的文件的名字,最后再解锁,这里先解锁是怕阻塞别的线程

  public  void convertFile(File sourceFile,
                                   String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
        File outputFile = new File(after_convert_file_path);
        if(!outputFile.getParentFile().exists()){
            //如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
            outputFile.getParentFile().mkdirs();
        }
        OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
        lock.lock();
        if(fileMap.containsKey(sourceFile.getName()))
        {
                lock.unlock();
                while(true) {
                    if (new File("E:/pdfFile/java.pdf").exists()) {
                        System.out.println("文件有了");
                        try {
                            OfficeManagerQueue.put(officeManager);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return;
                    }
                }
        }
        else
        {
            fileMap.put(sourceFile.getName(),sourceFile.getName());
        }
        lock.unlock();
        converter.convert(sourceFile,outputFile);
        try {
            OfficeManagerQueue.put(officeManager);
            System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());

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

核心代码

定义变量、常量、初始化将openoffice进程启动并放入阻塞队列,以及关闭服务时停止openoffice

    private final static String PORTS = "ports";
    private final static String PROPERTIES_FILE_NAME = "openOffice.properties";
    private final static String[] ports =
            PropertiesUtil.getPropertyByFileAndName(PROPERTIES_FILE_NAME,PORTS).split(",");
    private final static String OPPEN_OFFICE_HOME = "openOfficeHome";
    private final static String CONVERTED_PDF_HOME = "converted_pdf_home";
    private static OfficeManager OfficeManager = null;
    public static BlockingQueue<OfficeManager> OfficeManagerQueue = new LinkedBlockingDeque<OfficeManager>();
    private static  Lock lock = new ReentrantLock();
    private static Map fileMap = new ConcurrentHashMap<>(16);
    @PostConstruct
    public  void initOpenOfficeService() {
        DefaultOfficeManagerBuilder builder = new DefaultOfficeManagerBuilder();
        builder.setOfficeHome(getOfficeHome());
        for(String port : ports){
            builder.setPortNumbers(Integer.parseInt(port));
            OfficeManager = builder.build();
            try {
                OfficeManager.start();
                System.out.println("##############officeManager start !");
            } catch (OfficeException e) {
                //打印日志
                System.out.println("start openOffice Fail!");
                e.printStackTrace();
            }
            try {
                OfficeManagerQueue.put(OfficeManager);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    @PreDestroy
    public void destroyOpenOfficeService(){
        for(OfficeManager manager : OfficeManagerQueue){
            try {
                System.out.println("close all officeManager");
                manager.stop();
            } catch (OfficeException e) {
                System.out.println("officeManager stop fail!"+e.getMessage());
                e.printStackTrace();
            }
        }
    }

转换文件方法

    public  void convertFile(File sourceFile,
                                   String after_convert_file_path,String sourceFilePath,OfficeManager officeManager) throws OfficeException {
        File outputFile = new File(after_convert_file_path);
        if(!outputFile.getParentFile().exists()){
            //如果上级目录不存在也就是E:/pdfFile这个文件夹不存在则创建一个
            outputFile.getParentFile().mkdirs();
        }
        OfficeDocumentConverter converter = new OfficeDocumentConverter(officeManager);
        lock.lock();
        if(fileMap.containsKey(sourceFile.getName()))
        {
                lock.unlock();
                while(true) {
                    if (new File("E:/pdfFile/java.pdf").exists()) {
                        System.out.println("文件有了");
                        try {
                            OfficeManagerQueue.put(officeManager);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        return;
                    }
                }
        }
        else
        {
            fileMap.put(sourceFile.getName(),sourceFile.getName());
        }
        lock.unlock();
        converter.convert(sourceFile,outputFile);
        try {
            OfficeManagerQueue.put(officeManager);
            System.out.println("blockingQueue puted OfficeManagerQueue size :" + OfficeManagerQueue.size());

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

转换文件之前的准备

    public  void office2pdf(String sourceFilePath){
        OfficeManager officeManager = null;
        File sourceFile = new File(sourceFilePath);
        try{
            if(StringUtil.isEmpty(sourceFilePath))
            {
                //打印日志...
                return ;
            }

            if(!sourceFile.exists())
            {
                //打印日志...
                return ;
            }
            String after_convert_file_path = getAfterConverFilePath(sourceFilePath);
            officeManager = OfficeManagerQueue.take();
            System.out.println("blockingQueue taked , OfficeManagerQueue size :" + OfficeManagerQueue.size());
             convertFile(sourceFile,after_convert_file_path,sourceFilePath,officeManager);
        }catch (Exception e){
            e.printStackTrace();
            try {
                OfficeManagerQueue.put(officeManager);
                System.out.println("blockingQueue put , OfficeManagerQueue size :" + OfficeManagerQueue.size());
            } catch (InterruptedException e1) {
              //  lock.unlock();
                e1.printStackTrace();
                return;
            }
            System.out.println("转换异常");
        }
    }

 

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
好的,您的问题是关于Java多线程的存钱取钱问题。这是一个经典的多线程同步问题,可以通过使用synchronized关键字来实现线程安全。下面是一个简单的示例代码: ```java public class BankAccount { private int balance; public BankAccount(int balance) { this.balance = balance; } public synchronized void deposit(int amount) { balance += amount; System.out.println("Deposit successful. Balance: " + balance); } public synchronized void withdraw(int amount) { if (balance < amount) { System.out.println("Withdraw failed. Insufficient balance."); return; } balance -= amount; System.out.println("Withdraw successful. Balance: " + balance); } } public class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000); // Create two threads to simulate deposit and withdraw Thread depositThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.deposit(100); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread withdrawThread = new Thread(() -> { for (int i = 0; i < 5; i++) { account.withdraw(200); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } }); depositThread.start(); withdrawThread.start(); } } ``` 在这个示例代码中,我们创建了一个银行账户类BankAccount,并在其中实现了deposit和withdraw方法,并使用synchronized关键字来保证线程安全。 在main方法中,我们创建了两个线程来模拟存款和取款操作,每个线程执行5次操作。我们使用Thread.sleep方法来模拟每个操作之间的间隔,以便更好地观察多线程操作的结果。 当多个线程同时访问BankAccount对象的deposit和withdraw方法时,synchronized关键字可以确保每个方法只能被一个线程访问,从而避免了竞争条件和数据不一致的问题。 希望这个示例代码能够回答您的问题

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值