JUC并发
什么是JUC
juc就是java.util工具包下面的以上几种的统称。
Runnable 没有返回值,效率相比于Callable较低。
进程和线程
- 进程:运行的程序集合,是进行资源分配的基本单位
- 线程:cpu调度和执行的基本单位
- Java默认有几个线程?2个。main和gc
- 对于Java:Thread、Runnable、Callable
- Java真的可以开启线程吗? 不能
说明如下:
点入start方法声明处
点入start0方法声明处
可以发现这是一个本地方法,通过**底层C++**实现开启线程,
原因:Java无法直接操作硬件
- 并发:多线程操作同一个资源,交替执行。例如单核多条线程,快速交替
- 并行:多个操作同时处理执行。例如多核多线程同时执行。线程池。
- 并发编程的本质:为了充分利用CPU的资源
线程的状态(6种)
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,//创建
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,//运行
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,//阻塞
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called {@code Object.wait()}
* on an object is waiting for another thread to call
* {@code Object.notify()} or {@code Object.notifyAll()} on
* that object. A thread that has called {@code Thread.join()}
* is waiting for a specified thread to terminate.
*/
WAITING,//等待(死等)
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,//超时等待
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;//终止
}
- wait/sleep的区别
企业中不用sleep,而是用juc下的TimeUnit类下的方法。
wait是Object类的方法,sleep是Thread类的方法。
wait会释放锁,sleep不会释放锁
使用范围不同:wait必须在同步代码块中使用,sleep可以在任何地方使用
是否需要捕获异常:wait不需要捕获异常,sleep必须捕获异常
Lock锁(重点)
传统synchronized
真正的多线程开发:
线程就是一个单独的资源类,没有任何附属的操作
1.属性 & 方法
并发:多线程操作同一个资源类,把资源类丢入线程
package com.jerry.demo01;/*
* @author jerry404lee
* @date 2021/7/7 21:50
*/
//基本的卖票例子
public class Test1 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口, 用lambda表达式 (参数)->{代码}
new Thread(
()->{
for (int i = 1; i < 60 ; i++) {
ticket.sale();
}
}, "A"
).start();
new Thread(
()->{for (int i = 1; i < 60 ; i++) {
ticket.sale();
}}, "B"
).start();
new Thread(
()->{for (int i = 1; i < 60 ; i++) {
ticket.sale();
}}, "C"
).start();
}
}
//资源类
class Ticket{
//属性 方法
private int number = 50;
// 卖票的方式
//synchronized本质:队列&锁
public synchronized void sale(){
if(number > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
}
}
Lock
- 公平锁:先来后到
- (默认) 非公平锁:可以插队
Lock三步:
- new ReentrantLock();
- lock.lock(); //加锁
- 业务代码写在try块中
- finally块中执行 lock.unlock(); //解锁
package com.jerry.demo01;/*
* @author jerry404lee
* @date 2021/7/7 21:50
*/
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
//基本的卖票例子
public class Test1 {
public static void main(String[] args) {
//并发:多线程操作同一个资源类,把资源类丢入线程
Ticket ticket = new Ticket();
//@FunctionalInterface 函数式接口, 用lambda表达式 (参数)->{代码}
new Thread(
()->{
for (int i = 1; i < 60 ; i++) {
ticket.sale();
}
}, "A"
).start();
new Thread(
()->{for (int i = 1; i < 60 ; i++) {
ticket.sale();
}}, "B"
).start();
new Thread(
()->{for (int i = 1; i < 60 ; i++) {
ticket.sale();
}}, "C"
).start();
}
}
//资源类
class Ticket{
//属性 方法
private int number = 50;
Lock lock = new ReentrantLock();
// 卖票的方式
//synchronized本质:队列
public void sale(){
lock.lock();//加锁
try {
if(number > 0){
System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Synchronized和Lock的区别
- Synchronized 是内置的Java关键字,Lock 是一个Java类;
- Synchronized 无法判断获取锁的状态, Lock 可以判断是否获取了锁;
- Synchronized 会自动释放锁, Lock 必须手动释放锁,如果不释放锁,会造成死锁问题;
- Synchronized 线程1获得锁并阻塞,线程2会死等。Lock 锁不一定,因为有tryLock方法;
- Synchronized 是可重入锁(某个线程已经获得了某个锁,可以再次获取锁而不会出现死锁),不可以中断,非公平。 Lock 是可重入锁,可以判断锁,非公平(可以自己设置);
- Synchronized 适合锁少量的代码同步问题。Lock 适合锁大量的代码。
生产者和消费者问题
package pc;/*
* @author jerry404lee
* @date 2021/7/8 0:28
* 线程之间的通信问题 等待唤醒&通知唤醒
* 线程交替执行
*/
public class A {
public static void main(String[] args) {
Data data = new Data();
new Thread(
()->{
for (int i = 0; i < 10; i++) {
try {
data.inc();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "A"
).start();
new Thread(
()->{
for (int i = 0; i < 10 ; i++) {
try {
data.dec();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "B"
).start();
}
}
class Data{//数字 资源类
private int number = 0;
public synchronized void inc() throws InterruptedException {
if(number!=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
public synchronized void dec() throws InterruptedException {
if(number == 0){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
this.notifyAll();
}
}
这里判断语句用到if,可能出现虚假唤醒问题(上述代码如果加入类似的更多线程比如C+1、D-1,执行结果就会出现不正确的同步结果)
上述代码中实现“生产者-消费者问题”用的是“Synchronized+wait+notifyAll”。那么与Synchronized对应的Lock类如何解决相同问题呢?用Condition。
例子:
使用Condition的优势
- Condition 可以实现精准的通知和唤醒线程
package pc;/*
* @author jerry404lee
* @date 2021/7/8 13:10
*A执行完调用B,B执行完调用C,C执行完调用A
*/
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class B {
public static void main(String[] args) {
DataB data = new DataB();
new Thread(
()->{
for (int i = 0; i < 10; i++) {
data.printA();
}
},"A"
).start();
new Thread(
()->{
for (int i = 0; i < 10; i++) {
data.printB();
}
},"B"
).start();
new Thread(
()->{
for (int i = 0; i < 10; i++) {
data.printC();
}
},"C"
).start();
}
}
class DataB{//资源类 Lock
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
private int number = 1;
//标记位 1A 2B 3C
public void printA(){
lock.lock();
try {
while (number !=1){
conditionA.await();//根据标记位,等待
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAAAAAA");
number = 2; //更改标记位
conditionB.signal();//唤醒指定的B线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (number !=2){
conditionB.await();//根据标记位,等待
}
System.out.println(Thread.currentThread().getName()+"=>BBBBBBBB");
number = 3; //更改标记位
conditionC.signal();//唤醒指定的C线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (number !=3){
conditionC.await();//根据标记位,等待
}
System.out.println(Thread.currentThread().getName()+"=>CCCCCCCCCCC");
number = 1; // 更改标记位
conditionA.signal();//唤醒指定的A线程
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
运行结果
应用场景:生产线:下单-》支付-》交易-》物流
重点问题:什么是锁?如何判断锁的是谁?
8 锁现象
synchronized 锁的对象是方法的调用者,谁先拿到谁先执行
并发下不安全的ArrayList(集合类)
解决方案:
其中,CopyOnWrite:写入时复制,是计算机程序设计中的一种优化策略:多个线程调用的时候,list读取的时候是固定的,写入的时候,避免覆盖造成数据问题;读写分离。(之前用的是lock锁,现在可能是因为synchronized被优化,所以用synchronized)
看HashSet的源码,可以发现
事实上是用一个HashMap来实现的。
而在其他比如add操作上,实际上也是进行map操作,这里是利用了map中键值唯一性,正好实现set中无重复元素的特性。
Callable
着重比较与Runnable的区别:
- 可以有返回值
- 可以跑出异常
- 方法不同,run()/call()
package callable;/*
* @author jerry404lee
* @date 2021/7/8 16:21
*/
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(
futureTask,"A"
).start();
Integer o = (Integer)futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("call方法");
return 404;
}
}
运行结果
关键:Thread只认Runnable,所以用Callable初始化Thread的过程,需要借助FutureTask。
其中futureTask的get方法可能会产生阻塞,所以把它放在最后,或者需要使用异步通信来处理。
常用辅助类
CountDownLatch
作用:计数
代码示例:
package countdownlathc;/*
* @author jerry404lee
* @date 2021/7/8 16:48
*/
import java.util.concurrent.CountDownLatch;
//减法计数器
public class CDLdemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6); //从6开始倒计时
for (int i = 0; i < 6; i++) {
new Thread(
()->{
System.out.println(Thread.currentThread().getName()+"gone");
countDownLatch.countDown();
},String.valueOf(i)
).start();
}
//countDownLatch.await();//等待计数器归零,然后再向下执行
System.out.println("结束");
}
}
其中countDownLatch的await方法重要:等待计时器归零,然后才继续向下执行。否则结果可能如下:
去除注释,结果如下:
即所期望的“结束”出现在最后。
应用场景:针对必须要执行的任务的时候使用。
CyclicBarrier
Semaphore
运行结果:
应用:限流的时候也会用
信号量:联想操作系统中的部分,没有差别。
- semaphore.acquire():获得,假设如果已经满了,等待,等待被释放为止。
- semaphore.release():释放,会将当前的信号量释放+1,然后唤醒等待的线程。
ReadWriteLock读写锁
比起Lock,是更细粒度,分为读锁和写锁
package rw;/*
* @author jerry404lee
* @date 2021/7/8 19:05
*/
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//5个线程写入
for (int i = 0; i < 5; i++) {
final int tmp = i;
new Thread(
()->{
myCache.put(tmp+"",tmp*2);
},String.valueOf(i)
).start();
}
//5个线程读取
for (int i = 0; i < 5; i++) {
final int tmp = i;
new Thread(
()->{
myCache.get(tmp+"");
},String.valueOf(i)
).start();
}
}
}
/*
* 自定义缓存
*
* */
class MyCache{
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//存,写
public void put(String key, Object value){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"写入"+key+":"+value);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
//取,读
public void get(String key){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取成功");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
运行结果
小结
- 独占锁–写锁:一次只能被一个线程占有
- 共享锁–读锁:多个线程可以同时占有
- ReadWriteLock
- 读-读 可以共存
- 读-写 不能共存
- 写-写 不能共存
BlockingQueue阻塞队列
总结4组API
SynchronousQueue 同步队列
- 没有容量
- 进去一个元素,必须等待取出来之后才能再往里面放一个元素。
- put & take
线程池(重点)
池化技术:三大方法、七大参数、四种拒绝策略
优化资源的使用
线程池、连接池。。。
线程池的好处
- 降低资源的消耗
- 提高响应的速度
- 方便管理
核心:线程复用,可以控制最大并发数,管理线程
三大方法
但是这里标红的原因是不符合编码规约,Alibaba手册中说明了不使用Executors来创建线程池的原因是为了规避资源耗尽的风险,而是应该通过ThreadPoolExecutor来创建线程池。
package poll;/*
* @author jerry404lee
* @date 2021/7/8 23:25
*/
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test01 {
public static void main(String[] args) {
//ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程
//ExecutorService executorService =Executors.newFixedThreadPool(5);//创建一个固定线程数的线程池
ExecutorService executorService =Executors.newCachedThreadPool();//创建动态的线程池
try {
for (int i = 0; i < 100; i++) {
executorService.execute(
()->{
System.out.println(Thread.currentThread().getName()+"ok");
}
);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
executorService.shutdown();//用来关闭线程池
}
}
}
七大参数
点入上述Executors创建线程池的源码中,可以看到都是由ThreadPoolExecutor创建的线程池
所以观察一下ThreadPoolExecutor到底是什么
关键是七大参数
四种拒绝策略
那么,线程池的最大线程数如何设置?
需要了解IO密集型和CPU密集型(调优)
四大函数式接口
-
lambda表达式
-
函数式接口:只有一个方法的接口
Function:函数型接口。一个输入,一个输出
Predicate:断定型接口。有一个输入参数,返回值只能是布尔类型
Consumer:消费型接口。只有输入,没有返回值。
Supplier:供给型接口。没有输入,只有返回值。
Stream流式计算
什么是流式计算?
举例
这种写法就是链式编程。
ForkJoin
并行执行任务,提高效率,针对大数据量。
特点:工作窃取
如何使用ForkJoin?
- ForkJoinPool创建
- 新建计算任务 forkjoinPool.ForkJoinTask
异步回调
Future
一般用其实现类CompletableFuture
理解JMM
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。必须成对出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock, 就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
可以再看一下缓存一致性协议MESI
Volatile
Volatile是Java虚拟机提供的轻量级的同步机制。
-
保证可见性
-
不保证原子性
原子性:不可分割,要么全部执行,要么全部不执行。即线程在执行任务的时候,不能被分割,也不能被中断,要么同时成功,要么同时失败。
package vola;/*
* @author jerry404lee
* @date 2021/7/9 12:47
* //不保证原子性
*/
public class Demo {
private static int num = 0;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i < 20; i++) {
//理论上num的最终运行结果应该为20000
new Thread(
()->{
for (int j=0; j<1000; j++) {
add();
}
},String.valueOf(i)
).start();
}
while (Thread.activeCount()>2){//main线程&gc
Thread.yield();//yield让出计算资源并重新竞争资源,礼让,让main线程放弃CPU给别的线程,这样main线程就不会执行下去,等到其他线程都死了,main线程再往下执行
}
System.out.println(num);
}
}
运行结果,num不是20000
此时,就算将num加上关键字volatile,这个结果仍然不是预期的20000,这也说明了volatile不保证原子性。
但是,如果在用synchronized修饰add方法,这就保证了add方法中的num++的原子性。运行如图:
这里,也体现出一点**volatile(轻量级)和synchronized(重量级)**两种同步机制的使用差别。
使用javap -c命令可以看到num++操作实际上是多个步骤
当然,还有另外的方法保证原子性,也就是使用原子类Atomic
原子类的底层和操作系统挂钩,CAS。在内存中修改值。Unsafe类是一个特殊的存在。
- 禁止指令重排
指令重排就是在不改变期望的运行结果的前提下,改变指令运行顺序以提升计算速度的操作。算是计算机进行优化的一种方式。但是当这种优化面对多线程并发的情况时就容易出现“问题”。
处理器在进行指令重排的时候,考虑:数据之间的依赖。
内存屏障:禁止volatile上面指令和下面指令顺序交换。
- 保证特定操作的执行顺序
- 保证某些变来个的内存可见性
单例模式
饿汉式
package single;/*
* @author jerry404lee
* @date 2021/7/9 18:02
*/
//饿汉式单例
public class Hungry {
private Hungry(){
System.out.println(Thread.currentThread().getName()+"创建实例成功");
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
双重检测锁-懒汉式
package single;/*
* @author jerry404lee
* @date 2021/7/9 18:04
*/
public class Lazy {
private Lazy(){
System.out.println(Thread.currentThread().getName()+"ok");
}
private volatile static Lazy lazy;
public static Lazy getLazy(){
if(lazy == null){
synchronized (Lazy.class){
if(lazy == null){
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(
()->{
getLazy();
}
).start();
}
}
}
如果不是双重校验锁的话,同样在多线程的情况下会创建多个实例,破坏单例。
同时,这里要注意lazy变量的修饰符volatile的作用。既然synchronized关键字已经给class(仅一个)加锁了,也就是保证了原子性,那么volatile的作用是什么呢?volatile是不是多此一举呢?
- 双重检测加锁:保证了操作的原子性,只有一个线程能创建一个实例,其他线程无法创建第二个
- volatile关键字:是为了防止因为new lazy()这个操作因为底层是多条指令组成,所以可能会在多线程&指令重排的双重环境下导致只是完成了“分配空间”+“对象地址引用”这两步(指令重排),此时另外的线程过来发现对象已经被创建了(实际上还有第三步没有执行完,所以也就是没有真正完成初始化,因为指令重排的缘故而被欺骗了),但是导致最终获取到的对象是还没有被初始化完成的。
通过反射破坏单例
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy instance = Lazy.getLazy();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);//无视了私有的构造器
Lazy tmp = declaredConstructor.newInstance();
if ( tmp.hashCode()==instance.hashCode() && tmp.equals(instance)) {
System.out.println("相同实例");
} else {
System.out.println("创建了两个不同实例,破坏了单例");
}
}
CAS(compare and swap)
如上图:
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环。
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
比较初始值initialValue和期望值expectedValue是否相等,也就是是否与期望相符
- 如果不相等,则atomicInteger不修改值(仍然是initialValue的值)
- 如果相等,则将atomicInteger修改为新值newValue
可能还会问道Unsafe类
ABA问题
ABA问题(狸猫换太子)
旧A-被修改为B-为修改为新A,看似值还是A,但是这个修改的过程已经发生了。
Java中如何解决ABA问题?
用原子引用AtomicStampedReference,原理其实就是加了版本号,时间戳,进一步在值相等的情况下区分旧A和新A(联想MVCC版本控制)
可以看到,多了一个参数initialStamp
而compareAndSet函数中也多了两个参数expectedStamp和newStamp
package CAS;/*
* @author jerry404lee
* @date 2021/7/9 19:15
* //CAS:compare and swap比较并交换
*/
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
public class CASDemo {
public static void main(String[] args) {
AtomicStampedReference<Object> atomicInteger =
new AtomicStampedReference<>(19, 1);
//public final boolean compareAndSet(int expectedValue, int newValue)期望值,新值
//如果期望的值与定义值相等,那么就更新为新值,否则不更新
new Thread(
()->{
atomicInteger.compareAndSet(19,20,atomicInteger.getStamp(),atomicInteger.getStamp()+1);
System.out.println("线程A的版本号是"+atomicInteger.getStamp());
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
},"a"
).start();
new Thread(
()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicInteger.compareAndSet(20,21,atomicInteger.getStamp(),atomicInteger.getStamp()+1);
System.out.println("线程B的版本号是"+atomicInteger.getStamp());
},"b"
).start();
}
}
注意,这里,因为自动装箱的原因,有个大坑,推荐使用较小的参数(<128)进行测试。如果泛型是一个包装类,注意对象的引用问题。
公平锁、非公平锁
Lock部分有提及。
可重入锁、自旋锁等
可重入锁(递归锁):某个线程已经获取了某个锁,那么他可以再次获取该锁而不陷入死锁。
详细可见 可重入锁详解
自旋锁
简单介绍各种锁 通俗易懂 悲观锁、乐观锁、可重入锁、自旋锁、偏向锁、轻量/重量级锁、读写锁、各种锁及其Java实现!
死锁排查
-
用 jps -l 定位进程号
-
使用 jstack 进程号 查看进程信息
这里显示GC,是因为在源程序中写了死循环测试。
-
日志
-
看堆栈信息