Java8新写法
Jdk8 lambd新写法
一般使用Thread创建线程,如果频繁的创建销毁线程,系统资源比较浪费,线程启动调用的native方法,任务不能立即执行。
采用线程池可以通过重复利用已经创建的线程降低线程创建和销毁赵成的系统消耗,提高任务的响应速度,还可以对线程池中线程进行统一分配、调优和监控。
手动通过创建ThreadPoolExecutor创建线程池,其构造方法参数有:
threadFactory可以默认使用Executors.defaultThreadFactory()、handler可以默认使用ThreadPoolExecutor.defaultHandler(使用的是内部类ThreadPoolExecutor.AbortPolicy)。
ThreadPoolExecutor有几个关键常量:
1 | //默认为RUNNING(11100000000000000000000000000000) |
提交任务使用execute:
1 | public void execute(Runnable command) { |
execute其实就是修改ctl值,通过设置的值处理应该新启动线程还是放入队列中。
在创建Worker后,线程工厂创建线程启动调用Worker.run,之后调用的是runWorker方法。
1 | final void runWorker(Worker w) { |
总结线程池新增任务:
在线程池中新增任务时:当前工作线程数量小于corePoolSize,启动新线程;如果当前线程数量大于corePoolSize,把线程放入任务队列中,放入成功后,验证当前工作线程数是否为0,如果为0启动一个新线程处理任务;如果线程放入队列中失败,表示任务放入速度大于任务完成速度,这个时候启动新线程,但是总数不能大于maximumPoolSize。
对于submit,其实就是把Callback封装成FutureTask,然后调用execute,返回封装的FutureTask
ArrayBlockingQueue:数组阻塞队列,需要指定大小
LinkedBlockingQueue:链表队列,默认为Integer.MAX_VALUE
PriorityBlockingQueue:优先级阻塞队列
SynchronousQueue:同步阻塞队列
AbortPolicy(默认):直接抛出异常
CallerRunsPolicy:通过调用者所在线程执行任务,其实就是传递过来的Runnable.run,直接调用run方法
DiscardOldestPolicy:它放弃最旧的未处理请求(下一个任务),然后重试 execute;如果执行程序已关闭,则会丢弃该任务。也就是直接获取线程池中下一个任务然后不处理,重新执行execute
DiscardPolicy:不处理,丢弃当前未处理任务
在使用工具类Executors,基本上也是直接使用线程池,需要注意使用的队列。
在使用线程池的时候需要注意如果在线程中放入了数据也就是使用了ThreadLocal,因为线程在执行完任务后并不会销毁,可能其他任务获取到了当前线程设置的值,建议在使用完毕后删除ThreadLocal中的数据。
参考:
公司准备使用sonar作为代码质量管理,在代码中string不允许直接定义和环境相关的String,只能从配置文件中读取key,需要自己写一个sonar规则。
因为检测的是java文件,直接用java开发个自定义组件。
查看各种规则可以使用的语言:Support of Custom Rules by Language
编写规则的时候官方有demo:Sonar Java Custom Rules
通常在项目日志查看过程中都是直接登录服务器,找到服务器通过一些命令查看日志。有时候在定位问题的时候比较麻烦,不能快速找到问题日志。
这次尝试在本地搭建简单的ELK查询日志相关信息。
Elasticsearch:用于日志搜索查询
Logstash:用于日志收集
Kibana:用于页面展示
在平常工作中有时候需要查看方法的调用时间,或者需要知道某个业务的调用逻辑,需要些大量侵入式代码来完成。现在可以使用javaagent在main方法前执行,然后加载的类,通过字节码技术,在类中加入需要的代码。
jdk1.7中ConcurrentHashMap与jdk1.8中实现不一致,1.7中采用的事分段锁,此处使用的是1.8,利用CAS+Synchronized来保证并发更新的安全,当然底层采用数组+链表+红黑树的存储结构。
在ConcurrentHashMap中有几个重要内部类:
在ConcurrentHashMap中,最开始是采用链表存储数据,当数据过长(TREEIFY_THRESHOLD默认长度8),就会转换为红黑树来处理,将原有Node节点包装成TreeNode放入TreeBin中,然后由TreeBin完成红黑树的转换。
在使用ReentrantReadWriteLock时,获取写锁时,不能存在任何其他锁。如果存在读超大与写,可能会出现获取写锁线程一直处在等待状态导致饥饿。
在JDK1.8中引入StampedLock,StampedLock控制锁有3种状态:写,读,乐观读。
所谓的乐观读模式,也就是若读的操作很多,写的操作很少的情况下,你可以乐观地认为,写入与读取同时发生几率很少,因此不悲观地使用完全的读取锁定,程序可以查看读取资料之后,是否遭到写入执行的变更,再采取后续的措施(重新读取变更信息,或者抛出异常) ,这一个小小改进,可大幅度提高程序的吞吐量!!
在多线程操作过程中,如果写少读多,采用ReentrantLock(排他锁),会比较浪费资源,这种情况下可以采用Java读写锁。
ReadWriteLock接口,定义了readLock、writeLock
重入读写锁,ReadWriteLock子类。对于ReadWriteLock,内部主要有:
在Java线程基础中使用过synchronized,在JDK5一起,同步都是基于synchronized,在场景非常复杂的地方,使用synchronized不方便,JDK5引入了Lock。在包java.util.concurrent.locks中就有锁相关的类。
抽象独占同步锁。提供设置当前拥有独占访问的线程,获取设置的独占线程。
只能子类构造。
抽象队列同步,继承AbstractOwnableSynchronizer。通过队列先进先出来实现等待队列的阻塞,内部维护一个线程链表Node。在获取锁失败后,会生成Node节点,并放入链表末尾,直到等待超时或者线程中断或被上一个节点唤醒。其节点等待状态waitStatus有:
AbstractQueuedSynchronizer内部通过Unsafe.compareAndSet(原子操作int)来操作内存,保证线程的同步。其他API可查看相关文档。
同AbstractQueuedSynchronizer,只不过AbstractQueuedLongSynchronizer内部通过long字段来实现原子操作。当创建需要 64 位状态的多级别锁和屏障等同步器时使用。