Java 线程池 阻塞提交任务

场景

提交任务由单线程提交到线程池多线程处理,在线程池达到处理上线时可以在提交的线程阻塞等待。

方案

1.最常见的方案就是直接设置线程池的拒绝策略为 CallerRunsPolicy,当触发拒绝策略时,会将该任务直接在提交任务所在的线程直接运行该任务。这个方案有个小问题,当如果提交的任务负载很重导致提交任务的线程长时间阻塞,就会造成线程池的饥饿。

2.较可行但是有点恶心的方案,自定义阻塞策略在触发拒绝时获取任务队列阻塞提交。

public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
    try {
        if (!executor.isShutdown()) {
            executor.getQueue().put(r);
        }
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        throw new RejectedExecutionException("interrupted", e);
    }
}

这个方案也有问题:

如果你只有一个线程提交任务,而且任务的执行时间不可控,这个方案是我找到的算靠谱的了。

千万别这样用,你永远不知道别人会咋用你的线程池,一不小心就上当了!!!

3.较一般可行不那么恶心方案,自定义任务队列,直接让offer、and方法也阻塞

public class LimitedQueue<E> extends LinkedBlockingQueue<E> 
{
    public LimitedQueue(int maxSize)
    {
        super(maxSize);
    }

    @Override
    public boolean offer(E e)
    {
        // turn offer() and add() into a blocking calls (unless interrupted)
        try {
            put(e);
            return true;
        } catch(InterruptedException ie) {
            Thread.currentThread().interrupt();
        }
        return false;
    }

}

这个方案其实挺完美的,但是唯一的问题就是线程池永远只会有coreSize个线程,在任务队列达到上限时就直接阻塞了=.=丧失了线程池的伸缩能力。

4.优雅比较可行的方案,自定义实现线程池,用信号量控制同时进入的线程,这个方案代码很完美,但是操蛋的是无法精确控制线程数量。

public class BoundedExecutor extends ThreadPoolExecutor{

    private final Semaphore semaphore;

    public BoundedExecutor(int bound) {
        super(bound, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
        semaphore = new Semaphore(bound);
    }

    /**Submits task to execution pool, but blocks while number of running threads 
     * has reached the bound limit
     */
    public <T> Future<T> submitButBlockIfFull(final Callable<T> task) throws InterruptedException{

        semaphore.acquire();            
        return submit(task);                    
    }


    @Override
    protected void afterExecute(Runnable r, Throwable t) {
        super.afterExecute(r, t);

        semaphore.release();
    }
}
//这个方案也类似 他们问题也是一样的,就是任务执行完后线程并不是立马可用的,但semaphore释放了
class BlockingExecutor implements Executor {

    final Semaphore semaphore;
    final Executor delegate;

    private BlockingExecutor(final int concurrentTasksLimit, final Executor delegate) {
        semaphore = new Semaphore(concurrentTasksLimit);
        this.delegate = delegate;
    }

    @Override
    public void execute(final Runnable command) {
        try {
            semaphore.acquire();
        } catch (InterruptedException e) {
            e.printStackTrace();
            return;
        }

        final Runnable wrapped = () -> {
            try {
                command.run();
            } finally {
                semaphore.release();
            }
        };

        delegate.execute(wrapped);

    }
}

这个方案的问题就是

  • afterExecute这个方法不是线程执行完任务最后做的事情,也就是说线程执行完afterExecute还会要执行一些任务才能返回线程池,但是这个时候我们已经执行了semaphore.release(),任务进来以后发现没有线程可用又得创建一个线程!!
  • 这个方案只能实现coreSize==maxSize,如果你尝试将任务队列长度修改和信号量长度修改,你会发现由于问题1,你总会莫名其妙的就触发了拒绝策略了
  • 线程池里的线程总会比bound要多,而且如果你的任务很快完成(非常快那种),有可能创建非常多的线程。

Linux上使用Selenium运行有头浏览器

通常我们服务器vps是没有安装gui显示的,但是为了避免使用无头浏览器(很容易被识别),我们可以用Xvfb。

Xvfb

Xvfb是一个实现 X11 显示服务器协议的显示服务器。该程序将允许您以“无头”模式运行任何应用程序。基本上,这个程序不会在物理屏幕上输出 GUI,而是创建一个虚拟帧缓冲区并在那里“显示”UI。

安装

sudo apt-get install xvfb

使用

先启动一个xvfb服务 指定id,然后我们启动项目时也指定这个id运行

 
export DISPLAY=:7 指定变量
Xvfb -ac $DISPLAY -screen 0 1280x1024x8 //比如这样 启动一个服务 指定虚拟id是7  
//然后直接运行就可以 会根据环境变量找到xvfb服务的
 java -jar xxxx.jar

可以尝试这个脚本使用 https://gist.github.com/tsl0922/ab8d370a85653c4354ad
最好的方式就是启动一个xvfb服务 这样就可以直接使用有头浏览器了
创建 一个脚本 xvfb.sh 内容如下
#!/bin/bash

XVFB=/usr/bin/Xvfb
XVFBARGS="$DISPLAY -ac -screen 0 1024x768x16"
PIDFILE=${HOME}/xvfb_${DISPLAY:1}.pid
case "$1" in
  start)
    echo -n "Starting virtual X frame buffer: Xvfb"
    /sbin/start-stop-daemon --start --quiet --pidfile $PIDFILE --make-pidfile --background --exec $XVFB -- $XVFBARGS
    echo "."
    ;;
  stop)
    echo -n "Stopping virtual X frame buffer: Xvfb"
    /sbin/start-stop-daemon --stop --quiet --pidfile $PIDFILE
    echo "."
    ;;
  restart)
    $0 stop
    $0 start
    ;;
  *)
  echo "Usage: /etc/init.d/xvfb {start|stop|restart}"
  exit 1
esac
exit 0

启动方式 
bash xvfb.sh start

JackSon 注解笔记

超全的JackSon注解大全包含demo https://www.tutorialspoint.com/jackson_annotations/index.htm

  • @JsonProperty 属性注解 可以修改序列化别名、默认值、展示效果
  • @JsonView 属性和方法注解 可以在不同视图中显示不同的序列化
  • @JsonCreator 方法注解 控制序列化使用的构造函数
  • @JsonValue 属性注解 控制某属性作为序列化的唯一结果输出
  • @JsonUnwrapped 对象扁平化把子属性对象解构提取到根对象
  • @JsonTypeName 配合@JsonTypeInfo指定序列化时属性对应名称
  • @JsonTypeInfo 解决多态反序列化问题 这里的大佬总结的很好
  • @JsonSubTypes 解决多态序列化时,无法确认子类类型的问题
  • @JsonTypeIdResolver 多态序列化相关 这部分可以看这个大佬的总结
  • @JsonTypeId 指定序列化时的key值
  • @JsonRawValue 是否按照字面量序列化,就是序列化结果是字符”key”:”xxx”还是”key”:xxx
  • @JsonManagedReference, @JsonBackReference 解决循环引用 具体的案例看这里
  • @JsonInclude 可以实现根据条件过滤key或value值 比如隐藏所有null值不序列化
  • @JsonIgnoreType 标记在类上,所有以该类为类型的值都会被忽略
  • @JsonIgnoreProperties 标记在类上,控制该类哪些属性被隐藏,还可以控制遇到json字符串未知参数是否抛出异常 ignoreUnknown
  • @JsonIdentityReference @JsonIdentityInfo 解决循环引用问题 具体demo看这里 @JsonIdentityReference可以让第二次序列化的对象(存在循环引用的对象)序列化为它的id值,@JsonIdentityReference 设alwaysAsId=true会让对象直接序列化为它的id值
  • @JsonGetter 指定序列化时使用的方法,可用于定义非静态、无参数返回值(非 void)方法,用作逻辑属性的“getter”。它可以用作更通用 JsonProperty注释的替代方法(这是一般情况下的推荐选择)。Getter 是指在序列化具有该方法的类的 Object 实例时(可能继承自超类),通过该方法进行调用,并将返回值序列化为该属性的值。
  • @JsonFormat 序列化时指定格式,常用于各种时间格式
  • @JsonFilter 定义一个过滤器的名称 在序列化时根据该名称指定序列化器 具体demo看这里
  • @JsonAnySetter 该注解允许我们把一个可变的map属性作为标准属性, 在反序列过程中, 从Json字符串得到的属性值会加入到map属性中,可以用它来存不存在于类中的属性。
  • @JsonAnyGetter 该注解用于把可变的Map类型属性当做标准属性,和@JsonAnySetter一起使用
  • @JacksonInject 指定属性是注入的而不是从Json字符串中反序列化获取
  • @JsonAutoDetect 可以覆盖可见性,可以指定属性的可见性
  • @JacksonAnnotation,@JacksonAnnotationsInside 属于JackSon的元注解

@JsonProperty

注意:虽然官方文档说 该注解如果和@JsonIgnore一同使用,@JsonIgnore 则优先于此属性。但实际测试用发现如果两个注解同时存在,效果等同 READ_ONLY,序列化都能展示,反序化不能写入。


String value: 指定序列化key
String defaultValue: 指定key的默认值
boolean	required:是否必须,但请注意,从 2.6 开始,此属性仅用于 Creator 属性,以确保 JSON 中存在属性值:对于其他属性(使用 setter 或可变字段注入的属性),不执行验证,也就是说只和@JsonCreator一起使用时生效。
int index: 指定该字段排序
JsonProperty.Access access: 序列化权限控制 有下列四种

  • AUTO 自动确定此属性的读取和/或写入访问权限。
  • READ_ONLY 只能在序列化时读取,而在反序列化期间不能写入(设置)。
  • READ_WRITE 无论可见性规则如何,都将访问该属性以进行序列化(将值写为外部表示)和反序列化(从外部表示中读取值)。
  • WRITE_ONLY 只能被写(set)用于反序列化,而不会被读(get)在序列化时,即该属性的值不包含在序列化中。

序列化权限控制测试代码:MyTest.java

@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class MyTest {

    @JsonProperty(access = AUTO)
    public String myA;

    @JsonProperty(access = READ_ONLY)
    public String myB;

    @JsonProperty(access = WRITE_ONLY)
    public String myC;

    @JsonProperty(access = READ_WRITE)
    public String myD;

    public static void main(String[] args) throws JsonProcessingException {
        MyTest myTest = MyTest.builder().myA("A").myB("B").myC("C").myD("D").build();
        ObjectMapper mapper = new ObjectMapper();
        String toJson = mapper.writeValueAsString(myTest);
        System.out.println("=======预期打印======");
        System.out.println("{\"myA\":\"A\",\"myB\":\"B\",\"myC\":\"C\",\"myD\":\"D\"}");
        System.out.println("=======序列化打印=====");
        System.out.println(toJson);
        System.out.println("=======反列化打印=====");
        String fromJson = "{\"myA\":\"A\",\"myB\":\"B\",\"myC\":\"C\",\"myD\":\"D\"}";
        MyTest test = mapper.readValue(fromJson,MyTest.class);
        System.out.println(test);
    }
}
输出结果:
=======预期打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
MyTest(myA=A, myB=B, myC=C, myD=D)
=======序列化打印=====
{"myA":"A","myB":"B","myD":"D"}
=======反列化打印=====
MyTest(myA=A, myB=null, myC=C, myD=D)

@JsonCreator

JackSon默认使用无参构造方法和set方法反序列对象,使用该注解可以指定JaskSon使用指定方法进行反序列化构造对象,可以标注在构造方法和静态工厂方法上。

@JsonView

@JsonView使用方法:

  1.使用接口来声明多个视图 例如下面这个 代码来自baeldung.com

public class Views {
    public static class Public {
    }

    public static class Internal extends Public {
    }
}
public class Item {
 
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

  2.在pojo的属性上指定视图

public class Item {
 
    @JsonView(Views.Public.class)
    public int id;

    @JsonView(Views.Public.class)
    public String itemName;

    @JsonView(Views.Internal.class)
    public String ownerName;
}

  3.在Controller方法上指定视图

@JsonView(Views.Internal.class)
@RequestMapping("/items/internal/{id}")
public Item getItemInternal(@PathVariable int id) {
    return ItemManager.getById(id);
}

4.直接进行序列化或反序列化时可以指定视图

@Test
public void whenUseJsonViewToDeserialize_thenCorrect() 
  throws IOException {
    String json = "{"id":1,"name":"John"}";

    ObjectMapper mapper = new ObjectMapper();
    User user = mapper
      .readerWithView(Views.Public.class)
      .forType(User.class)
      .readValue(json);

    assertEquals(1, user.getId());
    assertEquals("John", user.getName());
}

@JsonValue

@JsonView是jackson json中的一个注解,指定属性后序列化将只使用该属性值作为序列化接口(这个注解只能作用于一个属性上)。

@JsonUnwrapped

@JsonUnwrapped 对象扁平化 如果属性序列化后是一个对象 会将该属性的对象解构提取到根对象中。

@JsonTypeName

用于绑定被注释类所具有的逻辑名称的注释。与JsonTypeInfo(特别是JsonTypeInfo.use()属性)一起使用以建立对象名称和属性之间的关系,反序列化为父类时,用于确定该对象的具体类型,与@JsonSubTypes 直接标注在父类等效。

@JsonTypeInfo

用于多态类型处理

Class<?> defaultImpl 指定默认的反序列化类型,当反序列对象无法映射到现在有的指定类型时会使用它进行反序列化。

JsonTypeInfo.As include 指定类型标识信息的展示方式。
有下列5种可选值 下面的测试结果使用的注解是@JsonTypeInfo(use= JsonTypeInfo.Id.NAME)

PROPERTY 使用单个可配置属性的包含机制,与实际数据(POJO 属性)一起作为单独的元属性包含在内。,这个属性的值由 @JsonTypeInfo注解的property确定,否则就使用不同use情况下的默认值(@class、@c、@type)。

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.PROPERTY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@type":"MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

EXISTING_PROPERTY 与PROPERTY区别在于,该注解在序列化时不会输出标识符,反序列流程根PROPERTY相同。

EXTERNAL_PROPERTY 只作用于属性上,把子属性的标识符提升到根对象里,具体使用场景没搞明白。

WRAPPER_OBJECT 包裹在一个对象中,相当于在外层创建一个父对象 {标识符:{原本的对象}} 用一个大对象包住

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.EXTERNAL_PROPERTY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"MyTest":{"myA":"A","myB":"B","myC":"C","myD":"D"}}

WRAPPER_ARRAY 包裹在一个数组中。

@JsonTypeInfo(use= JsonTypeInfo.Id.NAME,
            include = JsonTypeInfo.As.WRAPPER_ARRAY )
结果:
=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
["MyTest",{"myA":"A","myB":"B","myC":"C","myD":"D"}]

JsonTypeInfo.Id use 指定在序列化时类型标识信息展示的值。
有下列5种可选值

CLASS 意味着使用完全限定的 Java 类名作为类型标识符。

测试结果:可以看到序列化对象多了一个 @class key,而且其值为全限定类名。(若不指定property则默认为@class

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印========{"@class":"com.example.demo.bean.MyTest","myA":"A","myB":B,"myC":"C","myD":"D"}

CUSTOM 意味着键入机制使用自定义处理,可能具有自定义配置。这个注解需结合property属性和@JsonTypeIdResolver一起使用,指定类标识符的值。

@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "type")
@JsonTypeIdResolver(JacksonTypeIdResolver.class)
实现TypeIdResolver接口 自定义序列化流程

MINIMAL_CLASS 表示使用具有最小路径的 Java 类名作为类型标识符,多了一个”@c“字段,其值为最小路径类名。(若不指定property则默认为@c

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@c":".MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

NAME 表示使用逻辑类型名作为类型信息;然后需要将名称单独解析为实际的具体类型(类),多了一个”@type”字段,其值为类名。(若不指定property则默认为@type

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"@type":"MyTest","myA":"A","myB":"B","myC":"C","myD":"D"}

NONE 这意味着不包含显式类型元数据,并且键入纯粹是使用上下文信息完成的,可能会增加其他注释。

=======未配置注解打印======
{"myA":"A","myB":"B","myC":"C","myD":"D"}
=======配置注解打印=======
{"myA":"A","myB":"B","myC":"C","myD":"D"}

String property 指定类标识名称,在include=JsonTypeInfo.As.PROPERTY或use=JsonTypeInfo.Id.CUSTOM生效,其他情况使用默认的识别码名称。
注意:include=JsonTypeInfo.As.PROPERTY和property同时存在有个问题,如果POJO具有相同名称的属性,会出现两个!

@JsonSubTypes

用来列出给定类的子类,只有当子类类型无法被检测到时才会使用它,也可以在子类上直接使用@JsonTypeName 实现同等效果。