java深入源码

java深入源码知识

1、哪些情况下的对象会被垃圾回收机制处理掉?

java 的垃圾回收是 java 语言的重要功能之一。当程序创建对象、数组等引用类型实体时,系统会在堆内存中位置分配一块内存区,对象就保存在这块内存区中,当这块内存不在被任何变量引用时,这块内存就变成垃圾,等待垃圾回收机制进行回收。

垃圾回收机制只负责回收堆内存中的对象。当一个对象在堆内存中运行时,根据它被引用变量引用的状态,可以把它所处的状态分成如下三种。

  • 可达状态:当一个对象被创建后,若有一个以上的引用变量引用它,则这个对象在程序中处于可达状态,程序可通过引用变量来调用该对象的实例变量和方法。
  • 可恢复状态:如果程序中某个对象不再有任何变量引用它,它就进入了可恢复状态。在这种状态下,系统的垃圾回收机制准备回收该对象的所占用的内存,再回收该对象之前,系统会调用所有可恢复状态对象的finalize()方法进行资源清理。如果系统在调用 finalize() 方法时重新让一个引用变量引用该对象,则这个对象会再次变为可达状态;否则该对象进入不可达状态。
  • 不可达状态:当对象与所有引用变量的联系被切断,且系统已经调用所有对象的finalize()方法后依然没有使该对象变成可达状态,系统会回收该对象所占有的资源。

2、Java线程池

线程池的作用就是控制运行的线程数量,处理过程中将任务放入队列,然后线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务执行。

线程复用,控制并发数,管理线程

ThreadPoolExecutor

java.uitl.concurrent.ThreadPoolExecutor类是线程池中最核心的一个类,因此如果要透彻地了解Java中的线程池,必须先了解这个类。下面我们来看一下ThreadPoolExecutor类的具体实现源码。

提供了4个构造方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);

}

public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}


public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}

 从上面的代码可以得知,ThreadPoolExecutor继承了AbstractExecutorService类,并提供了四个构造器,事实上,通过观察每个构造器的源码具体实现,发现前面三个构造器都是调用的第四个构造器进行的初始化工作。

下面解释下一下构造器中各个参数的含义:

  • corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

  • maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;

  • keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;

  • unit:参数keepAliveTime的时间单位,有7种取值,在TimeUnit类中有7种静态属性:

    1
    2
    3
    4
    5
    6
    7
    TimeUnit.DAYS;               //天
    TimeUnit.HOURS; //小时
    TimeUnit.MINUTES; //分钟
    TimeUnit.SECONDS; //秒
    TimeUnit.MILLISECONDS; //毫秒
    TimeUnit.MICROSECONDS; //微妙
    TimeUnit.NANOSECONDS; //纳秒
  • workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:

    1
    2
    3
    4
    ArrayBlockingQueue;
    PriorityBlockingQueue
    LinkedBlockingQueue;
    SynchronousQueue;

    ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。

  • threadFactory:线程工厂,主要用来创建线程;

  • handler:表示当拒绝处理任务时的策略,有以下四种取值:

    当队列满了,并且工作线程大于等于线程池的最大数时,如何拒绝需要请求的runnable

    1
    2
    3
    4
      ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 (默认	)
    ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
    ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
    ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务 ,退回给原线程,让他去处理,让他先忙着,不要再发任务了,线程池先处理已经有的任务了。

    线程池状态

    1
    2
    3
    4
    5
    6
    7
    8
    9
    private static final int COUNT_BITS = Integer.SIZE - 3;
    private static final int CAPACITY = (1 << COUNT_BITS) - 1;

    // runState is stored in the high-order bits
    private static final int RUNNING = -1 << COUNT_BITS;
    private static final int SHUTDOWN = 0 << COUNT_BITS;
    private static final int STOP = 1 << COUNT_BITS;
    private static final int TIDYING = 2 << COUNT_BITS;
    private static final int TERMINATED = 3 << COUNT_BITS;

常用的线程池实现类

  • Executors.newFixedThreadPool (固定线程数)
  • Executors.newSingleThreadExecutor (单一线程)
  • Executors.newCachedThreadPool (执行很多短期异步任务,线程池根据需要创建新的线程,但在先前构建的线程可用时将重用他们,可以扩容,遇强则强)

线程池运行流程

  • 任务提交,如果核心线程没有用完,创新线程处理任务。如果核心线程用完了,检查阻塞队列是否满了,如果没满就放在阻塞队列里,如果满了就扩容核心数至最大线程数,创建线程去执行任务。如果阻塞队列满了,且线程池线程数已经达到最大线程数,按照拒绝策略进行拒绝。

3、ArrayList的扩容机制

  • ArrayList实现了List接口,继承了AbstractList,底层是数组实现的,一般我们把它认为是可以自增扩容的数组。它是非线程安全的,一般多用于单线程环境下(与Vector最大的区别就是,Vector是线程安全的,所以ArrayList 性能相对Vector 会好些),它实现了Serializable接口,因此它支持序列化,能够通过序列化传输(实际上java类库中的大部分类都是实现了这个接口的),实现了RandomAccess接口,支持快速随机访问(只是个标注接口,并没有实际的方法),这里主要表现为可以通过下标直接访问(底层是数组实现的,所以直接用数组下标来索引),实现了Cloneable接口,能被克隆。

  • ArrayList提供了3个构造方法

  • 先看一下无参的构造方法

    1
    2
    3
    4
    5
    6
    /**
    * Constructs an empty list with an initial capacity of ten.
    */
    public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
    1
    2
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};//默认空数组
    transient Object[] elementData; // non-private to simplify nested class access

    这里的将默认的空数组指向elementData。

  • 在看一下带参的构造方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
    // c.toArray might (incorrectly) not return Object[] (see 6260652)
    if (elementData.getClass() != Object[].class)
    elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
    // replace with empty array.
    this.elementData = EMPTY_ELEMENTDATA;
    }
    }

    可以看到将集合c转为数组指向elementData,这里的size是Arraylist的私有变量,是指元素个数。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
    this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
    this.elementData = EMPTY_ELEMENTDATA;
    } else {
    throw new IllegalArgumentException("Illegal Capacity: "+
    initialCapacity);
    }
    }
    1
    2
    3
    4
    /**
    * Shared empty array instance used for empty instances.
    */
    private static final Object[] EMPTY_ELEMENTDATA = {};

    如果参数大于0则创建一个长度为initialCapacity的数组指向elementData,如果为0则指向空数组(这里的空数组不是DEFAULTCAPACITY_EMPTY_ELEMENTDATA 这个数组)。

  • add方法

    1
    2
    3
    4
    5
    public boolean add(E e) {
    ensureCapacityInternal(size + 1); // Increments modCount!!
    elementData[size++] = e;
    return true;
    }

    首先调用了ensureCapacityInternal()方法。size是当前集合拥有的元素个数(未算进准备新增的e元素),从源码看出,调用了ensureCapacityInternal来保证容量问题,传进去的参数是size+1,来保证新增元素后容量满足要求。

    1
    2
    3
    private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
    }

    再看calculateCapacity()方法

    1
    2
    3
    4
    5
    6
    private static int calculateCapacity(Object[] elementData, int minCapacity) {
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
    return Math.max(DEFAULT_CAPACITY, minCapacity);
    }
    return minCapacity;
    }

    进一步判断elementData是否为默认的空数组,即是否调用了无参构造函数。如果是的话,则在DEFAULT_CAPACITY和minCapacity取一个大的,DEFAULT_CAPACITY默认为10

    1
    private static final int DEFAULT_CAPACITY = 10;

    所以,如果此时为空数组的话,size=0,size+1=1,与DEFAULT_CAPACITY比起来返回10.,所以没有指定大小,默认初始为一个容量为10的数组。然后在调用ensureExplicitCapacity()方法.

    1
    2
    3
    4
    5
    6
    7
    private void ensureExplicitCapacity(int minCapacity) {
    modCount++;

    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
    grow(minCapacity);
    }

    这里的modCount用在集合的Fail-Fast机制(即快速失败机制)的判断中使用的。稍后再看。

    这里看到新增元素后的minCapacity大小与当前集合的大小相比,如果大于当前集合则扩容,调用grow()方法.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    private void grow(int minCapacity) {
    // overflow-conscious code
    int oldCapacity = elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if (newCapacity - minCapacity < 0)
    newCapacity = minCapacity;
    if (newCapacity - MAX_ARRAY_SIZE > 0)
    newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    elementData = Arrays.copyOf(elementData, newCapacity);
    }
    1
    private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

    可以看到这里先取到当前集合的长度,进行1.5倍增加,这里>>1位运算右移1位,相当于/2即,oldCapacity+oldCapacity/2。

    然后扩容后的长度与minCapactiy相比,如果比所需的最小长度要小,则直接将最小长度指向最小需要的长度。

    如果扩容后的长度比MAX_ARRAY_SIZE还长,则调用hugeCapacity()方法。传入的参数是当前扩容需要的最小长度minCapacity。

    1
    2
    3
    4
    5
    6
    7
    private static int hugeCapacity(int minCapacity) {
    if (minCapacity < 0) // overflow
    throw new OutOfMemoryError();
    return (minCapacity > MAX_ARRAY_SIZE) ?
    Integer.MAX_VALUE :
    MAX_ARRAY_SIZE;
    }

    可以看到如果minCapacity大于MAX_ARRAY_SIZE,则返回Integer的最大值。否则返回MAX_ARRAY_SIZE。

    然后回到grow方法,调用Arrays.copyof方法,即复制原数组内容到一个新容量的大数组里。这里Arrays.copyof方法实际是调用System.arraycopy方法。

    应该可以很清楚的知道ArrayList底层扩容的原理了。ArrayList是1.5倍逐渐增长

    当然,如果一开始知道数据量很大的话,可以在初始化时预先指定容量。

  • get方法

    1
    2
    3
    4
    5
    public E get(int index) {
    rangeCheck(index);

    return elementData(index);
    }
    1
    2
    3
    4
    private void rangeCheck(int index) {
    if (index >= size)
    throw new IndexOutOfBoundsException(outOfBoundsMsg(index));
    }

    通过数组下标索引来指定返回的内容.

  • Java容器的快速报错机制ConcurrentModificationException

    Java容器有一种保护机制,能够防止多个进程同时修改同一个容器的内容。如果你在迭代遍历某个容器的过程中,另一个进程介入其中,并且插入,删除或修改此容器的某个对象,就会立刻抛出ConcurrentModificationException。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    public Iterator<E> iterator() {
    return new Itr();
    }

    /**
    * An optimized version of AbstractList.Itr
    */
    private class Itr implements Iterator<E> {
    int cursor; // index of next element to return
    int lastRet = -1; // index of last element returned; -1 if no such
    int expectedModCount = modCount;

    Itr() {}

    public boolean hasNext() {
    return cursor != size;
    }

    @SuppressWarnings("unchecked")
    public E next() {
    checkForComodification();
    int i = cursor;
    if (i >= size)
    throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
    throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
    }

    public void remove() {
    if (lastRet < 0)
    throw new IllegalStateException();
    checkForComodification();

    try {
    ArrayList.this.remove(lastRet);
    cursor = lastRet;
    lastRet = -1;
    expectedModCount = modCount;
    } catch (IndexOutOfBoundsException ex) {
    throw new ConcurrentModificationException();
    }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void forEachRemaining(Consumer<? super E> consumer) {
    Objects.requireNonNull(consumer);
    final int size = ArrayList.this.size;
    int i = cursor;
    if (i >= size) {
    return;
    }
    final Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length) {
    throw new ConcurrentModificationException();
    }
    while (i != size && modCount == expectedModCount) {
    consumer.accept((E) elementData[i++]);
    }
    // update once at end of iteration to reduce heap write traffic
    cursor = i;
    lastRet = i - 1;
    checkForComodification();
    }

    final void checkForComodification() {
    if (modCount != expectedModCount)
    throw new ConcurrentModificationException();
    }
    }

    从上面方法可以看到在迭代遍历的过程中都调用了方法checkForComodification来判断当前ArrayList是否是同步的。现在来举一个栗子,假设你往一个Integer类型的ArrayList插入了10条数据,那么每操作一次modCount(继承自父类AbstractList)就加一所以就变成10,而当你对这个集合进行遍历的时候就把modCount传到expectedModCount这个变量里,然后ArrayList在checkForComodification中通过判断两个变量是否相等来确认当前集合是否是同步的,如果不同步就抛出ConcurrentModificationException。所谓的不同步指的就是,如果你在遍历的过程中对ArrayList集合本身进行add,remove等操作时候就会发生。当然如果你用的是Iterator那么使用它的remove是允许的因为此时你直接操作的不是ArrayList集合而是它的Iterator对象。

    所以ArrayList循环删除元素时,是不可以调用remove方法,会抛出ConcurrentModificationException()异常。

4、Java的4种引用方式

  • 强引用 是指创建一个对象并把这个对象赋给一个引用变量。例如 String a=”ss”;强引用有引用变量指向时永远不会被垃圾回收,JVM宁愿抛出OutOfMemory错误也不会回收这种对象。

  • 软引用 (SoftReference)如果一个对象具有软引用,内存空间足够,垃圾回收器就不会回收它;只要垃圾回收器没有回收它,该对象就可以被程序使用。在SoftReference指向的对象被回收后, SoftReference本身其实也就没有用了. java提供了一个ReferenceQueue来保存这些所指向的对象已经被回收的reference. 用法是在定义SoftReference的时候将一个ReferenceQueue的对象作为参数传入构造函。

  • 弱引用(WeakReference)弱引用与软引用差不多都属于可有可无;弱引用与软引用的区别在于:只具有弱引用的对象生命周期更短,垃圾回收器一旦发现就只具有弱引用的对象,不管当前内存是否足够,都会将其回收,但是垃圾回收器是一个优先级很低的线程,不一定会很快的发现那些只具有弱引用的对象,弱引用可用于解决内存泄漏的问题

  • 虚引用(PhantomReference)虚引用不论所引用的对象是不是null,不论内存空间是否充足,都会被垃圾回收器回收

    可以看到,只要进行垃圾回收,虚引用就会被回收

    总结:

    对于垃圾回收器回收的顺序为

    虚引用—弱引用—-软引用—强引用。

    多使用软引用做缓存可以很好地避免oom.

5、常见的编码方式

  • Unicode

    如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是 Unicode,就像它的名字都表示的,这是一种所有符号的编码。

    Unicode 当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639表示阿拉伯字母AinU+0041表示英语的大写字母AU+4E25表示汉字。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表

  • UTF-8

    UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。

    UTF-8 的编码规则很简单,只有二条:

    1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。

    2)对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n + 1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。

  • ISO-8859-1

    ISO 组织在 ASCII 码基础上又制定了一些列标准用来扩展 ASCII 编码,它们是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵盖了大多数西欧语言字符,所有应用的最广泛。ISO-8859-1 仍然是单字节编码,它总共能表示 256 个字符

  • ASCII

    学过计算机的人都知道 ASCII 码,总共有 128 个,用一个字节的低 7 位表示,0~31 是控制字符如换行回车删除等;32~126 是打印字符,可以通过键盘输入并且能够显示出来

  • GB2312

    它的全称是《信息交换用汉字编码字符集 基本集》,它是双字节编码,总的编码范围是 A1-F7,其中从 A1-A9 是符号区,总共包含 682 个符号,从 B0-F7 是汉字区,包含 6763 个汉字。

6、UTF-8编码中的中文占几个字节,int几个字节

​ 如果一个字节,最高位为0,表示这是一个ASCII字符(00~7F)
​ 如果一个字节,以11开头,连续的1的个数暗示这个字符的字节数

中文需要3个字节。int需要1个字节。

7、静态代理和动态代理的区别,什么场景使用?

​ 代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务

Java动态代理的优势是实现无侵入式的代码扩展,也就是方法的增强;让你可以在不用修改源码的情况下,增强一些方法;在方法的前后你可以做你任何想做的事情(甚至不去执行这个方法就可以)。

  • 静态代理:由程序员创建或由特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。 静态代理事先知道要代理的是什么
  • 动态代理:动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler接口提供了生成动态代理类的能力。

8、Java的异常体系

常见异常

ConcurrentModificationException

OutOfMemoryError

StackOverflowError

9、Java位运算符

  • &=

    与运算,都为1才为1,如:a=1,b=2, a&=b 即a=a&b a为1

  • |=

    非运算,只要有1即为1,如:a=1,b=2 a|=b 即a=a|b a为3

  • ^=

    异或运算,相同为0不同为1,如:a=1,b=2 a^=b 即a=a^b a为2

  • <<=

    左移b位,如a =3,b=2 ,a为11 ,左移 1100即a=12,也是a 2 2

  • 》》=

    右移2位,如a=3,b=1,a为11,右移 1即a=1,也是a/2

  • 》》》

    与》》的区别是,高位用0补,如a=3,b=1 a为11, a变为001 a为1

  • ~=非运算

    非运算 二进制所有的位数取反,因为为运算都是按补码计算的,正数补码就是原码,负数的补码

    例如~3=-4

    int是4个字节,为32位

    3即00000000 00000000 00000000 00000011

    取反后为,依然是补码,我们需要转为原码

    11111111 11111111 11111111 11111100

    最高位是1 ,负数,所以负数的补码转为原码,左右的第一个1不变,其余数取反。

    即10000000 00000000 00000000 00000100

    即-4

JUC

Java.util.concurrent

对于容器类,在多线程下如果不进行并发处理的话,会报java.util.ConcurrentModificationException

例如Arraylist循环进行删除的时候会跑出此异常,所以真正去修改的时候应该使用迭代器iterator循环删除

List

Arraylist 不安全,Vector都加了synchronized了,效率低。

Arraylist默认大小10,每次扩容的时候,扩容到1.5倍,所以他有一个单独的属性size来返回元素数量,而不是数组的长度、

JUC提供了java.util.concurrent.CopyOnWriteArrayList读写分离,写时进行赋值,写的时候加锁,将原数据进行赋值一份,然后更新掉原数据,释放锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Appends the specified element to the end of this list.
*
* @param e element to be appended to this list
* @return {@code true} (as specified by {@link Collection#add})
*/
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

Set

HashSet,底层实际上是HashMap,key是元素对象,value是一个常量

JUC提供了java.util.concurrent.CopyOnWriteArraySet底层还是CopyOnWriteArrayList,不过在存储的时候走的是addIfAbsent方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private boolean addIfAbsent(E e, Object[] snapshot) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] current = getArray();
int len = current.length;
if (snapshot != current) {
// Optimize for lost race to another addXXX operation
int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++)
if (current[i] != snapshot[i] && eq(e, current[i]))
return false;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}

Map

HashMap,线程不安全,他是通过hashCode和equals方法来去重,默认初始容量是16 即1<<4,负载因子是0.75,每次扩容是2倍,HashMap是先插入数据再进行扩容的,但是如果是刚刚初始化容器的时候是先扩容再插入数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
 */
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
boolean evict) {
Node<K,V>[] tab; Node<K,V> p; int n, i;
//空表的话,需要第一次扩容
if ((tab = table) == null || (n = tab.length) == 0)
n = (tab = resize()).length;
//hashCode不重复,新增
if ((p = tab[i = (n - 1) & hash]) == null)
tab[i] = newNode(hash, key, value, null);
//hashCode重复,检查是否equeals相同,相同再检查是否是红黑树,是红黑树的话,放入红黑树,不是的话就放在链表尾部
else {
Node<K,V> e; K k;
if (p.hash == hash &&
((k = p.key) == key || (key != null && key.equals(k))))
e = p;
else if (p instanceof TreeNode)
e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
else {
for (int binCount = 0; ; ++binCount) {
if ((e = p.next) == null) {
p.next = newNode(hash, key, value, null);
if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
treeifyBin(tab, hash);
break;
}
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
p = e;
}
}
if (e != null) { // existing mapping for key
V oldValue = e.value;
if (!onlyIfAbsent || oldValue == null)
e.value = value;
afterNodeAccess(e);
return oldValue;
}
}
//非首次扩容是,先插入数据,再扩容
++modCount;
if (++size > threshold)
resize();
afterNodeInsertion(evict);
return null;
}

HashMap是线程不安全的,为此JUC提供了ConcurrenHashMap

ConcurrentHashMap实现线程安全的原理,通过CAS+Volatile的方式,插入和删除结点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
//初始化数组
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
//如果这个头结点是null,直接cas插入新结点
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
//正在扩容
tab = helpTransfer(tab, f);
else {
V oldVal = null;
//头结点不为null,使用synchronized加锁,保证其他线程无法操作
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}

辅助类

  • CountDownLatch

    允许一个或多个线程等待直到在其他线程中执行的一组操作完成的同步辅助。

    调用CountDown(),减少一个计数

    调用await(),等待直至计数器为0

  • CyclicBarrier

    允许一组线程全部等待彼此达到共同屏障点的同步辅助。 循环阻塞在涉及固定大小的线程方的程序中很有用,这些线程必须偶尔等待彼此。 屏障被称为循环 ,因为它可以在等待的线程被释放之后重新使用。

  • Semaphore

    • 一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个acquire()都会阻塞,直到许可证可用,然后才能使用它。 每个release()添加许可证,潜在地释放阻塞获取方。 但是,没有使用实际的许可证对象; Semaphore只保留可用数量的计数,并相应地执行

    可以限流

    示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    public class SemaphoreDemo {

    public static void main(String[] args) {
    Semaphore semaphore=new Semaphore(3);
    for(int i=0;i<6;i++){
    new Thread(()->{
    try {
    semaphore.acquire();
    System.out.println(Thread.currentThread().getName()+"占了座位休息2秒");
    Thread.sleep(2000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }finally {
    System.out.println(Thread.currentThread().getName()+"离开了");
    semaphore.release();
    }
    },i+"线程").start();
    }
    }

    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    1线程占了座位休息2秒
    2线程占了座位休息2秒
    0线程占了座位休息2秒
    2线程离开了
    1线程离开了
    3线程占了座位休息2秒
    4线程占了座位休息2秒
    0线程离开了
    5线程占了座位休息2秒
    3线程离开了
    4线程离开了
    5线程离开了

ReadWriteLock

ReadWriteLock维护一对关联的locks ,一个用于只读操作,一个用于写入。 read lock可以由多个阅读器线程同时进行,只要没有作者。 write lock是独家的。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class Chickten1{
private int total=30;
private ReentrantReadWriteLock lock=new ReentrantReadWriteLock();
void getOne(){
lock.writeLock().lock();
try {
total--;
System.out.println(Thread.currentThread().getName()+"拿走一个还剩"+total);
}catch (Exception e){

}finally {
lock.writeLock().unlock();
}
}
void see(){
System.out.println(Thread.currentThread().getName()+"看到还剩"+total);

}

}

public class ReadWriteLockTest {
public static void main(String[] args) {

Chickten1 chickten=new Chickten1();
for(int i=0;i<5;i++){
new Thread(()->{chickten.getOne();
},i+"人").start();
}
for(int i=0;i<5;i++){
new Thread(()->{chickten.see();
},i+"人").start();
}
}
}
1
2
3
4
5
6
7
8
9
10
2人拿走一个还剩29
0人拿走一个还剩28
1人拿走一个还剩27
3人拿走一个还剩26
4人拿走一个还剩25
0人看到还剩25
1人看到还剩25
2人看到还剩25
3人看到还剩25
4人看到还剩25

BlockingQueue阻塞队列

当队列是空的时候,从队列中获取元素时阻塞的,当队列是满的时候,插入队列元素的时候阻塞。

当前线程获取时,空的时候会阻塞,直至其他线程插入元素,当前线程插入,满的时候会阻塞,直至其他元素取出元素

实现类

常用方法

这四类方法分别对应的是:
1 . ThrowsException:如果操作不能马上进行,则抛出异常
2 . SpecialValue:如果操作不能马上进行,将会返回一个特殊的值,一般是true或者false
3 . Blocks:如果操作不能马上进行,操作会被阻塞
4 . TimesOut:如果操作不能马上进行,操作会被阻塞指定的时间,如果指定时间没执行,则返回一个特殊值,一般是true或者false

ForkJoinPool

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class MyTask extends RecursiveTask<Integer> {
private final static Integer A=10;

private Integer begin;
private Integer end;
private Integer result=0;
public MyTask(Integer begin, Integer end) {
this.begin = begin;
this.end = end;
}

public Integer getResult() {
return result;
}


@Override
protected Integer compute() {
if(this.end-this.begin<=A){
for(int i=begin;i<=end;i++)
result+=i;
}else {
Integer middle=(begin+end)/2;
MyTask myTask = new MyTask(begin, middle);
MyTask myTask1 = new MyTask(middle + 1, end);
result=myTask.compute()+myTask1.compute();
}
return result;
}
}

public class ForkJoinTest {

public static void main(String[] args) throws ExecutionException, InterruptedException {
ForkJoinPool forkJoinPool=new ForkJoinPool();
ForkJoinTask<Integer> submit = forkJoinPool.submit(new MyTask(0, 100));
System.out.println(submit.get());
forkJoinPool.shutdown();
}
}

CompletableFuture

异步处理任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class CompletableFutureTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Void> xxx = CompletableFuture.runAsync(() -> {
System.out.println("xxx");
});
xxx.get();

CompletableFuture<Integer> xxx1 = CompletableFuture.supplyAsync(() -> {
System.out.println("xxx");
return 123/0;
});
Integer integer = xxx1.whenComplete((r, e) -> {
System.out.println(r);
}).exceptionally(e -> {
return 4444;
}).get();
System.out.println(integer);
}
}

JUF

java.util.function

java内置4个函数接口

函数式接口 参数类型 返回类型 用途
Consumer消费型接口 T void 对类型为T的对象应用操作,包含方法 void accept(T t)
Supplier供给型接口 T 返回类型为T的对象,包含方法 T get()
Function<T,R>函数型接口 T R 对类型为T的对象操作,并返回结果,结果是R类型的对象,包含方法 R apply(T)
Predicate断定型接口 T boolean 确定类型为T的对象是否满足约束,并返回boolean值,包含方法boolean test(T t)

Stream

Stream是数据渠道,用于操作数据源(集合,数组等)

  • stream自己不会存储元素
  • stream不会改变源对象,相反,他们会返回一个持有结果的新Stream
  • stream操作是延迟的,这意味着他们会等到需要结果的时候才执行

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class LambdaTest {
public static void main(String[] args) {
List<User> users= new ArrayList<>();
users.add(new User(1,"name1",10));
users.add(new User(2,"name2",9));
users.add(new User(3,"name3",8));
users.add(new User(4,"name4",7));
users.add(new User(5,"name5",6));
users.add(new User(6,"name6",5));

/**
* 年龄大于4,按余额排序,查出第一个
*/
users.stream().filter(user -> user.getAge()>4).sorted((u1,u2)->{return u2.getBalance()-u1.getBalance();}).limit(1)
.forEach(t->System.out.println(t.getName()));
}
}