Java基础

参考文献

我的架构师之路:https://www.yuque.com/tiankongyiwusuoyouweihegeiwoanwei/kb/ud3nbr

23种设计模式

http://c.biancheng.net/view/1371.html

tomcat和osgi ,典型打破双亲委派策略

Tomcat是如何打破双亲委派机制的   https://www.jianshu.com/p/7706a42ba200

如何破坏双亲委派模型   https://blog.csdn.net/m0_37556444/article/details/81912283

1尽量减少对变量的重复计算

for (int i = 0; i < list.size(); i++){
  ...
}
//建议替换为:
for (int i = 0, length = list.size(); i < length; i++){
  ...
}

javadoc规范

因为LIst等集合都是使用了参数类型,所以无法使用泛态转化为List,

 

1 查看动态编译技术生成的接口代理类proxy

public class CodeUtil {
 
public static void main(String[]args)throws IOException {
 
byte[]classFile =ProxyGenerator.generateProxyClass("Proxy0",HumenImpl1.class.getInterfaces());
 
File file =new File("../Proxy0.class");
 
FileOutputStream fos =new FileOutputStream(file);
 
fos.write(classFile);
 
fos.flush();
 
fos.close();
 
}
 
windows环境下的文本文件换行符:\r\n

linux/unix环境下的文本文件换行符:\r

Mac环境下的文本文件换行符:\n

 

 

2java有界队列和饱和策略,无界队列

以ExecutorService的具体实现ThreadPoolExecutor来说,它定义了4种饱和策略。分别是AbortPolicy,DiscardPolicy,DiscardOldestPolicy和CallerRunsPolicy

上面的例子中我们定义了一个初始5个,最大10个工作线程的Thread Pool,并且定义其中的Queue的容量是20。如果提交的任务超出了容量,则会使用AbortPolicy策略

ThreadPoolExecutor threadPoolExecutor= new ThreadPoolExecutor(5, 10, 10, TimeUnit.SECONDS, new LinkedBlockingDeque<Runnable>(20));
threadPoolExecutor.setRejectedExecutionHandler(
    new ThreadPoolExecutor.AbortPolicy()
);

但是一般无界队列(例如LinkedBlockingQueue就是无界队列),故不会出现拒绝提交异常

从有界无界上分 

无界队列:指的是没有设置固定大小的队列。这些队列的特点是可以直接入列,直到溢出。当然现实使用中,几乎不会有到这么大的容量(超过 Integer.MAX_VALUE),所以从使用者的体验上,就相当于 “无界”。比如没有设定固定大小的 LinkedBlockingQueue。
常见的有界队列为

  • ArrayBlockingQueue 基于数组实现的阻塞队列,此类在初始化的时候会传入capacity值(数组的长度),后面的offer等操作一旦到达了capacity,则对象不会被存储。
  • LinkedBlockingQueue 其实也是有界队列,但是不设置大小时就时Integer.MAX_VALUE(因为无参构造函数内部会赋值capacity为Integer.MAX_VALUE),内部是基于链表实现的
  • ArrayBlockingQueue 与 LinkedBlockingQueue 对比一哈 
    • ArrayBlockingQueue 实现简单,表现稳定,添加和删除使用同一个锁,通常性能不如后者
    • LinkedBlockingQueue 添加和删除两把锁是分开的,所以竞争会小一些
  • SynchronousQueue 比较奇葩,内部容量为零,适用于元素数量少的场景,尤其特别适合做交换数据用,内部使用 队列来实现公平性的调度,使用栈来实现非公平的调度,在Java6时替换了原来的锁逻辑,使用CAS代替了
  • 上面三个队列他们也是存在共性的 
    • put take 操作都是阻塞的
    • offer poll 操作不是阻塞的,offer 队列满了会返回false不会阻塞,poll 队列为空时会返回null不会阻塞
    • 补充一点,并不是在所有场景下,非阻塞都是好的,阻塞代表着不占用CPU,在有些场景也是需要阻塞的,put take 存在必有其存在的必然性

常见的无界队列

  • ConcurrentLinkedQueue 无锁队列,底层使用CAS操作,通常具有较高吞吐量,但是具有读性能的不确定性,弱一致性——不存在如ArrayList等集合类的并发修改异常,通俗的说就是遍历时修改不会抛异常
  • PriorityBlockingQueue 具有优先级的阻塞队列,PriorityBlockingQueue也是基于最小二叉堆实现,使用基于CAS实现的自旋锁来控制队列的动态扩容,保证了扩容操作不会阻塞take操作的执行
  • DelayedQueue 延时队列,使用场景 
    • 缓存:清掉缓存中超时的缓存数据
    • 任务超时处理
    • 补充:内部实现其实是采用带时间的优先队列,可重入锁,优化阻塞通知的线程元素leader
  • LinkedTransferQueue 简单的说也是进行线程间数据交换的利器,在SynchronousQueue 中就有所体现,并且并发大神 Doug Lea 对其进行了极致的优化,使用15个对象填充,加上本身4字节,总共64字节就可以避免缓存行中的伪共享问题,其实现细节较为复杂,可以说一下大致过程: 
    • 比如消费者线程从一个队列中取元素,发现队列为空,他就生成一个空元素放入队列 , 所谓空元素就是数据项字段为空。然后消费者线程在这个字段上旅转等待。这叫保留。直到一个生产者线程意欲向队例中放入一个元素,这里他发现最前面的元素的数据项字段为 NULL,他就直接把自已数据填充到这个元素中,即完成了元素的传送。大体是这个意思,这种方式优美了完成了线程之间的高效协作。参考自 
      https://blog.csdn.net/u013851082/article/details/70140728
  • 现在也来说一说无界队列的共同点 
    • put 操作永远都不会阻塞,空间限制来源于系统资源的限制
    • 底层都使用CAS无锁编程

ConcurrentLinkedQueue

https://blog.csdn.net/qq_38293564/article/details/80798310

//使用CAS 原子指令
UNSAFE=sun.misc.Unsafe.getUnsafe();
Class<?> nc=Node.class;
//获取class 对象的field的偏移量
itemOfferset=UNSAFE.objectFieldOffset(nc.getDeclaredField("item"));
nextOffset=UNSAFE.objectFieldOffaset(nc.getDeclaredField("next"));
//根据偏移量设置值
UNSAFE.compareAndSwapObejct(this,nextOffset,oldValue,newValue);
ConcurrentLinkedQueue 的非阻塞算法实现可概括为下面 5 点:

使用 CAS 原子指令来处理对数据的并发访问,这是非阻塞算法得以实现的基础。
head/tail 并非总是指向队列的头 / 尾节点,也就是说允许队列处于不一致状态。 这个特性把入队 / 出队时,原本需要一起原子化执行的两个步骤分离开来,从而缩小了入队 / 出队时需要原子化更新值的范围到唯一变量。这是非阻塞算法得以实现的关键。
由于队列有时会处于不一致状态。为此,ConcurrentLinkedQueue 使用三个不变式来维护非阻塞算法的正确性。
以批处理方式来更新 head/tail,从整体上减少入队 / 出队操作的开销。
为了有利于垃圾收集,队列使用特有的 head 更新机制;为了确保从已删除节点向后遍历,可到达所有的非删除节点,队列使用了特有的向后推进策略。

BlockingQueue

BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null 或 false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

 抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e, time, unit)
移除remove()poll() 移除headtake()poll(time, unit)
检查element()peek()不可用不可用

ArrayBlockingQueue介绍

ArrayBlockingQueue是一个阻塞式的队列,继承自AbstractBlockingQueue,间接的实现了Queue接口和Collection接口。底层以数组的形式保存数据(实际上可看作一个循环数组)。常用的操作包括 add,offer,put,remove,poll,take,peek。

根据 ArrayBlockingQueue 的名字我们都可以看出,它是一个队列,并且是一个基于数组的阻塞队列。

ArrayBlockingQueue 是一个有界队列,有界也就意味着,它不能够存储无限多数量的对象。所以在创建 ArrayBlockingQueue 时,必须要给它指定一个队列的大小。

我们先来熟悉一下 ArrayBlockingQueue 中的几个重要的方法。

其中BlockingQueue继承自Queue,而且在此基础上增加put和take方法,二

 

  • add(E e):把 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则报异常 
  • offer(E e):表示如果可能的话,将 e 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false 
  • put(E e):把 e 加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻断直到 BlockingQueue 里面有空间再继续
  • poll(time):取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,取不到时返回 null 
  • take():取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 Blocking 有新的对象被加入为止 
  • remainingCapacity():剩余可用的大小。等于初始容量减去当前的 size
  • 先进先出队列(队列头的是最先进队的元素;队列尾的是最后进队的元素)
  • 有界队列(即初始化时指定的容量,就是队列最大的容量,不会出现扩容,容量满,则阻塞进队操作;容量空,则阻塞出队操作)
  • 队列不支持空元素

 

ConcurrentLinkedQueue

使用了单向链表,所有操作都是CAS+无限循环代替阻塞算法挂起调用线程,相比堵塞算法,这里使用了cpu资源换区阻塞所带来的开销

LinkedBlockedQueue

使用了单向链表,所有操作使用了ReentrantLock和ConditionObject,put和take是阻塞的,虽然是链表,但也是有界队列,但是不设置大小时就时Integer.MAX_VALUE

有使用putlock和notFull(Condition)和takeLock,以及notEmpty(Condition)

ArrayBlockingQueue

使用数组,有界阻塞队列,且初始化数组后不可变,而且通过使用全局独占锁熟悉爱你了同时只能有一个线程进入入队和出队操作,这个锁的力度比较大,

PriorityBlockingQueue

这个队列是带优先级的无解阻塞队列,每次出队都是返回优先级最高或者最低的元素,其内部使用了平衡二叉树堆实现,所以直接便利队列不保证有序,内部有一个数组,用于存放队列元素,

DelayQueue

并发队列是一个无界阻塞延迟队列,队列中的每一个元素都一个过期时间,当从队列中获取元素时,只有过期元素才会出队列,队列的头元素是最快要过期的元素,内部使用PriorityQueue存放数据,使用ReentrantLock实现线程同步,另外队列里面的元素要实现Delay接口

 

java synchonized和lock锁 内存可见性分析

Synchronized的内存可见性  http://www.javashuo.com/article/p-byttuaqk-cs.html 

 Java中为了保证每个线程中的原子操作,引入了内置锁,或者称为监视器锁,其中,每个Java对象都可以作为一个实现锁的对象,synchronized关键字修饰的代码块被称为同步代码块,线程进入同步代码块自动获取内置锁,退出同步代码块则释放锁,不需要调用者考虑它的创建以及消除,但是得十分熟悉内置锁的机制。

 Java中的锁机制具有可见性、互斥性两大通性,内置锁也不例外

2个线程中在对共享变量的读取或者写入都进行加锁处理,由于线程对应的都是同一把锁对象(该类对象)因此相互会排斥。可是就算这样子也不能说明内存可见性的。其实真正解决这个问题的是JMM关于Synchronized的两条规定: 

一、线程解锁前,必须把共享变量的最新值刷新到主内存中; 
二、线程加锁时,讲清空工做内存中共享变量的值,从而使用共享变量是须要从主内存中从新读取最新的值(加锁与解锁须要统一把锁) 

线程执行互斥锁代码的过程: 
1.得到互斥锁 
2.清空工做内存 
3.从主内存拷贝最新变量副本到工做内存 
4.执行代码块 
5.将更改后的共享变量的值刷新到主内存中 
6.释放互斥锁 

对于同一个监视器,变量的可见性有必定的方式可寻,非同一个监视器就不保证了

 

java线程安全集合

1.Vector

Vector和ArrayList类似,是长度可变的数组,与ArrayList不同的是,Vector是线程安全的,它给几乎所有的public方法都加上了synchronized关键字。由于加锁导致性能降低,在不需要并发访问同一对象时,这种强制性的同步机制就显得多余,所以现在Vector已被弃用

2.HashTable

HashTable和HashMap类似,不同点是HashTable是线程安全的,它给几乎所有public方法都加上了synchronized关键字,还有一个不同点是HashTable的K,V都不能是null,但HashMap可以,它现在也因为性能原因被弃用了

二、Collections包装方法

Vector和HashTable被弃用后,它们被ArrayList和HashMap代替,但它们不是线程安全的,所以Collections工具类中提供了相应的包装方法把它们包装成线程安全的集合

List<E> synArrayList = Collections.synchronizedList(new ArrayList<E>());

Set<E> synHashSet = Collections.synchronizedSet(new HashSet<E>());

Map<K,V> synHashMap = Collections.synchronizedMap(new HashMap<K,V>());


Collections针对每种集合都声明了一个线程安全的包装类,在原集合的基础上添加了锁对象,集合中的每个方法都通过这个锁对象实现同步

三、java.util.concurrent包中的集合

1.ConcurrentHashMap

ConcurrentHashMap和HashTable都是线程安全的集合,它们的不同主要是加锁粒度上的不同。HashTable的加锁方法是给每个方法加上synchronized关键字,这样锁住的是整个Table对象。而ConcurrentHashMap是更细粒度的加锁 
在JDK1.8之前,ConcurrentHashMap加的是分段锁,也就是Segment锁,每个Segment含有整个table的一部分,这样不同分段之间的并发操作就互不影响 
JDK1.8对此做了进一步的改进,它取消了Segment字段,直接在table元素上加锁,实现对每一行进行加锁,进一步减小了并发冲突的概率

2.CopyOnWriteArrayList和CopyOnWriteArraySet

它们是加了写锁的ArrayList和ArraySet,锁住的是整个对象,但读操作可以并发执行

3.

除此之外还有ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque等,至于为什么没有ConcurrentArrayList,原因是无法设计一个通用的而且可以规避ArrayList的并发瓶颈的线程安全的集合类,只能锁住整个list,这用Collections里的包装类就能办到

SimpleDateFormat线程不安全性

SimpleDateFormat在多线程解析或者格式化的时候是线程不安全的,因为它内部有一个Calender对象,Calender类是线程不安全的,所以导致SimpleDateFormat是线程不安全的

 

 

java dcl和volatile

事实上,双重校验锁时在并发情况下还是有可能出问题的

Java多线程之volatile关键字和内存屏障☆☆https://blog.csdn.net/huyongl1989/article/details/90712393

 

 

ThreadLocal内存泄漏问题

JAVA并发系列十九:深入理解ThreadLocal(三)–详解ThreadLocal内存泄漏问题 https://blog.csdn.net/tujisitan/article/details/106176680

Thead中有ThreadLocalMap,其key是ThreadLocal的WeakReference,value则是设置的值

当调用ThreadLocal.get方法的时候,其实是从Thread中得到ThreadLocalMap,然后以它自身的弱引用为key得到value值

考虑到这个ThreadLocal变量没有其他强依赖,而且当前线程还存在,由于线程的ThreadLocalMap里面的key为弱依赖,所以当前线程的ThreadLocalMap里面的ThreadLocal变量的弱引用会在gc的时候被回收,但是对应的value不为null,,会造成内存泄漏

如果threadLocal外部强引用被置为null(threadLocalInstance=null)的话,threadLocal实例就没有一条引用链路可达,很显然在gc(垃圾回收)的时候势必会被回收,因此entry就存在key为null的情况,无法通过一个Key为null去访问到该entry的value。同时,就存在了这样一条引用链:threadRef->currentThread->threadLocalMap->entry->valueRef->valueMemory,导致在垃圾回收的时候进行可达性分析的时候,value可达从而不会被回收掉,但是该value永远不能被访问到,这样就存在了内存泄漏。当然,如果线程执行结束后,threadLocal,threadRef会断掉,因此threadLocal,threadLocalMap,entry都会被回收掉。可是,在实际使用中我们都是会用线程池去维护我们的线程,比如在Executors.newFixedThreadPool()时创建线程的时候,为了复用线程是不会结束的,所以threadLocal内存泄漏就值得我们关注

所以得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了我们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的情况,这就发生了真正意义上的内存泄露。比如使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露.

 

FutureTask阻塞问题

线程池在使用FutureTASK时如果将拒绝策略设置成DiscardPolicy和DiscardOldestPolicy,并且在被拒绝任务的future对象上调用无参get方法,那么调用线程会被一直阻塞,但是如果拒绝策略使用了默认的AbortPolicy(虽然也会依旧拒绝执行任务)则会正常返回,get方法不会阻塞。

handler : 线程池对拒绝任务的处理策略。在 ThreadPoolExecutor 里面定义了 4 种 handler 策略,分别是

1. CallerRunsPolicy :这个策略重试添加当前的任务,他会自动重复调用 execute() 方法,直到成功。

2. AbortPolicy :对拒绝任务抛弃处理,并且抛出异常。

3. DiscardPolicy :对拒绝任务直接无声抛弃,没有异常信息。

4. DiscardOldestPolicy :对拒绝任务不抛弃,而是抛弃队列里面等待最久的一个线程,然后把拒绝任务加到队列。

3 雪花算法和雪花漂移算法,uuid算法比较

https://gitee.com/tp_love/idgenerator 

https://blog.csdn.net/zmh458/article/details/85412629

雪花算法的原理和实现Java  https://blog.csdn.net/lq18050010830/article/details/89845790

传统的雪花算法没有办法解决时间回拨问题

雪花漂移算法可以完全是数字类型的,位数在不断累加,最大位数是2^53-1,其最大值是9007199254740992,极致性能:500W/1s

129053495681099        (本算法运行1年)
387750301904971        (运行3年)
646093214093387        (运行5年)
1292658282840139       (运行10年)
9007199254740992       (js Number 最大值)
165399880288699493     (普通雪花算法生成的ID)

传统的雪花算法

最高位是符号位,始终为0,不可用。
+ 41位的时间序列,精确到毫秒级,41位的长度可以使用69年。时间位还有一个很重要的作用是可以根据时间进行排序。
+ 10位的机器标识,10位的长度最多支持部署1024个节点。
+ 12位的计数序列号,序列号即一系列的自增id,可以支持同一节点同一毫秒生成多个ID序号,12位的计数序列号支持每个节点每毫秒产生4096个ID序号。
 

package com.dmsdbj.cloud.tool.uuid;

import lombok.ToString;

/**
 * @DESCRIPTION 雪花算法生成器
 * @author sunshine
 * @date 2018年11月23日15:33:39
 */
@ToString
public class SnowflakeIdFactory {
    /**
     * 雪花算法解析 结构 snowflake的结构如下(每部分用-分开):
     * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 - 000000000000
     * 第一位为未使用,接下来的41位为毫秒级时间(41位的长度可以使用69年),然后是5位datacenterId和5位workerId(10
     * 位的长度最多支持部署1024个节点) ,最后12位是毫秒内的计数(12位的计数顺序号支持每个节点每毫秒产生4096个ID序号)
     *
     * 一共加起来刚好64位,为一个Long型。(转换成字符串长度为18)
     *
     **/

    /** 开始时间截 (2015-01-01) */
    private final long twepoch = 1288834974657L;
    /** 机器id所占的位数 */
    private final long workerIdBits = 5L;
    /** 数据标识id所占的位数 */
    private final long datacenterIdBits = 5L;
    /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);
    /** 支持的最大数据标识id,结果是31 */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
    /** 序列在id中占的位数 */
    private final long sequenceBits = 12L;
    /** 机器ID向左移12位 */
    private final long workerIdShift = sequenceBits;
    /** 数据标识id向左移17位(12+5) */
    private final long datacenterIdShift = sequenceBits + workerIdBits;
    /** 时间截向左移22位(5+5+12) */
    private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);
    /** 工作机器ID(0~31) */
    private long workerId;
    /** 数据中心ID(0~31) */
    private long datacenterId;
    /** 毫秒内序列(0~4095) */
    private long sequence = 0L;
    /** 上次生成ID的时间截 */
    private long lastTimestamp = -1L;

    public SnowflakeIdFactory() {
        this.datacenterId = maxDatacenterId;
        this.workerId = maxWorkerId;
    }

    /**
     * 构造函数
     * @param workerId 工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public SnowflakeIdFactory(long workerId, long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return SnowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            //服务器时钟被调整了,ID生成器停止服务.
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }

        // 如果是同一时间生成的,则进行毫秒内序列
        // sequenceMask 为啥是4095  2^12 = 4096
        if (lastTimestamp == timestamp) {
            // 每次+1
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出
            if (sequence == 0) {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = tilNextMillis(lastTimestamp);
            }
        }
        // 时间戳改变,毫秒内序列重置
        else {
            sequence = 0L;
        }

        // 上次生成ID的时间截
        lastTimestamp = timestamp;
        return ((timestamp - twepoch) << timestampLeftShift) | (datacenterId << datacenterIdShift) | (workerId << workerIdShift) | sequence;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    public static void main(String[] args) {
//        log.info("开始生成id");
        System.out.println(System.currentTimeMillis());
        SnowflakeIdFactory idWorker = new SnowflakeIdFactory(1, 1);
        long startTime = System.nanoTime();
        for (int i = 0; i < 50000; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
        System.out.println((System.nanoTime() - startTime) / 1000000 + "ms");
    }
}
/*
 * 版权属于:yitter(yitter@126.com)
 * 开源地址:https://gitee.com/yitter/idgenerator
 */
package com.github.yitter.core;

import com.github.yitter.contract.ISnowWorker;
import com.github.yitter.contract.IdGeneratorOptions;
import com.github.yitter.contract.OverCostActionArg;
import com.github.yitter.contract.IdGeneratorException;

public class SnowWorkerM1 implements ISnowWorker {

    /**
     * 基础时间
     */
    protected final long BaseTime;

    /**
     * 机器码
     */
    protected final short WorkerId;

    /**
     * 机器码位长
     */
    protected final byte WorkerIdBitLength;

    /**
     * 自增序列数位长
     */
    protected final byte SeqBitLength;

    /**
     * 最大序列数(含)
     */
    protected final int MaxSeqNumber;

    /**
     * 最小序列数(含)
     */
    protected final short MinSeqNumber;

    /**
     * 最大漂移次数
     */
    protected final int TopOverCostCount;

    protected final byte _TimestampShift;
    protected final static byte[] _SyncLock = new byte[0];

    protected short _CurrentSeqNumber;
    protected long _LastTimeTick = 0;
    protected long _TurnBackTimeTick = 0;
    protected byte _TurnBackIndex = 0;

    protected boolean _IsOverCost = false;
    protected int _OverCostCountInOneTerm = 0;
    protected int _GenCountInOneTerm = 0;
    protected int _TermIndex = 0;

    public SnowWorkerM1(IdGeneratorOptions options) {
        BaseTime = options.BaseTime != 0 ? options.BaseTime : 1582136402000L;
        WorkerIdBitLength = options.WorkerIdBitLength == 0 ? 6 : options.WorkerIdBitLength;
        WorkerId = options.WorkerId;
        SeqBitLength = options.SeqBitLength == 0 ? 6 : options.SeqBitLength;
        MaxSeqNumber = options.MaxSeqNumber <= 0 ? (1 << SeqBitLength) - 1 : options.MaxSeqNumber;
        MinSeqNumber = options.MinSeqNumber;
        TopOverCostCount = options.TopOverCostCount == 0 ? 2000 : options.TopOverCostCount;
        _TimestampShift = (byte) (WorkerIdBitLength + SeqBitLength);
        _CurrentSeqNumber = MinSeqNumber;
    }

    private void DoGenIdAction(OverCostActionArg arg) {

    }

    private void BeginOverCostAction(long useTimeTick) {

    }

    private void EndOverCostAction(long useTimeTick) {
        if (_TermIndex > 10000) {
            _TermIndex = 0;
        }
    }

    private void BeginTurnBackAction(long useTimeTick) {

    }

    private void EndTurnBackAction(long useTimeTick) {

    }

    private long NextOverCostId() {
        long currentTimeTick = GetCurrentTimeTick();

        if (currentTimeTick > _LastTimeTick) {
            EndOverCostAction(currentTimeTick);

            _LastTimeTick = currentTimeTick;
            _CurrentSeqNumber = MinSeqNumber;
            _IsOverCost = false;
            _OverCostCountInOneTerm = 0;
            _GenCountInOneTerm = 0;

            return CalcId(_LastTimeTick);
        }

        if (_OverCostCountInOneTerm >= TopOverCostCount) {
            EndOverCostAction(currentTimeTick);

            _LastTimeTick = GetNextTimeTick();
            _CurrentSeqNumber = MinSeqNumber;
            _IsOverCost = false;
            _OverCostCountInOneTerm = 0;
            _GenCountInOneTerm = 0;

            return CalcId(_LastTimeTick);
        }

        if (_CurrentSeqNumber > MaxSeqNumber) {
            _LastTimeTick++;
            _CurrentSeqNumber = MinSeqNumber;
            _IsOverCost = true;
            _OverCostCountInOneTerm++;
            _GenCountInOneTerm++;

            return CalcId(_LastTimeTick);
        }

        _GenCountInOneTerm++;
        return CalcId(_LastTimeTick);
    }

    private long NextNormalId() throws IdGeneratorException {
        long currentTimeTick = GetCurrentTimeTick();

        if (currentTimeTick < _LastTimeTick) {
            if (_TurnBackTimeTick < 1) {
                _TurnBackTimeTick = _LastTimeTick - 1;
                _TurnBackIndex++;

                // 每毫秒序列数的前5位是预留位,0用于手工新值,1-4是时间回拨次序
                // 最多4次回拨(防止回拨重叠)
                if (_TurnBackIndex > 4) {
                    _TurnBackIndex = 1;
                }
                BeginTurnBackAction(_TurnBackTimeTick);
            }

//            try {
//                 Thread.sleep(1);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }

            return CalcTurnBackId(_TurnBackTimeTick);
        }

        // 时间追平时,_TurnBackTimeTick清零
        if (_TurnBackTimeTick > 0) {
            EndTurnBackAction(_TurnBackTimeTick);
            _TurnBackTimeTick = 0;
        }

        if (currentTimeTick > _LastTimeTick) {
            _LastTimeTick = currentTimeTick;
            _CurrentSeqNumber = MinSeqNumber;

            return CalcId(_LastTimeTick);
        }

        if (_CurrentSeqNumber > MaxSeqNumber) {
            BeginOverCostAction(currentTimeTick);

            _TermIndex++;
            _LastTimeTick++;
            _CurrentSeqNumber = MinSeqNumber;
            _IsOverCost = true;
            _OverCostCountInOneTerm = 1;
            _GenCountInOneTerm = 1;

            return CalcId(_LastTimeTick);
        }

        return CalcId(_LastTimeTick);
    }

    private long CalcId(long useTimeTick) {
        long result = ((useTimeTick << _TimestampShift) +
                ((long) WorkerId << SeqBitLength) +
                (int) _CurrentSeqNumber);

        _CurrentSeqNumber++;
        return result;
    }

    private long CalcTurnBackId(long useTimeTick) {
        long result = ((useTimeTick << _TimestampShift) +
                ((long) WorkerId << SeqBitLength) + _TurnBackIndex);

        _TurnBackTimeTick--;
        return result;
    }

    protected long GetCurrentTimeTick() {
        long millis = System.currentTimeMillis();
        return millis - BaseTime;
    }

    protected long GetNextTimeTick() {
        long tempTimeTicker = GetCurrentTimeTick();

        while (tempTimeTicker <= _LastTimeTick) {
            tempTimeTicker = GetCurrentTimeTick();
        }

        return tempTimeTicker;
    }

    @Override
    public long nextId() {
        synchronized (_SyncLock) {
            return _IsOverCost ? NextOverCostId() : NextNormalId();
        }
    }
}

 

java 5种创建对象的方式

作为Java开发者,我们每天创建很多对象,但我们通常使用依赖管理系统,比如Spring去创建对象。然而这里有很多创建对象的方法,我们会在这篇文章中学到。

Java中有5种创建对象的方式,下面给出它们的例子还有它们的字节码

使用new关键字} → 调用了构造函数
使用Class类的newInstance方法} → 调用了构造函数
使用Constructor类的newInstance方法} → 调用了构造函数
使用clone方法} → 没有调用构造函数
使用反序列化} → 没有调用构造函数

如果你运行了末尾的的程序,你会发现方法1,2,3用构造函数创建对象,方法4,5没有调用构造函数。

1.使用new关键字

这是最常见也是最简单的创建对象的方式了。通过这种方式,我们可以调用任意的构造函数(无参的和带参数的)。

Employee emp1 = new Employee();
0: new           #19          // class org/programming/mitra/exercises/Employee
3: dup
4: invokespecial #21          // Method org/programming/mitra/exercises/Employee."":()V

2.使用Class类的newInstance方法

我们也可以使用Class类的newInstance方法创建对象。这个newInstance方法调用无参的构造函数创建对象。

我们可以通过下面方式调用newInstance方法创建对象:

Employee emp2 = (Employee) Class.forName("org.programming.mitra.exercises.Employee").newInstance();
或者

Employee emp2 = Employee.class.newInstance();
51: invokevirtual    #70    // Method java/lang/Class.newInstance:()Ljava/lang/Object;

3.使用Constructor类的newInstance方法

和Class类的newInstance方法很像, java.lang.reflect.Constructor类里也有一个newInstance方法可以创建对象。我们可以通过这个newInstance方法调用有参数的和私有的构造函数。

Constructor<Employee> constructor = Employee.class.getConstructor();
Employee emp3 = constructor.newInstance();
111: invokevirtual  #80  // Method java/lang/reflect/Constructor.newInstance:([Ljava/lang/Object;)Ljava/lang/Object;

这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。这也是众多框架,如Spring、Hibernate、Struts等使用后者的原因。想了解这两个newInstance方法的区别,请看这篇Creating objects through Reflection in Java with Example.

4.使用clone方法

无论何时我们调用一个对象的clone方法,jvm就会创建一个新的对象内存,将前面对象的内容全部拷贝进去。用clone方法创建对象并不会调用任何构造函数。

要使用clone方法,我们需要先实现Cloneable接口并实现其定义的clone方法。

Employee emp4 = (Employee) emp3.clone();
162: invokevirtual #87  // Method org/programming/mitra/exercises/Employee.clone ()Ljava/lang/Object;

5.使用反序列化

当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。
为了反序列化一个对象,我们需要让我们的类实现Serializable接口

ObjectInputStream in = new ObjectInputStream(new FileInputStream("data.obj"));
Employee emp5 = (Employee) in.readObject();
261: invokevirtual  #118   // Method java/io/ObjectInputStream.readObject:()Ljava/lang/Object;

我们从上面的字节码片段可以看到,除了第1个方法,其他4个方法全都转变为invokevirtual(创建对象的直接方法),第一个方法转变为两个调用,new和invokespecial(构造函数调用)。

 

java里面的几个有序map

TreeMap,TreeSet,LinkedHashMap

LinkedHashMap

LinkedHashMap是基于HashMap的子类,虽然LinkedHashMap增加了时间和空间上的开销,但是它通过维护一个额外的双向链表保证了迭代顺序。特别地,该迭代顺序可以是插入顺序,也可以是访问顺序。其中LinkedHashMap的默认实现是按插入顺序排序的。

与HashMap相比,LinkedHashMap增加了两个属性用于保证迭代顺序,分别是 双向链表头结点header 和 标志位accessOrder (值为true时,表示按照访问顺序迭代;值为false时,表示按照插入顺序迭代)。LinkedHashMap采用的hash算法和HashMap相同,但是它重新定义了Entry。LinkedHashMap中的Entry增加了两个指针 before 和 after,它们分别用于维护双向链接列表。特别需要注意的是,next用于维护HashMap各个桶中Entry的连接顺序,before、after用于维护Entry插入的先后顺序的,源代码如下:

Map 综述(二):彻头彻尾理解 LinkedHashMap    https://blog.csdn.net/justloveyou_/article/details/71713781

 

Java  高并发下random    ThreadLocalRandom

处于性能的考虑,random类在高并发下会导致大量线程竞争同一个原子变量,导致大量线程原地自旋转,从而浪费cpu资源

Random类除了提供无参的构造方法以外,还提供了有参的构造方法,我们可以传入int类型的参数,这个参数就被称为“种子”,这样“种子”就固定了,生成的随机数也都是一样了:

//导致输出10个相同的41
public static void main(String[] args) {
    for (int i = 0; i < 10; i++) {
        Random random = new Random(15);
        System.out.println(random.nextInt(100));
    }
}

//因为获取新的种子算法是固定的
//这个是random真正的种子算法
protected int next(int bits) {
        long oldseed, nextseed;
        AtomicLong seed = this.seed;
        do {
            oldseed = seed.get();
            nextseed = (oldseed * multiplier + addend) & mask;
        } while (!seed.compareAndSet(oldseed, nextseed));
        return (int)(nextseed >>> (48 - bits));
    }

Java  ConcurrentLinkedQueue

//待定,UNSAFE很重要

 

Java AQS源码详解

UNSAFE.unpark(thread);//唤醒某个线程

长久以来对线程阻塞与唤醒经常我们会使用object的wait和notify,除了这种方式,java并发包还提供了另外一种方式对线程进行挂起和恢复,它就是并发包子包locks提供的LockSupport
虽然LockSupport定义这三个字段但是基本没有使用,可能之后JDK会有所变化吧。这里parkBlockerOffset字段,解释起来就是挂起线程对象的偏移地址,对应的是Thread类的parkBlocker。

这个字段可以理解为专门为LockSupport而设计的,它被用来记录线程是被谁堵塞的,当程序出现问题时候,通过线程监控分析工具可以找出问题所在。注释说该对象被LockSupport的
getBlocker和setBlocker来获取和设置,且都是通过地址偏移量方式获取和修改的。由于这个变量LockSupport提供了park(Object parkblocker)方法

LockSupport中的park() 和 unpark() 的作用分别是阻塞线程和解除阻塞线程,而且park()和unpark()不会遇到“Thread.suspend 和 Thread.resume所可能引发的死锁”问题。

Java并发之AQS详解  https://www.cnblogs.com/waterystone/p/4920797.html

java并发包系列---LockSupport   https://blog.csdn.net/opensure/article/details/53349698

AQS的公平锁和非公平锁之间的区别在于在tryAcquire中,公平锁会先判断是否有pre node,如果有的话,他不会去获取锁,而非公平锁在tryAcquire中会直接尝试去获取锁资源。;

ReentranReadWriteLock这个接口写锁可重入次数有限制,限制为65535

 

 

java底层对不同jar包同类名(完全限定名)如何加载

当JAR包都属于同一个类加载器是,它们的加载顺序就是由系统的文件加载顺序来决定的。这往往就是因为环境的不同导致诡异的类冲突问题的元凶。由于大多数的容器的ClassLoader在获取对应录下的文件列表时是不会自己排序的,所以加载的顺序就依赖于底层文件系统返回的顺序。当系统的文件排序规则不一致时,就会发生上面的现象。在Linux系统中文件的顺序是由iNode的顺序来决定的。这时让我们来看一下两个不同环境下对相同JAR的排序是什么样的。
 

a. 公司环境

[root@home lib]# ls -i | sort
2359300 A.jar -- 该JAR包中的类是希望调用的
2359301 B.jar
1
2
3
b. 客户环境

[root@partner lib]# ls -i | sort
1235909 B.jar
1236002 A.jar -- 该JAR包中的类是希望调用的
1
2
3

在构建工具中使用插件

当使用maven时可以采用maven-enforcer-plugin,这个强大的maven插件,配合extra-enforcer-rules工具,能自动扫描Jar包将冲突检测并打印出来。

遗憾的是gradle中没有相应的插件,而我们日常使用的构建工具就是gradle

使用Linux命令

为了弥补gralde中没有现成插件的遗憾,当我们知道冲突的类名以后,我们可以在Linux中执行以下命令来检索冲突类出现在那些jar包中

此时很明显可以看到上面的现象是由文件系统的排序不同导致的

Java中重名类冲突处理机制和Jar包加载顺序

  • 同一个ClassLoader实例加载的类不能重复(不同的class文件,同样的类名也是重复),如果强行用同一个ClassLoader实例加载同一个类,则会报错attempted duplicate class definition for {your class}
  • java -classpath(-cp)加载配置jar包(classes)时,会按照书写定义顺序加载class,之后重复加载的class会被忽略,只有第一个生效
  • Idea中可以通过在Project Settings -> Modules -> Dependencies中通过上下箭头调整jar加载顺序,其实也就是调整-classpath(-cp)后的jar包书写顺序
  • Tomcat下的jar包貌似不同版本加载策略不同
  • spring-boot是自定义的jar包加载策略,顺序未确认,猜测默认是按字母排序
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值