分类 Java 下的文章

Guava的Strings.repeat

如果要自己实现一个repeat的话,最容易想到的可能会这样(示例不考虑int溢出的情况):

public static String repeat(String string, int count) {
    int length = string.length() * count;
    StringBuilder sb = new StringBuilder(length);
    for (int i = 0; i < count; i++) {
        sb.append(string);
    }
    return sb.toString();
}

用图画出来应该是这个样子的:

guava-strings-repeat (1).png

看到guava的实现,在位运算的时候还是小愣了一下,性能是比上面的实现好多了:

public static String repeat(String string, int count) {
  // 先去掉检测相关的判断,只看核心的实现
  final int len = string.length();
  final long longSize = (long) len * (long) count;
  final int size = (int) longSize;

  final char[] array = new char[size];
  string.getChars(0, len, array, 0);
  int n;
  for (n = len; n < size - n; n <<= 1) {
    System.arraycopy(array, 0, array, n, n);
  }
  System.arraycopy(array, 0, array, n, size - n);
  return new String(array);
}

用图画出来是这个样子的:

guava-strings-repeat.png

重点是这段代码:

  for (n = len; n < size - n; n <<= 1) {
    System.arraycopy(array, 0, array, n, n);
  }
  System.arraycopy(array, 0, array, n, size - n);

看下php的内核实现,其实原理一样,就是把现有的字串翻倍,当 [达到一半的长度, count为偶数] 或者 [刚超过一半的长度时, count为奇数] 做最后一次连接,只是写法不太一样:

// sed -n '5012,5058p' /usr/local/src/php-7.0.9/ext/standard/string.c
PHP_FUNCTION(str_repeat)
{
    zend_string        *input_str;        /* Input string */
    zend_long         mult;            /* Multiplier */
    zend_string    *result;        /* Resulting string */
    size_t        result_len;        /* Length of the resulting string */

    if (zend_parse_parameters(ZEND_NUM_ARGS(), "Sl", &input_str, &mult) == FAILURE) {
        return;
    }

    if (mult < 0) {
        php_error_docref(NULL, E_WARNING, "Second argument has to be greater than or equal to 0");
        return;
    }

    /* Don't waste our time if it's empty */
    /* ... or if the multiplier is zero */
    if (ZSTR_LEN(input_str) == 0 || mult == 0)
        RETURN_EMPTY_STRING();

    /* Initialize the result string */
    result = zend_string_safe_alloc(ZSTR_LEN(input_str), mult, 0, 0);
    result_len = ZSTR_LEN(input_str) * mult;

    /* Heavy optimization for situations where input string is 1 byte long */
    if (ZSTR_LEN(input_str) == 1) {
        memset(ZSTR_VAL(result), *ZSTR_VAL(input_str), mult);
    } else {
        char *s, *e, *ee;
        ptrdiff_t l=0;
        memcpy(ZSTR_VAL(result), ZSTR_VAL(input_str), ZSTR_LEN(input_str));
        s = ZSTR_VAL(result);
        e = ZSTR_VAL(result) + ZSTR_LEN(input_str);
        ee = ZSTR_VAL(result) + result_len;

        while (e<ee) {
            l = (e-s) < (ee-e) ? (e-s) : (ee-e);
            memmove(e, s, l);
            e += l;
        }
    }

    ZSTR_VAL(result)[result_len] = '\0';

    RETURN_NEW_STR(result);
}

媒体中心设计分享

更详细的内容可以查看文章结尾的PDF

媒体文件处理

magic bytes 校验
扩展名校验
文件名校验
mime校验
速率限制

图片的处理

  1. (水印颜色的优化) 颜色深浅的判断

  2. 图片中心的判断

  3. 颜色色差的聚类

音频的处理

视频的处理

http api跨域

iframe

同源策略,document.domain = tool.lu

jsonp

这种跨域实际上是在页面中动态插入一个 <script> 标签,然后在被加载的脚本中执行当前网页里的函数;显而易见,只支持GET请求。

ajax2

HTTP access control (CORS) - Cross-Origin Resource Sharing

  1. 简单请求

  2. 预检请求

http返回头

Access-Control-Allow-Origin: <origin> | *
Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header
Access-Control-Max-Age: <delta-seconds>
Access-Control-Allow-Credentials: true | false
Access-Control-Allow-Methods: <method>[, <method>]*
Access-Control-Allow-Headers: <field-name>[, <field-name>]*
static const auto maxPreflightCacheTimeout = std::chrono::seconds(600);  

http请求头

Origin: <origin>
Access-Control-Request-Method: <method>
Access-Control-Request-Headers: <field-name>[, <field-name>]*

默认情况下 跨域XHR 是不会带上Cookie的,需要设置 withCredentials = true;

preflight.png
request.png

附件:mediacenter.pdf

使用javassist修改idea mybatis插件

反编译查看源码

cd ~/Library/Application Support/IntelliJIdea15/mybatis_plus/lib/

使用 JD-GUI 打开 mybatis_plus.jar,查看源码:

Screenshot 2016-03-05 at 12.19.56.png
Screenshot 2016-03-05 at 12.19.53.png

修改验证逻辑

使用javassist修改字节码

import javassist.*;

class MyCrack {

    public static void main(String[] args) throws Exception {
        ClassPool pool = ClassPool.getDefault();
        CtClass c = pool.get("com.seventh7.mybatis.util.JavaUtils");
        CtMethod m = c.getDeclaredMethod("refValid");
        m.setBody("{ validated = true; valid = true; return valid; }");
        c.writeFile();

        CtClass cc = pool.get("com.seventh7.mybatis.service.JavaService");
        CtMethod mm = cc.getDeclaredMethod("stop");
        mm.setBody("{ return; }");
        cc.writeFile();
    }

}
# 运行
javac -classpath ".:javassist.jar:mybatis_plus.jar" MyCrack.java
java -classpath ".:javassist.jar:mybatis_plus.jar" MyCrack

此时会在当前目录下生成修改过的两个类文件,使用压缩软件替换jar包中的这两个文件;重启idea。

com/seventh7/mybatis/service/JavaService.class
com/seventh7/mybatis/util/JavaUtils.class

在JAVA中实现PHP的gzuncompress

背景

在系统改造的时候,从php迁移到java;由于php中为了节省redis的内存,对缓存的数据做了 gzcompress 处理;为了能读取出数据,有两套方案:

  1. 刷数据,将redis中老的数据清理掉,去掉 gzcompress 的步骤(缺点:刷数据的时间,和读取代码上线的时间点无法吻合;数据的写入入口比较多,容易遗漏)

  2. java中读取的时候可以进行 gzuncompress

一些知识

知道这些知识候就能避免我在实现过程中遇到的很多问题。

PHP中的 gzcompress

This function compresses the given string using the ZLIB data format.
Note:
This is not the same as gzip compression, which includes some header data. See gzencode() for gzip compression.

一直以为 gzcompress 就是 gz 的压缩,php中使用的 zlib 来压缩,压缩完的结果中携带了头信息,直接使用 gz 解压是不认这种格式的。

JAVA中的 new String(byte[])

java.lang.StringCoding.StringDecoder 当在编码 byte[] 不能处理的时候会进行一些处理;所以说 (new String(compressedByte)).getBytes()compressedByte 并不一定会完全一样。

说到这里就可以看下 jedis 提供的接口了,刚开始我是使用的 String get(String key),于是由于上面的原因,当我用这个返回值 getBytes() 的时候就已经发生了变化。正确的使用方法应该是使用 byte[] get(byte[] key),由于比较繁琐,封装一下。

实现

    public static String get(Jedis jedis, String key) {
        byte[] byteKey = key.getBytes();
        byte[] element = jedis.get(byteKey);
        return new String(gzuncompress(element));
    }

    public static List<String> mget(Jedis jedis, List<String> keys) {
        byte[][] byteKeys = new byte[keys.size()][];
        for (int i = 0; i < keys.size(); i++) {
            byteKeys[i] = keys.get(i).getBytes();
        }
        List<byte[]> elements = jedis.mget(byteKeys);
        List<String> result = new ArrayList<>();
        for (byte[] element : elements) {
            result.add(new String(gzuncompress(element)));
        }
        return result;
    }

    public static byte[] gzuncompress(byte[] data) {
        byte[] unCompressed = null;
        ByteArrayOutputStream bos = new ByteArrayOutputStream(data.length);
        Inflater deCompressor = new Inflater();
        try {
            deCompressor.setInput(data);
            final byte[] buf = new byte[1024];
            while (!deCompressor.finished()) {
                int count = deCompressor.inflate(buf);
                bos.write(buf, 0, count);
            }
            unCompressed = bos.toByteArray();
            bos.close();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            deCompressor.end();
        }

        return unCompressed;
    }

JAVA WEB乱码解决

设置环境变量

export $LANG="UTF-8"

设置tomcat接收GET参数时候的编码 server.xml (tomcat中的配置文件)

<Connector
    ....
    URIEncoding="UTF-8">

设置接收POST参数时候的编码

web工程中web.xml的设置

必须要放在所有filter之前

    <filter>
        <filter-name>encodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

对于读多写少的少量数据的缓存优化

前提:数据量比较少,并且读多写少,实时性要求不高的数据。

优点

  1. 访问本地cache节省了网络开销,减少中心cache集群(redis)的压力

  2. 正常的web机器内存利用率较低,减少web机内存资源的浪费

类库的选择

google guava cache

  1. LRU失效机制

  2. 流畅的api接口

  3. 使用内存缓存

  4. 配套stats接口

流程

未命名文件.png

  1. [被动失效] controller层访问cache,若获取不到数据,从db中获取数据,并刷到cache

  2. [主动失效] 当db数据变更时,主动失效缓存;注意:这里cache是在每台web机器都有一份,所以每台机器都需要刷一遍;所以我们需要一个配置中心;在web机器监听配置中心的变化,然后刷新各自机器的cache

  3. 定时将本机的cache的 命中率,量 上报到监控系统

主动失效机制

  1. 实时性要求较高的可以订阅mysql的binlog

  2. 实时性要求不高的可以定时跑crontab

Spring RequestParam过于智能

POST到后端的数据是这样的

keywords[]: 关键词1,关键词2
keywords[]: 关键词3,关键词4
@RequestParam(value = "keywords[]") List<String> keywords;

然而经过Spring的RequestParam处理之后,却变成了 ["关键词1", "关键词2", "关键词3", "关键词4"];对于 Spring 这种过分聪明的行为我表示很呵呵~

防止这种解析的办法就是覆盖掉原有的converters;不使用英文逗号为分隔符。

<mvc:annotation-driven conversion-service="conversionService"/>

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="lu.tool.util.StringToArrayConverter" />
        </set>
    </property>
</bean>
package lu.tool.console.util;

import org.springframework.core.convert.converter.Converter;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;

/**
 * Created by xiaozi on 8/12/15.
 */
public class StringToArrayConverter implements Converter<String, List<String>>{
    @Override
    public List<String> convert(String source) {
        return Arrays.asList(StringUtils.delimitedListToStringArray(source, ";"));
    }
}

fastjson处理Map的 key 为 int 的bug

JSON 中的 MAP 是不支持 int 为 key 的;

Map<Integer, String> map = new HashMap<>();
map.put(1, "one");
map.put(2, "two");
System.out.println(JSON.toJSONString(map));
// {1:"one",2:"two"}

标准的json规范中,要求对象的key必须为string

以库的形式调用YUI Compressor

import com.yahoo.platform.yui.compressor.CssCompressor;
import org.apache.commons.io.IOUtils;
import java.io.*;

public class YUIProcessor {
    public String compressCss(String code) {
        Reader in = new InputStreamReader(IOUtils.toInputStream(code));
        try (Writer out = new StringWriter()) {
            CssCompressor compressor = new CssCompressor(in);
            compressor.compress(out, -1);
            out.flush();
            return out.toString();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return "";
    }
}

追加:

yui compresser 很久没有更新,有很多的不足,已经废弃使用。

java Integer中highestOneBit和bitCount的实现原理

highestOneBit

图中的黑色位都可以不关注,然后基本原理就是这样的 (以8位作为例子)

QQ20150110-1.png

bitCount

0x55555555
    01010101010101010101010101010101
0x33333333
    00110011001100110011001100110011
0xf0f0f0f
    00001111000011110000111100001111
0x3f
    00000000000000000000000000111111

QQ20150110-2.png

java杂记

效率

先来个 Apache Commons 里面没有的

xiaozi/java-helpers

/*
[
    {id: 1, name: "name1", gender: 1},
    {id: 2, name: "name2", gender: 1},
    {id: 3, name: "name1", gender: 2},
]
*/

/*
[1, 2]
*/
CollectionUtils.pluck(persons, "id", new ArrayList<Integer>())

/*
{
    1: [
        {id: 1, name: "name1", gender: 1},
        {id: 2, name: "name2", gender: 1},
    ],
    2: [
        {id: 3, name: "name1", gender: 2},
    ]
}
*/
CollectionUtils.groupBy(persons, "gender", Integer.class)

/*
去重
*/
CollectionUtils.unique(integers, new ArrayList<Integer>())

Apache Commons 里面的

StringUtils

StringUtils.html

StringUtils.isEmpty(); // null 和 空字符串
StringUtils.isBlank(); // null 和 空字符串 和 全空白字符串
StringUtils.trim(); // 截取掉字符串两侧的空格
StringUtils.strip(); // 截取掉字符串两侧的空白字符串
StringUtils.equalsIgnoreCase(); // 忽略大小写的比较
StringUtils.indexOf(); // 字符/字符串在指定字符串中第一次出现的位置
StringUtils.indexOfIgnoreCase();
StringUtils.lastIndexOf();
StringUtils.contains(); // 是否包含子符串
StringUtils.left();
StringUtils.right();
StringUtils.mid(); // 截取字符串
StringUtils.split(); // 分割字符串
StringUtils.join(); // join数组为字符串
StringUtils.repeat(); // 重复输出字符串
StringUtils.leftPad();
StringUtils.rightPad(); // 填充字符串
StringUtils.lowerCase();
StringUtils.upperCase();
StringUtils.capitalize(); // 大写首字母
StringUtils.reverse(); // 倒转字符串

额,大多数提供的都跟PHP里面一样

CollectionUtils

CollectionUtils.html

CollectionUtils.union(); // 并集
CollectionUtils.intersection(); // 交集
CollectionUtils.containsAny(); // 是否存在交集
CollectionUtils.disjunction(); // 交集的补集
CollectionUtils.subtract(); // 差集
CollectionUtils.filter(); // 过滤

空List的返回

// new ArrayList<>();
Collections.emptyList();

片段

检测端口可用,(绑定,并关掉),来自 Canal

public static boolean isAvailablePort(int port) {
    ServerSocket ss = null;
    try {
        ss = new ServerSocket(port);
        ss.bind(null);
        return true;
    } catch (IOException e) {
        return false;
    } finally {
        if (ss != null) {
            try {
                ss.close();
            } catch (IOException e) {
            }
        }
    }
}

利用字符串标记,优雅解决内外配置文件的加载

// ...
private static final String CLASSPATH_URL_PREFIX = "classpath:";
// ...
    String conf = System.getProperty("canal.conf", "classpath:canal.properties");
    Properties properties = new Properties();
    if (conf.startsWith(CLASSPATH_URL_PREFIX)) {
        conf = StringUtils.substringAfter(conf, CLASSPATH_URL_PREFIX);
        properties.load(CanalLauncher.class.getClassLoader().getResourceAsStream(conf));
    } else {
        properties.load(new FileInputStream(conf));
    }

java动态加载jar包

目录结构如下

.
├── main
│   ├── java
│   │   └── lu
│   │       └── tool
│   │           └── jar
│   │               ├── InterfaceRunner.java
│   │               └── Loader.java
│   └── resources
└── test
    ├── java
    └── resources

InterfaceRunner.java 为挂载 jar 中类的实现接口
Loader.java 为jar的加载器和执行器

所有第三方包的jar路径,通过 web 界面管理,然后存储在一个文件中,这里不实现 web 的管理。

InterfaceRunner.java:

package lu.tool.jar;

/**
 * Created by xiaozi on 11/29/14.
 */
public interface InterfaceRunner {

    public void fire();

}

Loader.java

package lu.tool.jar;

import java.io.*;
import java.net.URL;
import java.net.URLClassLoader;

/**
 * Created by xiaozi on 11/29/14.
 */
public class Loader {

    public static void main(String[] args) {
        String configFile = System.getProperty("jar.conf");
        if (configFile == null || configFile.isEmpty()) {
            System.exit(1);
        }
        System.out.println(configFile);
        File file = new File(configFile);
        try {
            BufferedReader in = new BufferedReader(new FileReader(file));
            String s;
            while ((s = in.readLine()) != null) {
                if (s.isEmpty()) continue;
                System.out.println(s);
                URL url = new URL(s);
                URLClassLoader myClassLoader = new URLClassLoader(new URL[] {url}, Thread.currentThread().getContextClassLoader());
                Class<?> myClass = myClassLoader.loadClass("lu.tool.jar.Runner");
                InterfaceRunner action = (InterfaceRunner) myClass.newInstance();
                // 达到指定条件的时候触发,这里仅是个演示
                // 在没有优先级的执行条件下应该使用子进程的方式,防止其中的一个crash掉
                action.fire();
                myClassLoader.close();
                System.out.println("done");
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

}

jar.conf 存放 jar 的本地的绝对路径

file:///Users/xiaozi/Documents/testrunner/out/artifacts/testrunner_jar/testrunner.jar

被挂载jar包中的类实现

package lu.tool.jar;

/**
 * Created by xiaozi on 11/29/14.
 */
public class Runner implements InterfaceRunner {

    @Override
    public void fire() {
        System.out.println("Hello, I'm in another jar!");
    }

}

引用:java动态加载jar包,并运行其中的类和方法

访问可视化

网站的统计

由于 tool.lu 的流量还不是很大,所以我把每次的访问记录都存到了MySQL(如果流量大,这么做是作死的节奏)

主要流程图

design1.jpg

design2.jpg

使用canal做异步处理,主要是因为

  1. ip => city的映射,可能要调用第三方接口比较耗时

  2. 网站代码处不需要写2份数据

  3. 装x

其中cannal分发的数据处理是用的java,本打算sse也用java的netty来实现了,惭愧,尝试未果后就放弃了,最后用golang实现的。

这样做不会太耗性能,而且每秒钟往客户端传输一次数据,但是由于 vps 的内存有限,java 又比较吃内存,所以上线之后就直接下线了。

java中的线程池

为什么要使用线程池

  1. 重复利用已创建的线程降低线程创建和销毁造成的消耗

  2. 提高线程的可管理性,可进行统一的分配,调优和监控

线程池的处理流程

threadpool.jpg

ExecutorService

newCachedThreadPool();
newFixedThreadPool();
newScheduledThreadPool();
newSingleThreadExecutor();

ScheduledThreadPool使用优先级队列进行排序(距离下次调度间隔短的任务排在前面)
DelayedWorkQueue


package lu.tool.demo;

import java.util.concurrent.*;

/**
 * Created by xiaozi on 14-8-28.
 */
public class CachedThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        ThreadPoolExecutor tpe = (ThreadPoolExecutor) es;

//        tpe.setMaximumPoolSize(100);

        Future<?> future = null;
//        for (int i = 1; i < 8000; i++) {
        for (int i = 1; i < 100; i++) {
            future = tpe.submit(new TaskDemo());
        }

        System.out.println("largest pool size: " + tpe.getLargestPoolSize());
        System.out.println("task count: " + tpe.getTaskCount());

        if (future != null) {
            try {
                future.get();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

//        tpe.shutdown();
    }
}
package lu.tool.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * Created by xiaozi on 14-8-28.
 */
public class FixedThreadPoolDemo {

    public static void main(String[] args) {
        ExecutorService es = Executors.newFixedThreadPool(100);
        ThreadPoolExecutor tpe = (ThreadPoolExecutor) es;

        for (int i = 1; i < 8000; i++) {
            tpe.submit(new TaskDemo());
        }

        tpe.shutdown();
    }
}
package lu.tool.demo;

import java.util.concurrent.*;

/**
 * Created by xiaozi on 14-8-28.
 */
public class ScheduledThreadPoolDemo {

    public static void main(String[] args) {
        ScheduledExecutorService es = Executors.newScheduledThreadPool(100);

        for (int i = 1; i < 8000; i++) {
            es.schedule(new TaskDemo(), 3, TimeUnit.SECONDS);
        }

        // scheduleAtFixedRate
        // scheduleWithFixedDelay

        es.shutdown();
    }
}
package lu.tool.demo;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by xiaozi on 14-8-28.
 */
public class SingleThreadExecutorDemo {

    public static void main(String[] args) {
        ExecutorService es = Executors.newSingleThreadExecutor();

        for (int i = 1; i < 8000; i++) {
            es.submit(new TaskDemo());
        }

        es.shutdown();
    }
}

ThreadPoolExecutor

ThreadPoolExecutor(int corePoolSize,
                    int maximumPoolSize,
                    long keepAliveTime,
                    TimeUnit unit,
                    BlockingQueue<Runnable> workQueue,
                    RejectedExecutionHandler handler);
参数解释
corePoolSize线程池维护线程的最少数量
maximumPoolSize线程池维护线程的最大数量
keepAliveTime线程池维护线程所允许的空闲时间
unit线程池维护线程所允许的空闲时间的单位
workQueue线程池所使用的缓冲队列
handler线程池对拒绝任务的处理策略
package lu.tool.demo;


import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Created by xiaozi on 14-8-29.
 */
public class ThreadPoolExecutorDemo {

    public static void main(String[] args) {
        ThreadPoolExecutor tpe = new ThreadPoolExecutor(3, 5, 60,
                                                        TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(10),
                                                        new ThreadPoolExecutor.CallerRunsPolicy());

        for (int i = 1; i < 8000; i++) {
            tpe.submit(new TaskDemo());
        }

        tpe.shutdown();
    }
}

阻塞队列

ArrayBlockingQueu   // 有界
LinkedBlockingQueue // 无界
SynchronousQueue
PriorityBlockingQueue

线程池的监控

beforeExecute(Thread t, Runnable r)
afterExecute(Runnable r, Throwable t)

executor.getTaskCount();
executor.getCompletedTaskCount();

executor.getLargestPoolSize();

executor.isShutdown();  
executor.isTerminated();

多少线程合适

  1. CPU密集型任务(ncpu + 1)

  2. I/O密集型任务(2 * ncpu)

  3. 任务的执行时间

  4. 任务的依赖性

阻塞队列的实现

线程池队列
ScheduledThreadPoolDelayedWorkQueueReentrantLock.newConditionavailable
FixedThreadPoolLinkedBlockingQueueReentrantLock.newConditionput, take
CachedThreadPoolSynchronousQueueReentrantLock
SingleThreadLinkedBlockingQueueReentrantLock.newConditionput, take

引用

  1. 聊聊并发(三)——JAVA线程池的分析和使用

  2. 线程池

  3. Java四种线程池的使用

  4. 多线程之线程池探索

  5. 深入浅出多线程(4)对CachedThreadPool OutOfMemoryError问题的一些想法

  6. ScheduledThreadPoolExecutor实现原理

  7. JAVA线程池ThreadPoolExecutor

  8. JAVA线程池学习以及队列拒绝策略