Java多线程+PDF生成实战(笔记)

新建项目

打开idea,新建一个moudle。
在这里插入图片描述
选择Spring Initializr,单击next,进入下一步。
在这里插入图片描述
接下来,安装Spring Boot套路设置项目名称和包名。
在这里插入图片描述
单击next。
在这里插入图片描述
再单击next。
在这里插入图片描述
单击finish完成工程的创建。工程目录结构为:
在这里插入图片描述

文件配置

修改pom.xml,添加pdf和Spring等相关依赖

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.5.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.njust</groupId>
    <artifactId>pdfmutithread</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>pdfmutithread</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-freemarker</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf/itextpdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.5.13</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.itextpdf.tool/xmlworker -->
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.13</version>
        </dependency>


        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>

            </plugin>

            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

资源配置教程

首先完成资源文件的创建和配置,新建如下文件。
在这里插入图片描述
freemarker.ftl,这里的模板为了后面和多线程版本测试做对比,将PDF设置的比较大,由于数据比较简单。暂时使用数量模拟现实中的质量。说白了就是模拟一段耗时操作。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Title</title>
    <style>
        body {
            font-family: SimHei;
        }

        .pos {
            position: absolute;
            left: 100px;
            top: 150px
        }
    </style>
</head>
<body>
<div class="blue pos">
    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>


    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">水调歌头·明月几时有</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">宋代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">苏轼</a></p>
    <div class="contson" id="contson632c5beb84eb">
        <p>丙辰中秋,欢饮达旦,大醉,作此篇,兼怀子由。</p>
        <p>明月几时有?把酒问青天。不知天上宫阙,今夕是何年。我欲乘风归去,又恐琼楼玉宇,高处不胜寒。起舞弄清影,何似在人间。</p>
    </div>

    <h1 style="font-size:20px; line-height:22px; height:22px; margin-bottom:10px;">清平乐·别来春半</h1>
    <p class="source"><a href="https://blog.csdn.net/qq_32510597/article/details/105060870">五代</a><span></span><a
            href="https://blog.csdn.net/qq_32510597/article/details/105054876">李煜</a></p>
    <div class="contson" id="contson632c5beb84eb">
        别来春半,触目柔肠断。砌下落梅如雪乱,拂了一身还满。(柔肠断 一作:愁肠断) 雁来音信无凭,路遥归梦难成。离恨恰如春草,更行更远还生。
    </div>


</div>
</body>
</html>

application.yml配置模板路径及生成文件位置

DEST: D:/xxxx/pdfOut/
HTML: freemarker.ftl
FONT: simhei.ttf

至此资源配置完成

单线程版开发

首先新建PDFService类。如下图所示。
在这里插入图片描述
源码如下:

package com.njust.pdfmutithread.user;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import freemarker.template.Configuration;
import freemarker.template.Template;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;

import java.io.*;
import java.nio.charset.Charset;
import java.util.Map;

@Service
public class PDFService {
    private String font = "simhei.ttf";
    private static Configuration freemarkerCfg = null;
    @Value("${DEST}")
    private String dest;

    @Value("${HTML}")
    private String html;

    public String getFont() {
        return font;
    }

    public void setFont(String font) {
        this.font = font;
    }

    public static Configuration getFreemarkerCfg() {
        return freemarkerCfg;
    }

    public static void setFreemarkerCfg(Configuration freemarkerCfg) {
        PDFService.freemarkerCfg = freemarkerCfg;
    }

    public String getDest() {
        return dest;
    }

    public void setDest(String dest) {
        this.dest = dest;
    }

    public String getHtml() {
        return html;
    }

    public void setHtml(String html) {
        this.html = html;
    }

    public void createPdf(String content, String dest) throws IOException, DocumentException {
        // step 1
        Document document = new Document();
        FileOutputStream fileOutputStream = new FileOutputStream(dest);
        // step 2
        PdfWriter writer = PdfWriter.getInstance(document, fileOutputStream);
        // step 3
        document.open();
        // step 4
        XMLWorkerFontProvider fontImp = new XMLWorkerFontProvider(XMLWorkerFontProvider.DONTLOOKFORFONTS);
        fontImp.register(font);
        XMLWorkerHelper.getInstance().parseXHtml(writer, document,
                new ByteArrayInputStream(content.getBytes("UTF-8")), null, Charset.forName("UTF-8"), fontImp);
        // step 5
        document.close();

    }

    /**
     * freemarker渲染html
     */
    public String freeMarkerRender(Map<String, Object> data, String htmlTmp) {
        Writer out = new StringWriter();

        try {
            // 获取模板,并设置编码方式
            setFreemarkerCfg();
            Template template = freemarkerCfg.getTemplate(htmlTmp);
            template.setEncoding("UTF-8");
            //将合并后的数据和模板写入到流中,这里使用的字符流
            template.process(data, out);
            out.flush();
            return out.toString();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException ex) {
                ex.printStackTrace();
            }
        }
        return null;
    }

    /**
     * 设置freemarkerCfg
     */
    private void setFreemarkerCfg() {
        freemarkerCfg = new Configuration();
        //freemarker的模板目录
        try {
            freemarkerCfg.setDirectoryForTemplateLoading(new ClassPathResource("template").getFile());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }


}

这里的get set方法,主要为了方便测试的时候调用。
在这里插入图片描述
如上图所示,PDFService 的主要方法是createPdf:创建PDF文件。
freeMarkerRender:freemarker渲染html。setFreemarkerCfg:设置freemarkerCfg。然后新建一个task任务操作类。如下图所示:
在这里插入图片描述
PDFTaskSingThread负责流水线创建PDF

package com.njust.pdfmutithread.user.task;


import com.itextpdf.text.DocumentException;
import com.njust.pdfmutithread.user.PDFService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/26 22:09
 * @description:
 */
@Component
public class PDFTaskSingThread {

    @Autowired
    private PDFService pdfService;

    public void createOne() {
        //需要填充的数据
        Map<String, Object> data = new HashMap<>(16);
        String content = pdfService.freeMarkerRender(data, pdfService.getHtml());
        String temp = pdfService.getDest() + "a.pdf";
        //创建pdf
        try {
            pdfService.createPdf(content, temp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }

    public void createManyWithOneThread(int num) {
        //需要填充的数据
        Map<String, Object> data = new HashMap<>(16);
        String content = pdfService.freeMarkerRender(data, pdfService.getHtml());

        if (num <= 0) {
            num = 10;
        }
        String temp = null;
        for (int i = 0; i < num; i++) {
            temp = pdfService.getDest() + i + ".pdf";
            //创建pdf
            try {
                pdfService.createPdf(content, temp);
            } catch (IOException e) {
                e.printStackTrace();
            } catch (DocumentException e) {
                e.printStackTrace();
            }
        }

    }
}

在PDFService中新建测试类
在这里插入图片描述
PDFService主要根据JOB_LENGTH大小测试批量生产PDF花费的时间。

package com.njust.pdfmutithread.user;


import com.njust.pdfmutithread.user.task.PDFTaskSingThread;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/26 21:55
 * @description:
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class PDFServiceTest {

    @Autowired
    private PDFTaskSingThread pdfTaskSingThread;


    public static void printTime(long start, long end) {
        long spendTime = end - start;
        System.out.println("spendTime: " + spendTime);
    }

    @Test
    public void createOne() {
        long start = System.currentTimeMillis();
        pdfTaskSingThread.createOne();
        long end = System.currentTimeMillis();
        printTime(start, end);
    }

    //  JOB_LENGTH:260      spendTime: 17310
    //  JOB_LENGTH:512      spendTime: 30660
    //  JOB_LENGTH:1024      spendTime: 59573
    @Test
    public void createManyWithOneThread() {
        long start = System.currentTimeMillis();
        pdfTaskSingThread.createManyWithOneThread(JOB_LENGTH);
        long end = System.currentTimeMillis();
        printTime(start, end);

    }


    private final static String JOB_NAME = "生成PDF";
    private final static int JOB_LENGTH = 1024;

}

不同的JOB_LENGTH花费的时间在作者本人的电脑上运行的时间已经写在了注释里面。读者也可以在自己电脑上运行。
在这里插入图片描述
对具体流程不是很熟悉的读者可以根据上面的时序图辅助理解。

多线程工作框架

按照下图新建类
在这里插入图片描述
下面解释一下各个类的作用:

  • ITaskProcesser:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,所以传入的参数和方法的返回值均使用泛型。
  • ItemVo:存放到队列的元素
  • JobInfo:提交给框架执行的工作实体类,工作:表示本批次需要处理的同性质任务(Task)的一个集合。 注意:返回给用户的不能是引用,防止线程安全事故发生。即不允许用户修改底层框架使用的东西。
  • TaskResult:任务处理返回结果实体类
  • TaskResultType:方法本身运行是否正确的结果类型
  • CheckJobProcesser:任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
  • PendingJobPool:框架的主体类,也是调用者主要使用的类
  • SleepTools:线程休眠辅助工具类
  • PDFTaskMutiThread:多线程版生成PDF

源码:

ITaskProcesser

package com.njust.pdfmutithread.system.vo;

/**
 *
 *
 *类说明:要求框架使用者实现的任务接口,因为任务的性质在调用时才知道,
 *所以传入的参数和方法的返回值均使用泛型
 */
public interface ITaskProcesser<T, R> {
	/**
	 * @param data 调用方法需要使用的业务数据
	 * @return 方法执行后业务方法的结果
	 */
	TaskResult<R> taskExecute(T data);
}

ItemVo

package com.njust.pdfmutithread.system.vo;

import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 类说明:存放到队列的元素
 */
public class ItemVo<T> implements Delayed {

    private long activeTime;//到期时间,单位毫秒
    private T date;

    //activeTime是个过期时长
    public ItemVo(long activeTime, T date) {
        super();
        this.activeTime = TimeUnit.NANOSECONDS.convert(activeTime,
                TimeUnit.MILLISECONDS) + System.nanoTime();//将传入的时长转换为超时的时刻
        this.date = date;
    }

    public long getActiveTime() {
        return activeTime;
    }

    public T getDate() {
        return date;
    }

    //按照剩余时间排序
    @Override
    public int compareTo(Delayed o) {
        long d = getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS);
        return (d == 0) ? 0 : ((d > 0) ? 1 : -1);
    }

    //返回元素的剩余时间
    @Override
    public long getDelay(TimeUnit unit) {
        long d = unit.convert(this.activeTime - System.nanoTime(),
                TimeUnit.NANOSECONDS);
        return d;
    }


}

JobInfo

package com.njust.pdfmutithread.system.vo;


import com.njust.pdfmutithread.system.CheckJobProcesser;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * 类说明:提交给框架执行的工作实体类,工作:表示本批次需要处理的同性质任务(Task)的一个集合
 * 注意:返回给用户的不能是引用,防止线程安全事故发生。即不允许用户修改底层框架使用的东西
 */
public class JobInfo<R> {
    //区分唯一的工作
    private final String jobName;
    //工作的任务个数
    private final int jobLength;
    //这个工作的任务处理器
    private final ITaskProcesser<?, ?> taskProcesser;
    //成功处理的任务数
    private final AtomicInteger successCount;
    //已处理的任务数
    private final AtomicInteger taskProcesserCount;
    //拿结果从头拿,放结果从尾部放 保存工作中每个任务的返回结果,只有处理完成成放进去
    private final LinkedBlockingDeque<TaskResult<R>> taskDetailQueue;
    //工作的完成保存的时间,超过这个时间从缓存中清除
    private final long expireTime;

    //阻塞队列不应该由调用者传入,应该内部生成,长度为工作的任务个数
    public JobInfo(String jobName, int jobLength,
                   ITaskProcesser<?, ?> taskProcesser,
                   long expireTime) {
        super();
        this.jobName = jobName;
        this.jobLength = jobLength;
        this.taskProcesser = taskProcesser;
        this.successCount = new AtomicInteger(0);
        this.taskProcesserCount = new AtomicInteger(0);
        this.taskDetailQueue = new LinkedBlockingDeque<>(jobLength);
        this.expireTime = expireTime;
    }

    //返回成功处理的结果数
    //不能直接返回 successCount 因为这是一个引用,会导致线程不安全,以下同理
    public int getSuccessCount() {
        return successCount.get();
    }

    //返回当前已处理的结果数
    public int getTaskProcesserCount() {
        return taskProcesserCount.get();
    }

    //提供工作中失败的次数,为了方便调用者使用
    public int getFailCount() {
        return taskProcesserCount.get() - successCount.get();
    }

    //获得工作中每个任务的处理详情
    //注意由于阻塞队列是加锁的,所以不存在写的时候读,多线程冲突
    public List<TaskResult<R>> getTaskDetail() {
        List<TaskResult<R>> taskList = new LinkedList<>();
        TaskResult<R> taskResult;
        //从阻塞队列中拿任务的结果,反复取,一直取到null为止,说明目前队列中最新的任务结果已经取完,可以不取了
        //既然是任务结果,肯定已经处理完成了,所以全部拿完是对的
        while ((taskResult = taskDetailQueue.pollFirst()) != null) {
            taskList.add(taskResult);
        }
        return taskList;
    }

    //从业务应用角度来说,保证最终一致性即可,不需要对方法加锁.
    //注意 从jvm内存角度,只有传递进来的参数需要操作的时候才需要新建一个变量接受,即栈变量
    public void addTaskResult(TaskResult<R> result, CheckJobProcesser checkJob) {
        if (TaskResultType.Success.equals(result.getResultType())) {
            successCount.incrementAndGet();
        }
        taskDetailQueue.addLast(result);
        taskProcesserCount.incrementAndGet();

        //工作队列满了,放入过期队列
        if (taskProcesserCount.get() == jobLength) {
            checkJob.putJob(jobName, expireTime);
        }

//        if (taskProcesserCount.get() >= jobLength) {
//            if (result.getReturnValue() == null) {
//                System.out.println("chen: " + taskProcesserCount.get());
//            } else {
//                System.out.println("chen: " + taskProcesserCount.get() + " : " + result.getReturnValue());
//            }
//
//        }
    }

    public String getTotalProcess() {
        return "Success[" + successCount.get() + "]/Current["
                + taskProcesserCount.get() + "] Total[" + jobLength + "]";
    }

    public ITaskProcesser<?, ?> getTaskProcesser() {
        return taskProcesser;
    }


}

TaskResult

package com.njust.pdfmutithread.system.vo;

/**
 *
 *类说明:任务处理返回结果实体类
 */
public class TaskResult<R> {
	//方法本身运行是否正确的结果类型
	private final TaskResultType resultType;
	//方法的业务结果数据;
	private final R returnValue;
	//这里放方法失败的原因
	private final String reason;
	
	public TaskResult(TaskResultType resultType, R returnValue, String reason) {
		super();
		this.resultType = resultType;
		this.returnValue = returnValue;
		this.reason = reason;
	}
	
	//方便业务人员使用,这个构造方法表示业务方法执行成功返回的结果
	public TaskResult(TaskResultType resultType, R returnValue) {
		super();
		this.resultType = resultType;
		this.returnValue = returnValue;
		this.reason = "Success";
	}

	public TaskResultType getResultType() {
		return resultType;
	}

	public R getReturnValue() {
		return returnValue;
	}

	public String getReason() {
		return reason;
	}


	@Override
	public String toString() {
		return "TaskResult{" +
				"resultType=" + resultType +
				", returnValue=" + returnValue +
				", reason='" + reason + '\'' +
				'}';
	}
}

TaskResultType

package com.njust.pdfmutithread.system.vo;

/**
 * 类说明:方法本身运行是否正确的结果类型
 */
public enum TaskResultType {
    //方法成功执行并返回了业务人员需要的结果
    Success,
    //方法成功执行但是返回的是业务人员不需要的结果
    Failure,
    //方法执行抛出了Exception
    Exception;


}

CheckJobProcesser

package com.njust.pdfmutithread.system;


import com.njust.pdfmutithread.system.vo.ItemVo;

import java.util.concurrent.DelayQueue;

/**
 * 类说明:任务完成后,在一定的时间供查询,之后为释放资源节约内存,需要定期处理过期的任务
 */
public class CheckJobProcesser {
    //DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素
    private static DelayQueue<ItemVo<String>> queue
            = new DelayQueue<>();//存放已完成任务等待过期的队列

    //单例模式------
    private CheckJobProcesser() {
    }

    private static class ProcesserHolder {
        public static CheckJobProcesser processer = new CheckJobProcesser();
    }

    public static CheckJobProcesser getInstance() {
        return ProcesserHolder.processer;
    }
    //单例模式------

    //处理队列中到期任务的实行
    private static class FetchJob implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    //拿到已经过期的任务 take()是阻塞方法
                    ItemVo<String> item = queue.take();
                    String jobName = item.getDate();
                    PendingJobPool.getMap().remove(jobName);
                    System.out.println(jobName + " is out of date,remove from map!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /*任务完成后,放入队列,经过expireTime时间后,从整个框架中移除*/
    public void putJob(String jobName, long expireTime) {
        //实现名称和到期时间绑定
        ItemVo<String> item = new ItemVo<String>(expireTime, jobName);
        queue.offer(item);
        System.out.println("Job[" + jobName + "已经放入了过期检查缓存,过期时长:" + expireTime);
    }

    static {
        Thread thread = new Thread(new FetchJob());
        thread.setDaemon(true);
        thread.start();
        System.out.println("开启任务过期检查守护线程................");
    }


}


PendingJobPool

package com.njust.pdfmutithread.system;


import com.njust.pdfmutithread.system.vo.ITaskProcesser;
import com.njust.pdfmutithread.system.vo.JobInfo;
import com.njust.pdfmutithread.system.vo.TaskResult;
import com.njust.pdfmutithread.system.vo.TaskResultType;

import java.util.List;
import java.util.Map;
import java.util.concurrent.*;

/**
 * 框架的主体类,也是调用者主要使用的类
 */
public class PendingJobPool {

    //保守估计
    private static final int THREAD_COUNTS =
            Runtime.getRuntime().availableProcessors();
    //任务队列 注意: 框架尽量避免使用无界队列 防止用户无限制上传
    private static BlockingQueue<Runnable> taskQueue
            = new ArrayBlockingQueue<>(5000);
    //线程池,固定大小,有界队列
    private static ExecutorService taskExecutor =
            new ThreadPoolExecutor(THREAD_COUNTS, THREAD_COUNTS, 60,
                    TimeUnit.SECONDS, taskQueue);
    //job的存放容器
    //G<?> 与 G<? extends Object>等同,如List<?> 与List<? extends Objext>等同。
    private static ConcurrentHashMap<String, JobInfo<?>> jobInfoMap
            = new ConcurrentHashMap<>();

    private static CheckJobProcesser checkJob
            = CheckJobProcesser.getInstance();

    public static Map<String, JobInfo<?>> getMap() {
        return jobInfoMap;
    }

    //单例模式------注意:限制用户使用的线程数,防止应用崩掉
    private PendingJobPool() {
    }

    //懒加载式的线程安全
    private static class JobPoolHolder {
        public static PendingJobPool pool = new PendingJobPool();
    }

    public static PendingJobPool getInstance() {
        return JobPoolHolder.pool;
    }
    //单例模式------

    //对工作中的任务进行包装,提交给线程池使用,并处理任务的结果,写入缓存以供查询
    private static class PendingTask<T, R> implements Runnable {

        private JobInfo<R> jobInfo;
        private T processData;

        public PendingTask(JobInfo<R> jobInfo, T processData) {
            super();
            this.jobInfo = jobInfo;
            this.processData = processData;
        }

        //告诉编译器忽略指定的警告,不用在编译完成后出现警告信息。
        @SuppressWarnings("unchecked")
        @Override
        public void run() {
            R r = null;
            ITaskProcesser<T, R> taskProcesser =
                    (ITaskProcesser<T, R>) jobInfo.getTaskProcesser();
            TaskResult<R> result = null;

            //用户的代码是最不可信的,所以防止用户程序抛出异常,需要try catch处理
            try {
                //调用业务人员实现的具体方法
                result = taskProcesser.taskExecute(processData);
                //要做检查,防止开发人员处理不当
                if (result == null) {
                    result = new TaskResult<R>(TaskResultType.Exception, r,
                            "result is null");
                }
                if (result.getResultType() == null) {
                    if (result.getReason() == null) {
                        result = new TaskResult<R>(TaskResultType.Exception, r, "reason is null");
                    } else {
                        result = new TaskResult<R>(TaskResultType.Exception, r,
                                "result is null,but reason:" + result.getReason());
                    }
                }
            } catch (Exception e) {
//                e.printStackTrace();
                result = new TaskResult<R>(TaskResultType.Exception, r,
                        e.getMessage());
            } finally {
                jobInfo.addTaskResult(result, checkJob);
            }
        }
    }

    //根据工作名称检索工作
    @SuppressWarnings("unchecked")
    private <R> JobInfo<R> getJob(String jobName) {
        JobInfo<R> jobInfo = (JobInfo<R>) jobInfoMap.get(jobName);
        if (null == jobInfo) {
            throw new RuntimeException(jobName + "是个非法任务、或该任务已经过期!");
        }
        return jobInfo;
    }

    //调用者提交工作中的任务
    public <T, R> void putTask(String jobName, T t) {
        JobInfo<R> jobInfo = getJob(jobName);
        PendingTask<T, R> task = new PendingTask<>(jobInfo, t);
        taskExecutor.execute(task);
    }

    //调用者注册工作,如工作名,任务的处理器等等,名称唯一,相当于ID
    public <R> void registerJob(String jobName, int jobLength,
                                ITaskProcesser<?, ?> taskProcesser, long expireTime) {
        JobInfo<R> jobInfo = new JobInfo<>(jobName, jobLength, taskProcesser, expireTime);
        // 不能使用put,防止任务在处理的时候,用户修改map,导致处理异常
        if (jobInfoMap.putIfAbsent(jobName, jobInfo) != null) {
            throw new RuntimeException(jobName + "已经注册了!");
        }
    }

    //获得每个任务的处理详情
    public <R> List<TaskResult<R>> getTaskDetail(String jobName) {
        JobInfo<R> jobInfo = getJob(jobName);
        return jobInfo.getTaskDetail();
    }

    //获得工作的整体处理进度
    public <R> String getTaskProgess(String jobName) {
        JobInfo<R> jobInfo = getJob(jobName);
        return jobInfo.getTotalProcess();
    }

}

PDFTaskMutiThread

package com.njust.pdfmutithread.user.task;


import com.itextpdf.text.DocumentException;
import com.njust.pdfmutithread.system.vo.ITaskProcesser;
import com.njust.pdfmutithread.system.vo.TaskResult;
import com.njust.pdfmutithread.system.vo.TaskResultType;
import com.njust.pdfmutithread.user.PDFService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/26 22:06
 * @description: 多线程版生成PDF
 */
@Component
public class PDFTaskMutiThread implements ITaskProcesser<String, Integer> {

    @Autowired
    private PDFService pdfService;

    @Override
    public TaskResult<Integer> taskExecute(String data) {
        createOne(data + ".pdf");
        return new TaskResult<>(TaskResultType.Success, Integer.valueOf(data));
    }

    public void createOne(String fileName) {
        //需要填充的数据
        Map<String, Object> data = new HashMap<>(16);
        String content = pdfService.freeMarkerRender(data, pdfService.getHtml());
        // 当content 为null时 ,不停尝试
        while (content == null) {
            content = pdfService.freeMarkerRender(data, pdfService.getHtml());
        }
        String temp = pdfService.getDest() + fileName;
        //创建pdf
        try {
            pdfService.createPdf(content, temp);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (DocumentException e) {
            e.printStackTrace();
        }

    }
}

修改PDFServiceTest类,添加多线程版的测试方法。完整代码如下:

package com.njust.pdfmutithread.user;


import com.njust.pdfmutithread.system.PendingJobPool;
import com.njust.pdfmutithread.system.vo.TaskResult;
import com.njust.pdfmutithread.user.task.PDFTaskMutiThread;
import com.njust.pdfmutithread.user.task.PDFTaskSingThread;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * @author Chen
 * @version 1.0
 * @date 2020/3/26 21:55
 * @description:
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest
public class PDFServiceTest {

    @Autowired
    private PDFTaskSingThread pdfTaskSingThread;


    public static void printTime(long start, long end) {
        long spendTime = end - start;
        System.out.println("spendTime: " + spendTime);
    }

    @Test
    public void createOne() {
        long start = System.currentTimeMillis();
        pdfTaskSingThread.createOne();
        long end = System.currentTimeMillis();
        printTime(start, end);
    }

    //  JOB_LENGTH:260      spendTime: 17310
    //  JOB_LENGTH:512      spendTime: 30660
    //  JOB_LENGTH:1024      spendTime: 59573
    @Test
    public void createManyWithOneThread() {
        long start = System.currentTimeMillis();
        pdfTaskSingThread.createManyWithOneThread(JOB_LENGTH);
        long end = System.currentTimeMillis();
        printTime(start, end);

    }


    private final static String JOB_NAME = "生成PDF";
    private final static int JOB_LENGTH = 1024;

    @Autowired
    private PDFTaskMutiThread pdfTaskMutiThread;

    //查询任务进度的线程
    private static class QueryResult implements Runnable {

        private PendingJobPool pool;

        public QueryResult(PendingJobPool pool) {
            super();
            this.pool = pool;
        }

        @Override
        public void run() {
            int num = 0;
            List<TaskResult<String>> taskDetail;
            try {
                while (num < JOB_LENGTH) {
                    taskDetail = pool.getTaskDetail(JOB_NAME);
                    if (!taskDetail.isEmpty()) {
                        num += taskDetail.size();
                    }
                }
            } catch (Exception e) {

            }
            end_chen = System.currentTimeMillis();
            PDFServiceTest.printTime(start_chen, end_chen);
        }

    }


    private static long start_chen;
    private static long end_chen;

    //    JOB_LENGTH:260 spendTime: 17599
    //    JOB_LENGTH:512 spendTime: 33531
    //    JOB_LENGTH:1024 spendTime: 56144
    @Test
    public void testPDFTaskMutiThread() throws InterruptedException {
        start_chen = System.currentTimeMillis();
        //拿到框架的实例
        PendingJobPool pool = PendingJobPool.getInstance();
        //注册job
        pool.registerJob(JOB_NAME, JOB_LENGTH, pdfTaskMutiThread, 1000 * 50);
        // 假定用户不按套路出牌
        for (int i = 0; i < JOB_LENGTH; i++) {
            //依次推入Task
            pool.putTask(JOB_NAME, String.valueOf(i));
        }
        Thread t = new Thread(new QueryResult(pool));
        t.start();
        t.join();
    }

}

可以发现当生成的PDF较多时,多线程占优势。但是苦逼的我电脑配置实在是太差了。能跑出这个效果已经谢天谢帝了。
在这里插入图片描述
多线程版的时序图,如上图所示。

问题

多线程生成PDF会报异常,但是工作框架具有完美的异常处理能力。即使有异常,但是依然可以正确生成PDF。但是为什么出现异常还没有搞明白。等我搞明白了,再更新一下。睡觉了。。。各位小伙伴晚安!!!

总结

多线程在IO密集型任务明显占有优势。但是实现起来真的烧脑啊。各位读者有什么问题,欢迎评论交流!

发布了22 篇原创文章 · 获赞 4 · 访问量 22万+

猜你喜欢

转载自blog.csdn.net/qq_32510597/article/details/105131003