引言
如何理解多线程呢,目前我们写的程序都单线程,单线程指的是整个程序只有一条唯一执行路径 所有代码被串联到这条路径中。多线程指的就是一个程序中,存在类似多条执行路径。
一.多线程基础
1.1 线程与进程
1.1.1 进程概念
进程是对应操作系统而言的,一个应用程序或者服务,操作系统主要的任务就是进程调度。目前的操作系统都支持多进程的任务调度。
1.1.2 线程概念
线程是进程的细分,一个进程可以包含多条线程,线程也叫轻量级进程。CPU调度的最小单位是线程
1.2 线程创建[高频面试]
java中,线程类Thread ,就是用于描述线程这类事物的,每个线程对象都是可运行的,所以Thread类中有一个run() 方法, 但是每个线程做的事情不一样,所以通常就要自定义类来继承Thread根据自己的要求重写run()。
1.2.1 继承Thread类
自动定义线程类
//线程类class WashFeet extends Thread{}
重写run()
//线程类class WashFeet extends Thread{//重写run , 这个线程要做的事情@Overridepublic void run() {while (true){System.out.println("洗脚........");}}}
创建并启动线程
public class HeavenandEarth {public static void main(String[] args) {//实例化一个洗脚线程对象WashFeet son1 = new WashFeet();//启动线程son1.start();}}
启动线程 必须使用 start() 不可直接调用run() , 区别在于 调用start() 是触发JVM调用run() 为当前对象对象分配自己的栈空间,形成一条线程。 直接调用run() 则不会,就跟平时一样相当于掉一个普通方法。
1.2.2 实现Runnable接口
Runnable 可以跑,可以运行的,描述的是这样一类事物,线程就是一种可以运行事物。Thread 类本身就是Runnable的实现类,它还定义了一些管理线程的方法。
使用Runnable方式创建线程,依然要依赖Thread。Runnable 仅仅表达的是线程要执行的任务。使用这种方式的思想,希望把线程对象 与线程要执行的任务分离开来。
创建Runnable实现类( Task任务 )
//线程任务class WashFeet implements Runnable{@Overridepublic void run() {while (true)System.out.println("洗脚.....");}}class WashHand implements Runnable{@Overridepublic void run() {while (true)System.out.println("洗手.....");}}
创建线程并启动
public class SkyAndEarth {public static void main(String[] args) {Thread son = new Thread( new WashFeet() );son.start();while (true){System.out.println("正在营业....");}}}
1.2.3 两种方式对比
方式一,线程对象和线程任务是耦合的,方式二,线程对象和线程任务是分离的
方法一,扩展性不好,类单继承的。 方式二,扩展性更好,接口是多实现。
方法一,调用线程方法直接 ,方式二,不可直接调用线程方法,需要先调用 Thread.currentThread(),这个方法的作用是拿到与当前线程任务绑定的线程对象。
1.3 线程常见方法
1.3.1 start()
1.3.2 ~~stop() ~~
1.3.3 Thread.sleep( long ms )
1.3.4 setName()/getName()
取名字/获得名字,如果没有设置名字,默认名字为Thread-(0-n)
1.3.5 Thread.yield()
线程礼让. 让出cpu执行时间片,自己进入就绪状态,再次等待调度。
1.3.6 join()
同步,加入线程线程中
1.3.7 setDeamon(boolean xx)/isDeamon()
设置守护线程/判断守护线程
1.3.8 setPriority(int xx)/getPriority()
设置/获取优先级 ,默认三个 MIN_PRIORITY = 1;NORM_PRIORITY = 5;MAX_PRIORITY = 10;
1.4 理解同步与异步
同步:一个线程全部做完以后,另一个线程才开始执行,也就是一个线程的开始总是接着另一个线程的结尾。
异步:一个线程不需要等待,另一个线程执行完毕后,才执行,从宏观上看线程是同时执行,但是微观上是一个线程执行一点点后,另外一个线程又执行一点点,这样交替执行**。
思考, 多线程好处是什么 ? 可以充分利用CPU,尤其是在处理阻塞问题的时候,使用多线程可以提高效率。
二.线程同步[高频面试]
2.1 为什么要线程同步
2.2线程同步方法
2.2.1 同步代码块
使用 synchronized( obj ){ 被同步代码 } 同步代码块方式,它原理当线程执行到有synchronized的代码块时,先尝试去获取obj 对象的锁(每个对象有且只有一把),如果取到了则进入代码块中执行,期间不被打扰(如果这时有其他线程准备进入代码块但是取不到锁),直到执行完毕后,自动归还锁。其他线程方可进入。
//同步案例public class TicketsDemo {public static void main(String[] args) {//模拟3个窗口Thread win1 = new Thread( new Window() );win1.setName("张阿姨");win1.start();Thread win2 = new Thread( new Window() );win2.setName("王阿姨");win2.start();Thread win3 = new Thread( new Window() );win3.setName("李阿姨");win3.start();}}//窗口线程class Window implements Runnable{//模拟100张票static int total = 100;@Overridepublic void run() {while (true){20个语句1000线程synchronized( "" ){ // 小括号()里需要一个对象,这个对对象必须满足,不同线程进入时是同一个对象。这样才有互斥性。if(total>0) {total--;//没时间了System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);}else{break;}}}}}
2.2.2 同步方法
和同步代码块原理基本一致也是使用synchronized 只是把同步范围扩大至整个方法,也就是锁的粒度更粗。
public class TicketsDemo {public static void main(String[] args) {Thread w1 = new Thread(new Window( ) );Thread w2 = new Thread(new Window( ) );Thread w3 = new Thread(new Window( ) );w1.setName("张");w2.setName("王");w3.setName("刘");w1.start();w2.start();w3.start();}}//窗口线程class Window implements Runnable{@Overridepublic void run() {while ( SoftWare.size()>0 ){//1000个SoftWare.sales();}}}//卖票软件class SoftWare{private static int total = 100 ;public static synchronized void sales(){30 句话if( total>0 ){total--;System.out.println( Thread.currentThread().getName() +"销售1张,还剩余"+total);}}public static int size(){return total;}}
2.2.3 同步不安全的集合
Collections 是集合的工具类,它提供了除了存取元素以外的其他功能,这里比如就有去把一个线程不安全的集合转换成线程安全的集合。
static ``<T> [List](../../java/util/List.html)<T> |
[**synchronizedList**](../../java/util/Collections.html#synchronizedList(java.util.List))([List](../../java/util/List.html)<T> list)返回指定列表支持的同步(线程安全的)列表。 |
|---|---|
static ``<K,V> [Map](../../java/util/Map.html)<K,V> |
[**synchronizedMap**](../../java/util/Collections.html#synchronizedMap(java.util.Map))([Map](../../java/util/Map.html)<K,V> m)返回由指定映射支持的同步(线程安全的)映射。 |
static ``<T> [Set](../../java/util/Set.html)<T> |
[**synchronizedSet**](../../java/util/Collections.html#synchronizedSet(java.util.Set))([Set](../../java/util/Set.html)<T> s)返回指定 set 支持的同步(线程安全的)set。 |
以 [**synchronizedList**](https://www.yuque.com/java/util/Collections.html#synchronizedList(java.util.List))([List](https://www.yuque.com/java/util/List.html)<T> list) 为例, 实现原理就是使用 同步代码块,这方法返回值实际上是 SynchronizedList 的实例,这个实例套娃使用了 出入参数不安全的集合 ,在不安全外围包裹了一个 同步代码块。
public E get(int index) {
synchronized (mutex) {return list.get(index);}
}
Vector Hashtable 它们安全是因为使用同步方法 而 Collections 工具类让集合安全使用的是 同步代码块。
2.2.4 死锁问题[高频面试]
多个个线程因为竞争资源而陷入相互等待的情况,无法恢复的场景。
产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程—资源的环形链。
package deadlock;/*** 死锁案例*/public class DeathLock {public static void main(String[] args) {Object o1 = new Object();Object o2 = new Object();Thread son1 = new Thread( new Foo(o1,o2) );son1.start();Thread son2 = new Thread( new Boo(o1,o2) );son2.start();}}class Foo implements Runnable {Object o1 = null;Object o2 =null;public Foo(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}@Overridepublic void run() {synchronized (o1){System.out.println( "Foo线程获得o1,想去锁o2");//可选 主要是为其他线程争取时间try {Thread.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}synchronized (o2){System.out.println("Foo线程获得o2");}System.out.println("Foo释放o2");}System.out.println("Foo 释放o1");}}class Boo implements Runnable{Object o1 = null;Object o2 =null;public Boo(Object o1, Object o2) {this.o1 = o1;this.o2 = o2;}@Overridepublic void run() {synchronized (o2){System.out.println( "Boo线程获得o2,想去锁o1");synchronized (o1){System.out.println("Boo线程获得o1");}System.out.println("Boo释放o1");}System.out.println("Boo 释放o2");}}
预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)
实际操作:按照统一顺序分配锁, 使用jdk.15 Lock的 tryLock( 5000 )
三.线程生命周期[高频面试]
四.线程通信
4.1 什么是线程通信
4.2 线程通信方法
wait():void , 它是Object的方法,不是线程提供的方法,让执行这个wait()调用的线程等待。 无限期等待,直到有被唤醒。
wait( long ms ) : 等待指定的毫米数,如果没有被唤醒,则自动唤醒。
wait( long ms, int naos) : 更精确的等待时间。
notify(): 唤醒等待在该对象上的线程(只会唤醒一个)。
notifyAll(): 唤醒全部等待在该对象上的全部线程。
这些方法必须使用在存在 synchronized 代码块内 或者 synchronized 方法中。
package waitandnofity;/*** 等待唤醒案例*/public class WaitAndNotifyCase {public static void main(String[] args) throws InterruptedException {Object obj = new Object();Thread son1 = new Thread( new Foo(obj) );son1.setName("张三");Thread son2 = new Thread( new Foo(obj) );son2.setName("李四");son1.start();son2.start();//让主线程等5s后去唤醒其他线程Thread.sleep(5000);synchronized (obj){//obj.notify();//唤醒一个obj.notifyAll();//唤醒全部}}}class Foo implements Runnable{Object obj = null;public Foo(Object obj) {this.obj = obj;}@Overridepublic void run() {synchronized (obj){System.out.println(Thread.currentThread().getName()+ "执行开始...");try {obj.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+ "执行完毕...");}}}
4.3 生产者消费者模型
package producerconsumer;/*** 生产者和消费者案例*/public class ProducerAndConsumer {public static void main(String[] args) {//容器Container container = new Container();Thread xfz1 = new Thread( new Consumer(container) );xfz1.setName("张三");xfz1.start();Thread xfz2 = new Thread( new Consumer(container) );xfz2.setName("李四");xfz2.start();Thread scz1 = new Thread( new Producer( container ) );scz1.setName("王师傅");scz1.start();Thread scz2 = new Thread( new Producer( container ) );scz2.setName("李师傅");scz2.start();}}//抽象出一个容器事物class Container{Object[] data = new Object[10]; //底层使用数组模拟栈int size; //元素计数器//存public synchronized void add( Object obj ){while( size==data.length ){ //存满了,不能存了,生产者线程,等待try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}data[size++] = obj;System.out.println(Thread.currentThread().getName() + "生产"+obj);//唤醒刚刚因为没有商品而等待的消费者线程this.notifyAll();}//取public synchronized Object get(){while (size==0){try {this.wait(); //商品不足 消费者线程等待。} catch (InterruptedException e) {e.printStackTrace();}}Object result = data[--size];System.out.println(Thread.currentThread().getName() +"消费"+result);//唤醒刚刚因为没有空间 而等待的生产者线程this.notifyAll();return result;}}//生产者class Producer implements Runnable{//共享容器Container container ;public Producer(Container container) {this.container = container;}@Overridepublic void run() {for ( int i =1;i<=20; i++ ){container.add( "汉堡"+i);}}}//消费者class Consumer implements Runnable{//共享容器Container container ;public Consumer(Container container) {this.container = container;}@Overridepublic void run() {for ( int i =1;i<=20; i++ ){container.get();}}}
五.线程池
5.1 什么是线程池
池是一种容器的概念,池化技术,都是容器技术。把多个线程对象存入一个容器中,因为线程也是宝贵的资源,频繁的创建销毁线程会存在内存开销。
5.2 如何创建线程池[面试题]
Executor : 线程池的顶级接口
ExecutorService: Executor 的子接口,扩展了一些管理线程任务的方法。
ScheduledExecutorService: ExecutorService 的子接口 提供了延时执行任务的方法。
Executors 线程池的工具类 ,可以创建出线程池实例
//1 单线程池
_ExecutorService es = Executors._newSingleThreadExecutor();
//2 固定线程池
_ExecutorService es2 = Executors._newFixedThreadPool(10);
//3 可变数量的线程池
_ExecutorService es3 = Executors._newCachedThreadPool();
//4 创建定时任务线程池
_ScheduledExecutorService se4 = Executors._newScheduledThreadPool(10);
5.3 提交任务到线程池
线程池创建以后,就可以接收任务了,在java中线程任务有两种,第一种就是Runnable 前面已经学习了。
5.3.1 Runnable 类型的任务
//关注运行没有结果的任务class WashFeet implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"洗脚...");try {Thread.sleep(3000);//模拟一个延时} catch (InterruptedException e) {e.printStackTrace();}}}
方法名为 run() 没有返回值 不可抛出异常
5.3.2 Callable 类型的任务
//关注结果有返回值的任务class NumberCount implements Callable<Integer> {@Overridepublic Integer call() throws Exception {int sum = 0;for(int i=0;i<100;i++){sum += i;}System.out.println(Thread.currentThread().getName() +"sum:"+sum);return sum;}}
特点: 方法名为 call() 有返回值 可抛出异常
5.3.3 Future 异步结果
Future 保存异步运算的结果,它提供了一个get()用于返回真正的异步计算结果,但是调用get()方法会造成调用线程阻塞,直到获得结果为止。
Future<Integer> result = es2.submit( new NumberCount() );Integer sum = result.get(); //阻塞调用语句的线程System.out.println(sum);
5.4 关闭线程池
shutdown() 方法,调用后线程池可拒绝接收新的任务,待全部现有任务执行完毕后关闭线程池。
5.5 统计多个任务运行的总时间
- 使用 shutdown()+isTerminated() 循环判断
- CountDownLatch 同步辅助工具类 , 构造一个任务总数,每个任务结束就减一,直到为0,把调用 await() 的线程唤醒。 ```java package case12;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;
public class TestCount {
public static void main(String[] args) throws InterruptedException {CountDownLatch cdl = new CountDownLatch(3);//实例化线程池ExecutorService es = Executors.newFixedThreadPool(3);//提交50个任务for ( int i=0 ; i< 50; i++ ){es.submit( new Foo( cdl ) );es.submit( new Boo( cdl ) );}//关闭es.shutdown();Foo.cdl.await(); //让执行这语句的线程等等,等计数器减为0,调用线程才会被唤醒。System.out.println("所以人都执行完毕了");}
}
class Foo implements Runnable{
//所以Foo实例共享。CountDownLatch cdl = null;Foo( CountDownLatch cdl ){this.cdl=cdl;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"Over");//线程执行结束 ,让计数器 -1cdl.countDown();}
} class Boo implements Runnable{
//所以Foo实例共享。//所以Foo实例共享。CountDownLatch cdl = null;Boo( CountDownLatch cdl ){this.cdl=cdl;}@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"Over");//线程执行结束 ,让计数器 -1cdl.countDown();}
}
<a name="dxwXu"></a>## 六.JUC锁[高频面试]<a name="cvoxg"></a>### 6.1 锁分类锁的作用就是保证数据一致性,但是往往就会牺牲效率,所以使用锁需要自己做好平衡和取舍。同等情况下,当然使用性能更好的锁,是最好的方案。<a name="IIDUI"></a>#### 6.1.1 悲观锁怀疑任何情况都会出现并发问题,所以在设计的时候 就默认锁定,jdk1.5前就是 synchronized ,早期说这个性能有问题(经过优化现在一般不去比较了)。在JDK1.5新的解决方法是提供了Lock接口和他的实现类。例如 ReentrantLock ReentrantReadWriteLock等。<a name="KBzeP"></a>#### 6.1.2 乐观锁乐观锁就是和悲观锁相反的思想,就是先不怀疑存在并发问题,如果确有存在再解决,通常需要为并发修改的数据设定一个版本号,修改前对一下先前获取的版本号,修改时在比较版本号如果一致才修改,不一致说明有版本变化不可修改**。CAS算法**就是一种乐观锁算法,它是硬件层面实现的,把多语句指令和为一个指令,确保了原子操作。<br />**Compare And Swap**( 比较 和 交换 ) V (内存值) E(期望值) N(新值) 当且仅当 V==E时 才将 V=N 。<a name="rQbnl"></a>#### 6.1.3 ReentrantLock**JDK1.5 引入全新的锁体系,JUC包大部分都是这个版本引入的。ReentrantLock是Lock接口的实现类,意为重入锁(所谓重入锁就是允许同一个线程多次对对象取锁),具有和**synchronized ,还提供了一些方法。功能更强大```javapackage lock.synchronize.block;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;//同步案例public class TicketsDemo {public static void main(String[] args) {//模拟3个窗口Thread win1 = new Thread( new Window() );win1.setName("张阿姨");win1.start();Thread win2 = new Thread( new Window() );win2.setName("王阿姨");win2.start();Thread win3 = new Thread( new Window() );win3.setName("李阿姨");win3.start();}}//窗口线程class Window implements Runnable{//声明锁static Lock lock = new ReentrantLock(true);//模拟100张票static int total = 100;@Overridepublic void run() {while (true){try{lock.lock(); //获得锁if(total>0) {total--;System.out.println(Thread.currentThread().getName() + "卖出1张,还剩" + total);}else{break;}}finally {lock.unlock(); //确保一定可以解锁}}}}
和对比 synchronized
- synchronized 关键字 ReentrantLock 类(jdk1.5新引入)
- 都是重入锁(ReentrantLock 功能强大 提供了更多的方法,以及实现公平锁策略)
- synchronized 上锁与解锁自动完成 , ReentrantLock 需要自己上锁和解锁。
6.1.3 ReadWriteLock
凡是用锁都会影响效率,如果把这种影响做到最小了呢? 读写锁就是一种经过细化考量的一种优化的锁。这种锁的特点是 读-读不阻塞线程, 读-写要阻塞线程,写-写要阻塞。一个把读写锁可以分离出 读锁和写锁。
package lock.readwriteLock;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/*** 读写锁案例*/public class TestReadWriteLockCase {public static void main(String[] args) {long begin = System.currentTimeMillis();//并发访问源Foo obj = new Foo();//线程池ExecutorService executorService = Executors.newFixedThreadPool(20);//提交任务 18 次读for(int i= 0; i<18; i++){executorService.submit(new Runnable() {@Overridepublic void run() {obj.getAge();}});}//提交2次写任务for(int i=0;i<2;i++){executorService.submit(new Runnable() {@Overridepublic void run() {obj.setAge( 100 );}});}//关闭executorService.shutdown();while ( !executorService.isTerminated() ){}long end = System.currentTimeMillis();//计算时间System.out.println( (end-begin)/1000 );}}class Foo {private Integer age ;private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();public Integer getAge() {try {readWriteLock.readLock().lock();Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}finally {readWriteLock.readLock().unlock();}return age;}public void setAge(Integer age) {try {readWriteLock.writeLock().lock();Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}finally {readWriteLock.writeLock().unlock();}this.age = age;}}
6.2 Condition 通信
在jdk1.5以后,如果不使用 synchronized 同步方法, 那么线程通信可以使用Condition 实现,它的核心做法是,
- 获得一个 阻塞条件对象 Conditon cd = lock.newCondition();
- 阻塞方法 cd.awiat() 作用同 wait()
唤醒方法 cd.signal() / cd.signalAll() 作用同 notify() 和 notifyAll()
```java package lock.producerconsumer; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; /**生产者和消费者案例 */ public class ProducerAndConsumer { public static void main(String[] args) { //容器 Container container = new Container(); Thread xfz1 = new Thread( new Consumer(container) ); xfz1.setName(“张三”); xfz1.start();
Thread scz1 = new Thread( new Producer( container ) ); scz1.setName(“王师傅”); scz1.start(); } }
//抽象出一个容器事物 class Container{ private Object[] data = new Object[10]; //底层使用数组模拟栈 private int size; //元素计数器 private ReentrantLock lock = new ReentrantLock(); //从锁上获取阻塞条件 private Condition cd = lock.newCondition(); //存 public void add( Object obj ){
try{lock.lock();while( size==data.length ){ //存满了,不能存了,生产者线程,等待try {cd.await();} catch (InterruptedException e) {e.printStackTrace();}}data[size++] = obj;System.out.println(Thread.currentThread().getName() + "生产"+obj);//唤醒刚刚因为没有商品而等待的消费者线程//TODOcd.signalAll();}finally {lock.unlock();}}//取public Object get(){try{lock.lock();while (size==0){try {cd.await(); ; //商品不足 消费者线程等待。} catch (InterruptedException e) {e.printStackTrace();}}Object result = data[--size];System.out.println(Thread.currentThread().getName() +"消费"+result);//唤醒刚刚因为没有空间 而等待的生产者线程cd.signalAll();return result;}finally {lock.unlock();}}
} //生产者 class Producer implements Runnable{ //共享容器 Container container ;
public Producer(Container container) {this.container = container;}@Overridepublic void run() {for ( int i =1;i<=20; i++ ){container.add( "汉堡"+i);}}
} //消费者 class Consumer implements Runnable{ //共享容器 Container container ; public Consumer(Container container) { this.container = container; } @Override public void run() { for ( int i =1;i<=20; i++ ){ container.get(); } } } ```
七.并发集合 (JUC 集合)

CopyOnWriteArrayList : 底层使用 ReentrantLock实现锁,并且修改数据,拷贝一个新的数字操作。
CopyOnWriteArraySet : 底层套娃使用 CopyOnWriteArrayList ,添加前通过addIfAbsent(e) 判断元素是否已经存在,如果存在着不添加。
ConcurrentHashMap : 底层使用(1.8早期分段锁 后期使用CAS 无锁算法)
ConcurrentLinkedQueue : 并发安全的 队列,链表实现
ArrayBlockingQueue : 阻塞队列,基于数组实现 ,也是有有界队列
LinkedBlockingQueue: 阻塞队列,基于链表实现, 是无界队列
补充:
volatile 【面试题】
关键字 用于修饰变量 , 它作用是,可以将线程工作内存中的数据,立即同步到主存,以保证其他线程池立即可见。简单的说就是解决内存可见性问题。但是不能保证原子性问题。
JAVA (JMM)java内存模型:
JUC 中除有线程池 锁 同步工具, 还提供了一大堆 原子操作类。
AtomicInger AtomicLong……. 类它们的底层使用cas算法保证线程安全问题。
附图
JVM内存模型:

