一、java基础
1、面向对象易维护、复用、扩展,但性能比面向对象低。
Java性能低的主要原因不是因为面向对象,而是因为它是半编译语言,不是被CPU直接执行的二进制码
2、Java特点
面向对象(封装、继承、多态)、平台无关(虚拟机)、安全可靠、多线程、编译与解释并存、支持网络编程
3、JVM JDK JRE
JVM 有针对不同系统的特定实现(Windows,Linux, macOS),⽬的是使⽤相同的字节码,它们都会给出相同的结果。
JDK 是 Java Development Kit,它是功能⻬全的 Java SDK。它能够创建和编译程序。(编程+运行)
JRE 是 Java 运⾏时环境。它是运⾏已编译 Java 程序所需的所有内容的集合,包括 Java 虚拟机(运行)
4、Java和c++
①都是⾯向对象的语⾔,都⽀持封装、继承和多态
②Java 不提供指针来直接访问内存,程序内存更加安全
③Java 的类是单继承的, C++ ⽀持多重继承;虽然 Java 的类不可以多继承,但是接⼝可以多继承。
④Java 有⾃动内存管理机制,不需要程序员⼿动释放⽆⽤内存
5、重载与重写
重载就是同样的⼀个⽅法能够根据输⼊数据的不同,做出不同的处理,⽅法名必须相同,别的不同,发生在编译期;
重写就是当⼦类继承⾃⽗类的相同⽅法,输⼊数据⼀样,但要做出有别于⽗类的响应时,就要覆盖⽗类⽅法,返回值类型、⽅法名、参数列表必须相同,如果⽗类⽅法访问修饰符为 private/final/static 则⼦类就不能重写该⽅法,发生在运行期。
构造器Constructor 不能被 override(重写) ,但是可以 overload(重载)
6、
7、 StringBuilder 与 StringBuffer
都继承⾃ AbstractStringBuilder 类,没有⽤ final 关键字修饰,所以这两种对象都是可变的。
StringBuffer 是线程安全的(加了同步锁)。 StringBuilder 是⾮线程安全的。
每次对 String 类型进⾏改变的时候,都会⽣成⼀个新的 String 对象,然后将指针指向新的 String对象。 StringBuffer 每次都会对 StringBuffer 对象本身进⾏操作,⽽不是⽣成新的对象并改变对象引⽤。相同情况下使⽤ StringBuilder 相⽐使⽤ StringBuffer获得 10%~15% 左右的性能提升
- 操作少量的数据: 适⽤ String
- 单线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuilder
- 多线程操作字符串缓冲区下操作⼤量数据: 适⽤ StringBuffer
8、接口与抽象类
①抽象类要被子类继承, 接口要被类实现
②接⼝⽅法默认修饰符是 public,只能有static、 final 变量;抽象⽅法可以有 public、 protected 和 default 这些修饰符(为了被重写所以不能使⽤ private )
③ ⼀个类可以实现多个接⼝,但只能实现⼀个抽象类。
接⼝⾃⼰本身可以通过 extends 关键字扩展多个接⼝。
接口可多继承接口, 但类只能单根继承。
④抽象类里可以没有抽象方法;如果一个类里有抽象方法, 那么这个类只能是抽象类
⑤抽象方法只能申明,不能实现。abstract void abc(),不能写成abstract void abc(){}。
⑥从设计层⾯来说,抽象是对类的抽象,是⼀种模板设计,⽽接⼝是对⾏为的抽象,是⼀种⾏为的规范。
9、==与equals
对象相等:⽐的是内存中存放的内容是否相等。
引⽤相等:⽐较的是他们指向的内存地址是否相等。
==: 它的作⽤是判断两个对象的地址是不是相等。即,判断两个对象是不是同⼀个对象。基本数据类型(八种基本数据类型)⽐较的是值,引⽤数据类型(数组、类、接口)⽐较的是内存地址。
equals() : 若没有覆盖equals,则等价于 = =;若覆盖了,大多数情况比如string,integer,则是比较内容
String x = "string";
String y = "string";
String z = new String("string");
System.out.println(x==y); // true,引用相同
System.out.println(x==z); // false,==:string比较引用,开辟了新的堆内存空间,所以false
System.out.println(x.equals(y)); // true,equals:string:比较值,相同
System.out.println(x.equals(z)); // true,equals:string比较值,相同
10、hashcode与equals
获取哈希码(散列码),返回int,确定对象在哈希表中的索引位置,任何类都有hashcode()函数。
如果hashcode相同,再调用equals()检查两个对象是否真的相同,否则hashset不会让其加入成功,这样减少了equals使用次数,提高就执行速度。
两个对象相同,则有相同的hashcode;反之hashcode相同,对象不一定相等。所以要重写equals时必须重写hashcode()函数,如果没有重写 hashCode(),则该 class的两个对象⽆论如何都不会相等。
11、线程
多线程问题:内存泄漏、上下文切换、死锁
12、异常处理
error程序无法处理的错误:Java 虚拟机运⾏错误(Virtual MachineError)、类定义错误(NoClassDefFoundError)等。
exception程序可以处理的异常:NullPointerException(要访问的
变量没有引⽤任何对象)、 ArithmeticException(算术运算异常,⼀个整数除以 0时,抛出该异常)和 ArrayIndexOutOfBoundsException (下标越界异常)
try-catch方法:
try 块: ⽤于捕获异常。其后可接零个或多个 catch 块,如果没有 catch 块, 则必须跟⼀个finally 块。
catch 块: ⽤于处理 try 捕获到的异常。
finally 块: ⽆论是否捕获或处理异常, finally 块⾥的语句都会被执⾏,并覆盖其他return值。除非:
①在 finally 语句块第⼀⾏发⽣了异常。 因为在其他⾏, finally 块还 是会得到执⾏
②在前⾯的代码中⽤了 System.exit(int)已退出程序。 exit 是带参函数 ;若该语句在异常语句之后, finally 会执⾏
③程序所在的线程死亡。
④关闭 CPU。
13、获取键盘输入
法一、BufferedReader
BufferedReader input = new BufferedReader(new InputStreamReader(System.in));
String s = input.readLine();
法二、Scanner
Scanner input = new Scanner(System.in);
String s = input.nextLine();//nextLine()可以接收空格或者tab键,其输入应该以enter键结束,注意bug
int age = input.nextInt();
float salary = input.nextFloat();
input.close();
14、IO流
输入流:InputStream(字节)、Reader(字符)
输出流:OutputStream(字节)、Writer(字符)
字符流是由 Java 虚拟机将字节转换得到的,这个过程还算是⾮常耗时,并且,如果我们不知道编码类型,就很容易出现乱码问题。所以, I/O 流就⼲脆提供了⼀个直接操作字符的接⼝,⽅便我们平时对字符进⾏流操作。
15、深拷⻉ vs 浅拷⻉
- 浅拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型进⾏引⽤传递般的拷⻉,此为浅拷⻉。
- 深拷⻉:对基本数据类型进⾏值传递,对引⽤数据类型,创建⼀个新的对象,并复制其内容,此为深拷⻉。
二、java集合
1、List,Set,Map
- List(对付顺序的好帮⼿): List接⼝存储⼀组不唯⼀(可以有多个元素引⽤相同的对象),有序的对象,vector线程安全
- Set(注重独⼀⽆⼆的性质): 不允许重复的集合。不会有多个元素引⽤相同的对象。无序,hashcode决定
- Map(⽤Key来搜索的专家): 使⽤键值对存储。
Map会维护与Key有关联的值。两个Key可以引⽤相同的对象,但Key不能重复,典型的Key是String类型,但也可以是任何对象,hashtable线程安全
2、 Arraylist 与 LinkedList、vector
①都不同步,不保证线程安全
② Arraylist 底层是object数组,查找快get(int index)
③LinkedList底层是双向链表,插入、添加、删除快,更占内存
④vector是同步的,线程安全,但效率太低,尽量用 Arraylist
3、HashMap 和 Hashtable 、HashSet
- HashMap 是⾮线程安全的, HashTable 是线程安全的;HashMap 要⽐ HashTable 效率⾼⼀点。另外, HashTable 基本被淘汰
- HashMap 中, null 可以作为键,这样的键只有⼀个,可以有⼀个或多个键所对应的值为 null。但是在 HashTable 中 会异常
- ①创建时如果不指定容量初始值, Hashtable 默认的初始⼤⼩为11,之后每次扩充,容量变为原来的2n+1。 HashMap 默认的初始化⼤⼩为16。之后每次扩充,容量变为原来的2倍。
②创建时如果给定了容量初始值,那么 Hashtable 会直接使⽤你给定的⼤⼩,⽽ HashMap 会将其扩充为2的幂次⽅⼤⼩ - HashMap 在解决哈希冲突时,当链表⻓度⼤于阈值(默认为8)时,将链表转化为红⿊树,以减少搜索时间。 Hashtable 没有这样的机制。
- HashSet 底层就是基于 HashMap 实现的,仅存储对象,使用成员对象计算hashcode,(hashmap存储键值对,使用key计算hashcode)
4、ConcurrentHashMap
解决多线程并发环境下 HashMap死循环问题,线程安全的,比Hashtable效率高
5、数据结构总结
Collection
- List
Arraylist: Object数组
Vector: Object数组
LinkedList: 双向链表(JDK1.6之前为循环链表, JDK1.7取消了循环) - Set
HashSet(⽆序,唯⼀) : 基于 HashMap 实现的,底层采⽤ HashMap 来保存元素
LinkedHashSet: LinkedHashSet 继承于 HashSet,并且其内部是通过 LinkedHashMap 来实现
的。有点类似于我们之前说的LinkedHashMap 其内部是基于 HashMap 实现⼀样,不过还是有⼀
点点区别的
TreeSet(有序,唯⼀): 红⿊树(⾃平衡的排序⼆叉树)
Map
-
HashMap:基于拉链式散列结构即由数组和链表或红⿊树组成。
-
LinkedHashMap: LinkedHashMap 继承⾃HashMap,所以它的底层仍然是基于拉链式散列结构即由数组和链表或红⿊树组成。另外增加了⼀条双向链表,使得上⾯的结构可以保持键值对的插⼊顺序。同时通过对链表进⾏相应的操作,实现了访问顺序相关逻辑。
-
Hashtable: 数组+链表组成的,数组是 HashMap 的主体,链表则是主要为了解决哈希冲突⽽存在的
-
TreeMap: 红⿊树(⾃平衡的排序⼆叉树)
三、多线程
1、多线程
⼀个进程中可以有多个线程,多个线程共享进程的堆和⽅法区 (元空间)资源,但是每个线程有⾃⼰的程序计数器、虚拟机栈和本地⽅法栈。
- 堆和⽅法区是所有线程共享的资源,其中堆是进程中最⼤的⼀块内存,主要⽤于存放新创建的对象
(所有对象都在这⾥分配内存),⽅法区主要⽤于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据 - 程序计数器私有主要是为了线程切换后能恢复到正确的执⾏位置。
- 为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地⽅法栈是线程私有的
2、并行与并发
- 并发:当有多个线程在操作时,如果只有一个CPU,把CPU运行时间划分成若干个时隙,每个时间段执行不同的线程;
- 并⾏: 有多个CPU时,可以一个CPU执行一个线程,互不抢占资源
3、线程死锁
- 互斥条件:该资源任意⼀个时刻只由⼀个线程占⽤。
- 请求与保持条件:⼀个进程因请求资源⽽阻塞时,对已获得的资源保持不放。
- 不剥夺条件:线程已获得的资源在末使⽤完之前不能被其他线程强⾏剥夺,只有⾃⼰使⽤完毕后
才释放资源。 - 循环等待条件:若⼲进程之间形成⼀种头尾相接的循环等待资源关系。
4、sleep()与wait()
- 两者都可以暂停线程的执⾏。
- sleep ⽅法没有释放锁,⽽ wait ⽅法释放了锁 。
- wait() ⽅法被调⽤后,线程不会⾃动苏醒,需要别的线程调⽤同⼀个对象上的 notify() 或者 notifyAll() ⽅法。sleep() ⽅法执⾏完成后,线程会⾃动苏醒。或者可以使⽤ wait(long timeout)超时后线程会⾃动苏醒。
5、start()与run()
- new ⼀个 Thread,线程进⼊了新建状态,调⽤ start()⽅法,会启动⼀个线程并使线程进⼊了就绪状态,当分配到时间⽚后就可以开始运⾏了。 start()会执⾏线程的相应准备⼯作,然后⾃动执⾏run() ⽅法的内容,这是真正的多线程⼯作。
- 直接执⾏ run() ⽅法,会把 run ⽅法当成⼀个 main线程下的普通⽅法去执⾏,而不是另起一个线程,所以这并不是多线程⼯作。
6、synchronized
synchronized关键字解决的是多个线程之间访问资源的同步性,synchronized关键字可以保证被它修饰的⽅法或者代码块在任意时刻只能有⼀个线程执⾏。
三种使用方法:
- 修饰实例⽅法: 作⽤于对象实例
- 修饰静态⽅法: 给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员
- 修饰代码块: 对给定对象加锁,给当前类加锁
7、双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public synchronized static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (uniqueInstance==null){
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance == null){
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
8、 并发编程的三个重要特性
- 原⼦性 : ⼀个的操作或者多次操作,要么所有的操作全部都得到执⾏并且不会收到任何因素的⼲扰⽽中断,要么所有的操作都执⾏,要么都不执⾏。 synchronized 可以保证代码⽚段的原⼦性。
- 可⻅性 :当⼀个变量对共享变量进⾏了修改,那么另外的线程都是⽴即可以看到修改后的最新值。 volatile 关键字可以保证共享变量的可⻅性。
- 有序性 :代码在执⾏的过程中的先后顺序,Java 在编译器以及运⾏期间的优化,代码的执⾏顺序未必就是编写代码时候的顺序。 volatile 关键字可以禁⽌指令进⾏重排序优化。
9、synchronized 关键字和 volatile 关键字的区别
- volatile只能⽤于变量,⽽synchronized可以修饰⽅法以及代码块。
- volatile关键字是线程同步的轻量级实现,所以volatile性能⽐synchronized要好。但synchronized引⼊的偏向锁和轻量级锁优化之后,执⾏效率有了显著提升, 实际开发中使⽤synchronized的场景还是更多⼀些。
- 多线程访问volatile关键字不会发⽣阻塞,⽽synchronized关键字可能会发⽣阻塞
- volatile能保证数据的可⻅性,但不能保证数据的原⼦性。synchronized两者都能保证。
- volatile主要⽤于解决变量在多个线程之间的可⻅性、有序性,⽽synchronized解决的是多个线程之间访问资源的同步性。
10、线程池
线程池提供了⼀种限制和管理资源。每个线程池还维护⼀些基本统计信息,例如已完成任务的数量。
使⽤线程池的好处:
-
降低资源消耗。通过重复利⽤已创建的线程降低线程创建和销毁造成的消耗。
-
提⾼响应速度。当任务到达时,任务可以不需要的等到线程创建就能⽴即执⾏。
-
提⾼线程的可管理性。线程是稀缺资源,如果⽆限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使⽤线程池可以进⾏统⼀的分配,调优和监控。
Runnable接⼝和Callable接⼝的区别:
- Runnable 接⼝不会返回结果或抛出检查异常,但是 Callable 接⼝可 以。所以,如果任务不需要返回结果或抛出异常推荐使⽤Runnable 接⼝,这样代码更加简洁。⼯具类 Executors 可以实现 Runnable 对象和 Callable 对象之间的相互转换
execute()⽅法和submit()⽅法的区别:
- execute() ⽅法⽤于提交不需要返回值的任务,所以⽆法判断任务是否被线程池执⾏成功与否;
- submit() ⽅法⽤于提交需要返回值的任务。线程池会返回⼀个 Future 类型的对象判断任务是否执⾏成功
11、创建线程池
《阿⾥巴巴Java开发⼿册》中不允许使⽤ Executors 去创建,⽽是通过ThreadPoolExecutor 的⽅式。
⽅式⼀:通过构造⽅法实现ThreadPoolExecutor
方式二:通过Executor 框架的⼯具类Executors来实现 我们可以创建三种类型的ThreadPoolExecutor:
- FixedThreadPool : 该⽅法返回⼀个固定线程数量的线程池。该线程池中的线程数量始终不变。当有⼀个新的任务提交时,线程池中若有空闲线程,则⽴即执⾏。若没有,则新的任务会被暂存在⼀个任务队列中,待有线程空闲时,便处理在任务队列中的任务。
- SingleThreadExecutor: ⽅法返回⼀个只有⼀个线程的线程池。若多余⼀个任务被提交到该线程池,任务会被保存在⼀个任务队列中,待线程空闲,按先⼊先出的顺序执⾏队列中的任务。
- CachedThreadPool: 该⽅法返回⼀个可根据实际情况调整线程数量的线程池。线程池的线程数量不确定,但若有空闲线程可以复⽤,则会优先使⽤可复⽤的线程。若所有线程均在⼯作,⼜有新的任务提交,则会创建新的线程处理任务。所有线程在当前任务执⾏完毕后,将返回线程池进 ⾏复⽤。
12、ThreadPoolExecutor
/**
* ⽤给定的初始参数创建⼀个新的ThreadPoolExecutor。
*/
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
ThreadPoolExecutor 3 个最重要的参数:
- corePoolSize : 核⼼线程数线程数定义了最⼩可以同时运⾏的线程数量。
- maximumPoolSize : 当队列中存放的任务达到队列容量的时候,当前可以同时运⾏的线程数 量变为最⼤线程数。
- workQueue : 当新任务来的时候会先判断当前运⾏的线程数量是否达到核⼼线程数,如果达到 的话,新任务就会被存放在队列中。
ThreadPoolExecutor 其他常⻅参数:
- keepAliveTime :当线程池中的线程数量⼤于 corePoolSize 的时候,如果这时没有新的任务提交,核⼼线程外的线程不会⽴即销毁,⽽是会等待,直到等待的时间超过了keepAliveTime 才会被回收销毁;
- unit : keepAliveTime 参数的时间单位。
- threadFactory :executor 创建新线程的时候会⽤到。
- handler :饱和策略。如果当前同时运⾏的线程数量达到最⼤线程数量并且队列也已经被放满了任时, ThreadPoolTaskExecutor 定义⼀些策略
线程池demo:Runnable
+ThreadPoolExecutor
MyRunnable.java
import java.util.Date;
/**
* 这是⼀个简单的Runnable类,需要⼤约5秒钟来执⾏其任务。
*/
public class MyRunnable implements Runnable {
private String command;
public MyRunnable(String s) {
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
processCommand();
System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
ThreadPoolExecutorDemo.java
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ThreadPoolExecutorDemo {
private static final int CORE_POOL_SIZE = 5;
private static final int MAX_POOL_SIZE = 10;
private static final int QUEUE_CAPACITY = 100;
private static final Long KEEP_ALIVE_TIME = 1L;
public static void main(String[] args) {
//使⽤阿⾥巴巴推荐的创建线程池的⽅式
//通过ThreadPoolExecutor构造函数⾃定义参数创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 0; i < 10; i++) {
//创建WorkerThread对象(WorkerThread类实现了Runnable 接⼝)
Runnable worker = new MyRunnable("" + i);
//执⾏Runnable
executor.execute(worker);
}
//终⽌线程池
executor.shutdown();
while (!executor.isTerminated()) {
}
System.out.println("Finished all threads");
}
}
输出:(线程池每次会同时执⾏ 5 个任务,这 5 个任务执⾏完之后,剩余的 5 个任务才会被执⾏。)
pool-1-thread-2 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool-1-thread-5 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool-1-thread-4 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool-1-thread-1 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool-1-thread-3 Start. Time = Tue Nov 12 20:59:44 CST 2019
pool-1-thread-5 End. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-3 End. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-2 End. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-4 End. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-1 End. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-2 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-1 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-4 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-3 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-5 Start. Time = Tue Nov 12 20:59:49 CST 2019
pool-1-thread-2 End. Time = Tue Nov 12 20:59:54 CST 2019
pool-1-thread-3 End. Time = Tue Nov 12 20:59:54 CST 2019
pool-1-thread-4 End. Time = Tue Nov 12 20:59:54 CST 2019
pool-1-thread-5 End. Time = Tue Nov 12 20:59:54 CST 2019
pool-1-thread-1 End. Time = Tue Nov 12 20:59:54 CST 2019
execute方法:
13、Atomic原子类
Atomic 是指⼀个操作是不可中断的。即使是在多个线程⼀起执⾏的时候,⼀个操作⼀旦开始,就不会被其他线程⼲扰。
并发包 java.util.concurrent
的原⼦类都存放在 java.util.concurrent.atomic
下,共四类:
基本类型:(使⽤原⼦的⽅式更新基本类型)
- AtomicInteger:整形原⼦类
- AtomicLong:⻓整型原⼦类
- AtomicBoolean:布尔型原⼦类
数组类型:(使⽤原⼦的⽅式更新数组⾥的某个元素)
- AtomicIntegerArray:整形数组原⼦类
- AtomicLongArray:⻓整形数组原⼦类
- AtomicReferenceArray:引⽤类型数组原⼦类
引⽤类型
- AtomicReference:引⽤类型原⼦类
- AtomicStampedReference:原⼦更新带有版本号的引⽤类型。该类将整数值与引⽤关联起来,可⽤于解决原⼦的更新数据和数据的版本号,可以解决使⽤ CAS 进⾏原⼦更新时可能出现的 ABA 问题。
- AtomicMarkableReference :原⼦更新带有标记位的引⽤类型
对象的属性修改类型
- AtomicIntegerFieldUpdater:原⼦更新整形字段的更新器
- AtomicLongFieldUpdater:原⼦更新⻓整形字段的更新器
AtomicInteger 类主要利⽤ CAS (compare and swap) + volatile 和 native ⽅法来保证原⼦操作,从⽽避免 synchronized 的⾼开销,执⾏效率⼤为提升。