Java

如何正确停止线程

Submitted by Lizhe on Wed, 05/24/2017 - 00:18

这个话题完全是由于 http://lizhe.name.csdn.net/node/102 带出来的

在谈论这个话题之前,首先我要申明一个概念, java本身不推荐你杀死任何线程而是推荐你要"让它自己运行完毕",然后我们来看下面几个概念

1. stop是不安全的, stop会释放持有的全部锁然后直接杀死线程,可能会造成数据不一致,而且已经过期,会立即杀死线程

2. interruput 不会立即杀死线程

    当线程处于运行状态时,interrupt不会终止线程,只是设置了一个表示位

    当线程处于阻塞状态(如调用sleep、wait、join等地方) 会抛出一个异常InterruptedException,并且中断状态也将被清除,这样线程就得以退出阻塞的状态

3. ExecutorService shutdown方法会让线程池停止接受新任务,但是不会终止或暂停任何当前持有的任务

ExecutorService shutdown 之后 Future.get() 方法的阻塞问题

Submitted by Lizhe on Tue, 05/23/2017 - 17:44

今天碰到这个问题记录一下

需求很简单, 差不多是在10万条数据库记录里查找一个特定账户

不过接口是产品包提供的,每次查询都需要进行一次slelect, 不能直接写sql

所以这里我的思路是启动4个线程,然后分别去查找10万/4 条记录,一旦某个线程执行结束, 就关闭全部线程,终止查找

这里只是一个POC,

 

数据类

package com.lz.bean;

如何在web应用中使用springbatch

Submitted by Lizhe on Sun, 05/21/2017 - 23:21

现在spring batch 的官方quick start 已经是使用spring boot来做的了

2015年曾经帮老白做过一个简单爬虫程序,使用的是spring mvc和spring batch

今天突然想起来稍微做个总结吧

实际上springbatch脱离springbatch使用起来也蛮简单的, springbatch需要把每次提交的batch存入数据库(如果没有数据库的话它会使用一个内存数据库,总之离不了数据库)

你可以想象, 只要一行代码就足以启动一个job

这里我假设读者您使用过spring boot条件下的spring batch, 所以对spring batch的其他概念就不做介绍了

在web应用下,你需要在controller里注册一个jobOperator对象,这里我假设您跟我一样也使用springmvc

jobOperator.start("pickJob", parameters);

这个jobOperator需要通过下面的方式初始化

java volatile CAS 性能影响 (伪共享)

Submitted by Lizhe on Fri, 05/19/2017 - 10:00

volatile 的作用请参阅

http://lizhe.name.csdn.net/node/94

关于CAS

http://lizhe.name.csdn.net/node/96

本文讨论volatile和CAS因false sharing的原因对性能造成的影响

先看下面的例子

package testatomic;

public class Data {
    public volatile long value1;
    public volatile long value2;
    public volatile long value3;
    public volatile long value4;
}
 

java atomic CAS

Submitted by Lizhe on Thu, 05/18/2017 - 18:26

 

java.util.concurrent.atomic 包中的类使用CAS , 而不是传统意义上的同步

CAS (Compare and Swap) 在处理竞争问题时使用的是乐观锁机制 ( 乐观锁在读的时候不会独占,在写的时候会进行检查 )

CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做

不过上面的步骤实际上会来带ABA问题, 就是线程1 读取了A,然后线程2先将原始值改成了B然后又改回A, 线程1在这之后察觉不到版本变化

要避免ABA问题可以使用AtomicStampedReference

在竞争不激烈的情况下, atomic包下的原子类可以获得更好的性能, 不过即使在竞争激烈的情况下, 新的原子类也可以获得比synchronized更好的性能,只是会消耗更多内存(下面提到的数组)

当多个线程同时更新某个原子类实例时, 这个类可以独立的保存每个线程所做的更新

操作值会被保存在一个数组里,  每个线程都可以快速返回, 当某个线程是图获取当前值时, 操作值会被累加起来

java forkjoin 模型

Submitted by Lizhe on Thu, 05/18/2017 - 16:57

 

实际上就是多线程的一种递归模型

所有能拆分的任务都可以分布到各个线程中去

典型的递归问题比如汉诺塔

请参考 http://lizhe.name.csdn.net/node/82

用forkjoin重写之后是这样的

package testgc;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ForkjoinHanoi {

java volatile 线程安全

Submitted by Lizhe on Thu, 05/18/2017 - 10:39

本来这部分内容是放在上一篇文章中的,结果越写越多,觉得应该另起一篇再详细描述一下

不看不看只要答案党看这里

 

使用volatile关键字

----------------------------------------------------

1. 只能保证读取一致性

2. 不能保证线程安全性(原子性)

----------------------------------------------------

下面看几个例子

第一个例子用来证明读取一致性

package testgc;

java 多线程性能调整

Submitted by Lizhe on Wed, 05/17/2017 - 15:59

现在已经很少有人自己写线程池了, jdk5 之后自带的线程池已经可以满足日常的多种需求

对于多线程通常我们考虑两个点

1. 如何让cpu工作量饱和

2. 线程过多时多读在多个线程之间轮询会带来的性能损耗

指导思想很简单

对于cpu密集型的应用程序, 尽量保证线程池大小=cpu的内核数

对于IO密集型的应用程序, 要按照实际情况适当>cpu内核数

要尽量避免使用同步(synchronization) 和 volatile 关键字

synchronization 的工作方式简单粗暴, 同一个时间内, 当锁相同时只能有一个线程进入代码块进行处理

volatile 实际上并不能帮助你处理线程安全问题,它只是要求每个线程在访问这个变量时都会去主内存中读取这个变量

用volatile修饰的变量,线程在每次使用变量的时候,都要实时回写给主内存,而不是操作变量的拷贝, 而且这个关键字由于放弃使用cpu寄存器会带来大量的性能开销

关于volatile关键字与线程安全之间的关系请参阅

http://lizhe.name.csdn.net/node/94

 

java 内存堆的调整

Submitted by Lizhe on Wed, 05/17/2017 - 09:42

 

这也是个老话题, 估计所有java老手都会使用 -Xms 和 -Xmx

关于这两个参数的核心问题是, 什么时候jvm使用的是xms大小,什么时候使用的是xmx大小

jvm启动以后,虚拟机将使用xms设置的堆大小,当GC运行次数超出预期,频繁的发生时, 它才会调高堆的大小,注意不是一次调整到xmx而是逐渐提高

直到GC发生次数降低到预期值或者是堆内存大小已经调整到xmx设置的值

 

一旦堆的大小范围确定下来了,接下来你需要考虑的是新生代和老生代内存的划分

其实根据之前说过的不同垃圾收集器的原理, 现代jvm都是采用分代回收的方式, 那么也就意味着 如果新生代过大,老生代较小, Minor GC 的发生次数就会降低, 但是Full GC可能会频繁发生

反过来如果新生代较小, 老生代过大可能会导致Minor GC频繁发生, Full GC运行时间过长的情况

平衡是门艺术

所有用于调整代空间的命令都是针对新生代的, 剩余的部分都会被划分给老生代

-XX:NewRatio=N

设置新生代比例,默认值是2 

Java 静态块到底在什么时候被调用(如何确定一个class是否被classloader加载)

Submitted by Lizhe on Tue, 05/16/2017 - 17:21

今天在项目的老代码里debug , 发现有一个获取ESB对象的初始化动作写在了静态块里

结果debug一直到使用这个类的时候这段代码才被执行到

这些年一直用spring, 主观上认为单例模式跟静态块在缓存这个层面上差不多,也没有深究过

一直觉得静态块跟spring的单例对象差不多, tomcat容器在启动时就应该被调用, 实际上并非如此

class只有在第一次被调用时才会被classloader加载, 当class被加载后static块才会被调用

为了证实这种猜测,我们使用一个简单例子做验证

我尝试使用jconsole查看classloader, 跟本地jvm加上

-Djava.rmi.server.hostname=10.10.8.57

-Dcom.sun.management.jmxremote  

-Dcom.sun.management.jmxremote.port=8011

-Dcom.sun.management.jmxremote.ssl=false

-Dcom.sun.management.jmxremote.authenticate=false