transient是Java语言的关键字,用来表示一个成员变量不是该对象序列化的一部分。当一个对象被序列化的时候,transient型变量的值不包括在序列化的结果中。而非transient型的变量是被包括进去的。 注意static修饰的静态变量天然就是不可序列化的。
在Java中,long
是一个基本数据类型,而 Long
是一个包装类。当你看到 1l
和 1L
,它们都是对 long
类型的常量进行字面量表示。l
和 L
在这里只是标记,用来表示后面的数字是一个 long
类型,而不是 int
类型。
具体来说:
long a = 1l;
和 Long b = 1L;
都是正确的,并且它们都表示一个 long
类型的整数常量。
l
和 L
在这里没有其他区别。它们都是用来明确地表示数字是 long
类型的。
使用 Long
包装类通常在需要进行null检查或者需要进行类型转换的情况下更有用。例如,当你从方法返回值中读取一个可能为null的 long
值时,使用 Long
可以更安全。
总之,1l
和 1L
在功能上是等价的,只是风格上的选择。在Java中,通常推荐使用小写的 l
作为 long
常量的标记,因为大写的 L
可能会与数字的1混淆。
ArrayList
ArrayList实现了Serializable、RandomAccess、Cloneable、List。
ArrayList的扩容机制
ArrayList 是 Java 中的一个动态数组,它可以根据需要自动增长。当我们在 ArrayList 中添加元素时,如果当前容量不足以容纳新元素,ArrayList 会自动进行扩容。
ArrayList 的扩容机制如下:
初始容量:当我们创建一个新的 ArrayList 实例时,它通常会指定一个初始容量(initial capacity)。这个容量决定了数组列表的初始大小。例如,如果我们创建一个 ArrayList 并指定初始容量为 10,那么它就会分配一个大小为 10 的数组。
自动扩容:当我们往 ArrayList 中添加元素时,如果当前容量已满,ArrayList 会自动进行扩容。扩容时,新的容量通常是旧容量的 1.5 倍(在 JDK 1.6 和 JDK 1.7 中)或两倍(在 JDK 1.8 及更高版本中)。扩容后的新数组会被复制旧数组的所有元素(使用的是Arrays.copyOf()方法)。
ArrayList为什么可迭代
ArrayList 是一个实现了 Iterable
接口的类,这意味着它可以被迭代。Iterable
接口是 Java 集合框架的一部分,它定义了一个方法 iterator()
,该方法返回一个 Iterator
对象。Iterator
接口也定义了一些方法,如 next()
和 hasNext()
,用于遍历集合的元素。
要理解为什么 ArrayList 是可迭代的,我们首先需要了解 Iterable
和 Iterator
接口的作用。
Iterable
接口:这是所有集合类(如 ArrayList、LinkedList 等)应该实现的接口。它只有一个方法 iterator()
,该方法返回一个 Iterator
对象。通过这个 Iterator
对象,我们可以遍历集合的所有元素。
Iterator
接口:它提供了一种迭代集合元素的通用机制。每个 Iterator
对象都知道如何从给定的集合中获取元素。
当我们调用一个实现了 Iterable
接口的类的 iterator()
方法时,会返回一个 Iterator
对象。我们可以使用这个 Iterator
对象来遍历集合中的所有元素。在 Java 中,遍历集合元素通常使用 for-each 循环(也称为增强型 for 循环),这实际上就是通过 Iterator 来实现的。
ArrayList实现可迭代
ArrayList实现Iterable
写一个内部类实现Iterator
public ArrayListimplements Iterable { ... @Override public Iterator iterator() { return new ArrayListIterator(); } private class ArrayListIterator implements Iterator { private int index; @Override public boolean hasNext() { return index < size; } @Override public E next() { if(!hasNext()) throw new NoSuchElementException(); return (E) elementData[index ++ ]; } } }
ArrayList删除元素的机制
注意System.copyarray()方法的使用
/** * @description: 删除index位置的元素 * @param: [int] * @return: E */ public E remove(int index){ if (!isValued(index)) { throw new ArrayListException("out of range"); } modCount ++ ; E oldValue = get(index); int moveNum = size - 1 - index; if(moveNum > 0){ //使用System.arraycopy System.arraycopy(elementData,index + 1,elementData,index,moveNum); } elementData[-- size] = null; return oldValue; }
final:修饰符(关键字)有三种用法:修饰类、变量和方法。修饰类时,意味着它不能再派生出新的子类,即不能被继承,因此它和abstract是反义词。修饰变量时,该变量使用中不被改变,必须在声明时给定初值,在引用中只能读取不可修改,即为常量。修饰方法时,也同样只能使用,不能在子类中被重写。 finally:通常放在try…catch的后面构造最终执行代码块,这就意味着程序无论正常执行还是发生异常,这里的代码只要JVM不关闭都能执行,可以将释放外部资源的代码写在finally块中。 finalize:Object类中定义的方法,Java中允许使用finalize() 方法在垃圾收集器将对象从内存中清除出去之前做必要的清理工作。这个方法是由垃圾收集器在销毁对象时调用的,通过重写finalize() 方法可以整理系统资源或者执行其他清理工作。
阿里面试题:ConcurrentHashMap为什么是线程安全的?_concurrenthashmap为什么线程安全-CSDN博客
jdk 1.7 时:
分段锁(Segment locking):ConcurrentHashMap将内部的数据结构分成多个段(segment),每个段维护着一个独立的哈希表。在读操作时,并不需要获取整个哈希表的锁,而是只需要获取对应段的锁。这样可以降低并发度限制,允许多个线程同时读取不同的段,从而提高读操作的并发性能。
使用volatile和CAS(Compare and Swap)操作:ConcurrentHashMap中的关键字段使用volatile修饰,保证了可见性,对于读操作不需要加锁。对于写操作,使用CAS操作来保证线程安全,通过原子方式更新数据。这样可以避免了全局锁的开销,提高了写操作的并发性能。
内部数据结构:ConcurrentHashMap使用了一种特殊的哈希表结构,即数组+链表+红黑树的结合体。当链表长度超过阈值时,会将链表转换为红黑树,提高查找、插入和删除操作的效率。这种数据结构的设计使得在单个段上的操作更加高效。
不会进行全局锁定:与Hashtable等旧版哈希表不同,ConcurrentHashMap的读操作不需要获取锁,只有在写操作时才需要进行锁定。这样可以允许多个线程同时读取数据,提高并发性能。
通过上述机制的结合,ConcurrentHashMap实现了高效的线程安全。它在读多写少的场景下,能够提供较好的并发性能,同时保障了数据的一致性和线程安全性。
jdk 1.8
jdk1.8ConcurrentHashMap是数组+链表,或者数组+红黑树结构,并发控制使用Synchronized关键字和CAS操作。下面会从源码角度讲解jdk1.8 ConcurrentHashMap控制线程同步的原理
Volatile关键字的作用主要有如下两个:
线程的可见性:当一个线程修改一个共享变量时,另外一个线程能读到这个修改的值。
顺序一致性:禁止指令重排序。
volatile是如何保证可见性的呢? 在X86处理器下通过工具获取JIT编译器生成的汇编指令来查看对volatile进行写操作时,会多出lock addl。Lock前缀的指令在多核处理器下会引发两件事情:
将当前处理器缓存行的数据写回到系统内存。 这个写回内存的操作会使其他cpu里缓存了该内存地址的数据无效。 如果声明了volatile的变量进行写操作,JVM就会向处理器发送一条Lock前缀的指令,将这个变量所在缓存行的数据写回到系统内存。但是,就算写回到内存,如果其他处理器缓存的还是旧的,在执行操作就会有问题。所以,在多处理器下,为了保证各个处理器的缓存是一致的,就会实现缓存一致性协议,每个处理器通过嗅探在总线传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。 顺序一致性
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序。
当变量声明为volatile时,Java编译器在生成指令序列时,会插入内存屏障指令。通过内存屏障指令来禁止重排序。 JMM内存屏障插入策略如下: 在每个volatile写操作的前面插入一个StoreStore屏障,后面插入一个StoreLoad屏障。 在每个volatile读操作后面插入一个LoadLoad,LoadStore屏障。 单例模式就是一个volatile的一个例子
public class SafeDoubleCheckedLocking { private volatile static Instance instane; public static Instance getInstane(){ if(instane==null){ synchronized (SafeDoubleCheckedLocking.class){ if(instane==null){ instane=new Instance(); } } } return instane; } }
创建一个对象主要分为三个步骤:
分配对象内存空间。
初始化对象。
设置instance指向内存空间。
Volatile与Synchronized比较
Volatile是轻量级的synchronized,因为它不会引起上下文的切换和调度,所以Volatile性能更好。
Volatile只能修饰变量,synchronized可以修饰方法,静态方法,代码块。
Volatile对任意单个变量的读/写具有原子性,但是类似于i++这种复合操作不具有原子性。而锁的互斥执行的
特性可以确保对整个临界区代码执行具有原子性。
多线程访问volatile不会发生阻塞,而synchronized会发生阻塞。
volatile是变量在多线程之间的可见性,synchronize是多线程之间访问资源的同步性。
protected Object clone()--->创建并返回此对象的一个副本。
boolean equals(Object obj)--->指示某个其他对象是否与此对象“相等
protected void finalize()--->当垃圾回收器确定不存在对该对象的更多引用时,由对象的垃圾回收器调用此方法。
Class extendsObject> getClass()--->返回一个对象的运行时类。
int hashCode()--->返回该对象的哈希码值。
void notify()--->唤醒在此对象监视器上等待的单个线程。
void notifyAll()--->唤醒在此对象监视器上等待的所有线程。
String toString()--->返回该对象的字符串表示。
void wait()--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法。
void wait(long timeout)--->导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll()方法,或者超过指定的时间量。
void wait(long timeout, int nanos)--->导致当前的线程等待,直到其他线程调用此对象的 notify()
linkedList的底层是双向链表(jdk1.7之前是双向循环链表)链表实现,ArrayList的底层是Object的动态数组数组,且默认长度是10,每次扩容为原来的1.5倍。
ArrayList具有随机访问的特性,所以在查和改的操作上效率很高,但是对于增和删的操作,涉及对数组的动态扩容,效率较低;LinkedList对数据的操作则都是通过指针的移动来进行的。
HashMap的底层实现(jdk1.8之后),数组 + 链表 + 红黑树,HashTable的底层实现是数组 + 链表。
HashMap的key和value都可以是null,HashTable的key和value都不能是null
HashMap的初始链表长度是16,到数组的使用到达 0.75 时候,会进行扩容,长度扩展二倍,HashTable的初始长度是11,扩容的为原来容量的 2倍加一。
HashMap是线程不安全的,HashTable是线程安全的;如果想要保证线程安全,可以使用concurrentHashMap
继承Thread类创建线程
实现Runnable接口创建线程
使用Callable和Future创建线程 有返回值
使用线程池创建线程