JUC

  • 1、线程和进程
  • 2、LOCK锁(重点)
  • 3、线程通信---生产者和消费者问题
  • 4、8锁现象(关于锁的8个问题)
  • 5、集合类不安全
  • 6、Callable
  • 7、常用的辅助类
  • CountDownLatch
  • CyclicBarrier
  • Semaphore
  • 8、读写锁
  • 9、阻塞队列
  • 10、线程池(重点)
  • 线程池:3大方法,7大参数,4种拒绝策略
  • 11、四大函数式接口(重点必须掌握)
  • 12、Stream流式计算
  • 13、ForkJoin
  • 14、异步回调
  • 15、JMM
  • 16、Volatile
  • 17、彻底玩转单例模式
  • 18、深入理解CAS
  • 19、原子引用
  • 20、各种锁的理解
  • 公平锁、非公平锁
  • 可重入锁
  • 自旋锁
  • 死锁


1、线程和进程

  • 线程、进程
  • 线程:一个程序
一个进程往往包含很多线程,至少包含一个

java默认有两个线程:main、GC
  • 线程:线程是进程中的实际运作单位,是操作系统能够进行运算调度的最小单位
  • Java真的能开启线程吗?

不能,java不能直接操作硬件

Thread中的start方法是一个同步方法,调用时会将此线程加入线程组,然后调用start0方法,start0方法是一个本地方法

//本地方法,调用底层的C++,Java运行在虚拟机,不能直接操作硬件
priavte native void start0();
  • 并发、并行
  • 并发(单核):多线程操作同一个资源,快速交替
  • 并行(多核):多个线程同时执行

cpu多核,多个线程可以同时执行

  • 并发编程的本质:充分利用CPU资源
  • 线程有几个状态? 6种
  • NEW 新生
  • RUNNABLE 运行
  • BLOCKED 阻塞
  • WAITING 等待
  • TIMED_WAITING 超时等待
  • TERMINATED 终止
  • wait/sleep区别
  • 来自不同的类
  • wait -->Object
  • sleep -->Thread
  • 关于锁的释放
  • wait会释放锁
  • sleep不释放,抱着锁睡觉
  • 使用的范围不同
  • wait必须在同步代码块中
  • sleep可以在任何地方睡
  • 是否需要捕获异常
  • wait不需要捕获异常
  • sleep需要捕获异常
  • 唤醒方式
  • wait需要其他线程调用同一对象的nofity()/nofityAll()才能重新恢复
  • sleep在时间到了之后会自动重新恢复

2、LOCK锁(重点)

  • 传统Synchronized
    本质:队列、锁
  • Lock
  • 实现类
  • ReentrantLock 可重入锁(常用)
  • ReentrantReadWriteLock.ReadLock 读锁
  • ReentrantReadWriteLock.WriteLock 写锁
  • 公平锁与非公平锁
  • 公平锁:十分公平,先来后到
  • 非公平锁:是十分不公平,可以插队(默认)
  • 使用
Lock lock = new 实现类;
//加锁
lock.lock();
//尝试获取锁
//lock.tryLock();
try{
    //业务代码
}catch(Exception e){
    e.printStackTrace();
}finally{
    //解锁
    lock.unlock
}
  • Synchronized和Lock区别
  • Synchronized 是内置的Java关键字;Lock是一个Java接口
  • Synchronized 无法判断获取所得状态;Lock可以判断是否获取到了锁
  • Synchronized 会自动释放锁;Lock必须手动释放锁,如果不释放锁,死锁
  • Synchronized 只要没有获得锁就一直等待;Lock锁不一定会一直等待下去
  • Synchronized 可重入锁,不可以中断,非公平;Lock 可重入锁,可以判断锁,默认非公平(可以设置)
  • Synchronized 适合锁少量的代码同步问题;Lock适合锁大量的同步代码
  • 锁是什么,如何判断锁的是谁

3、线程通信—生产者和消费者问题

  • Synchronized版 Synchronized wait() notify()
    A、B 两个线程,线程安全 if判断
    A、B、C、D四个线程可能会出现虚假唤醒的情况 if判断一定要改成while判断
  • JUC版 Lock await() signal()
  • Confition 精准通知和唤醒线程
    任何一个新的技术,绝对不仅仅覆盖了原来的技术,是优势和补充

4、8锁现象(关于锁的8个问题)

  • 如何判断锁的是谁,锁的是谁
    **深刻理解锁 **
  • 一个对象两个同步方法,因为有锁,Synchronized 锁的对象是方法的调用者,谁先拿到谁先执行
  • 增加在第一个线程调用方法里等待4秒,还是谁先拿到谁执行
  • 增加了一个普通方法后,先普通方法,再锁方法
  • 两个对象两个同步方法,不等待的先执行完,按时间
  • 增加两个静态同步方法一个对象,谁先拿到谁先执行,因为static静态方法,类刚加载就有了,锁的是Class
  • 两个静态同步方法两个对象,谁先拿到谁先执行,static方法,锁的是Class ,两个对象的Class模板只有一个
  • 普通同步方法和静态同步方法一个对象,普通同步方法先执行,静态的同步方法锁的是类,普通同步方法锁的是方法
  • 普通同步方法和静态同步方法两个对象,普通同步方法先执行,静态的同步方法锁的是类,普通同步方法锁的是方法
  • 小结
  • new
  • this 具体的某个方法
  • static
  • 唯一的Class模板

5、集合类不安全

  • List不安全
  • 并发下ArrayList不安全 (会报出并发修改异常) java.util.ConcurrentModificationException
  • (Vector默认就是安全的) 但ArrayList是1.2出的 Vector是1.0出的
List<String> list = new Vector<>();
  • 可以用Collections.sysnchroizedList(new ArrayList<>()); 变得安全
List<String> list = Collections.sysnchroizedList(new ArrayList<>);
  • 可以用JUC下的CopyOnWriteArrayList<>();变得安全
List<String> list = new CopyOnWriteArrayList<>();
  • CopyOnWrite 写入时复制 COW 计算机程序设计领域的一种优化策略
  • 多个线程调用list时,读取的时候是固定的,写入的时候会有覆盖问题,CopyOnWrite可以在写入的时候避免覆盖,造成数据问题
  • CopyOnwrite与Vector相比优势?
  • Vector中使用了Sysnchronized锁,影响性能
  • CopyOnWrite中使用Lock锁,写入时候先复制一份,写入之后再set回去
  • Set不安全
  • 同List理可证,HashSet也不安全,会报出(并发修改异常) java.util.ConcurrentModificationException
  • 可以用Collections.sysnchroized(new HashSet<>())变得安全
Set<String> set = Collections.sysnchroizedSet(new HashSet<>());
  • 可以用JUC下的CopyOnWriteHashSet<>()方法变得安全
Set<String> set = new CopyOnWriteArraySet<>();
  • HashSet底层是什么?
  • 源码:
//HashSet源码public HashSet() {        map = new HashMap<>();    }//add方法源码public boolean add(E e) {        return map.put(e, PRESENT)==null;    }

HashSet的不可重复就是利用了HashMap中Key不可重复

  • Map不安全
  • 同理也会抛出并发修改异常
  • 可以用Collections.sysnchrized(new HashMap<>())变得安全
Map<String,String> map = Collections.sysnochrizedMap(new HashMap<>());
  • 可以用JUC下的ConcurrentHashMap<>()变得安全
Map<String,String> map = new ConcurrentHashMap<>();

6、Callable

  • 与Runnable类似,创建线程的第三种方式
  • 可以有返回值
  • 可以抛出异常
  • 方法不同,run()/call()
  • 使用方式
new Thread(new FutureTask<Integer>(new CallableTest())).start();
  • 因为Thread中只能接收Runnable对象,FutureTask与Runnable和Callable都有关,互相转换,所以需要FutureTask中传入Callable对象,再把FutureTask传入到Thread中进行启动
  • 细节
  • FutureTask可以get得到Callable的返回值,所以可能会产生阻塞,要把它放在最后一行或者使用异步通信来处理
futureTask.get()
  • FutureTask的结果会被缓存,效率高

7、常用的辅助类

CountDownLatch

  • 减法计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
  • counDownLatch.countDown(); 数量-1 不会阻塞
  • countDownLatch.await();等待计数器归零 假设计数器变为0,countDownLatch.await就会被唤醒,继续执行

CyclicBarrier

  • 加法计数器
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {            //达到预设数值之后的业务        });
  • cyclicBarrier.await等待计数器到数值 假设计数器变为预设的数值,cyclicbarrier.await就会执行

Semaphore

  • 信号量
Semaphore semaphore = new Semaphore(3);
  • 多个共享资源互斥的时候使用
  • 并发限流,控制最大的线程数
  • semaphore.acquire(); 获得 假设如果线程满了,等待,等待被释放为止
  • semaphore.release();释放 将当前信号量+1,然后唤醒等待的线程 放到finally中

8、读写锁

  • 读-读 可以共存 读-写不可共存 写-写不可共存
  • 创建ReadWriteLock对象
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
  • 读锁readWriteLock.readLock() 共享锁
    readWriteLock.readLock().lock();读锁上锁
    readWriteLock.readLock().unlock();读锁释放
  • 写锁readWriteLock.writeLock() 独占锁
    readWriteLock.writeLock().lock();写锁上锁
    readWriteLock.writeLock().unlock();写锁释放

9、阻塞队列

  • BlockingQueue不是新的东西 BlockingQueue和List和Set同级
  • 使用情况
  • 多线程并发执行、线程池
  • LinkedBlockingQueue
  • ArrayBlockingQueue
  • 队列定义
    ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(队列大小)
  • 添加元素
  • add
    arrayBlockingQueue.add("a");如果队列已满会抛出队列已满异常 Queue full
  • offer
    arrayBlockingQueue.offer("a");如果队列已满不会抛出异常,会返回false
  • put
    arrayBlockingQueue.put("a");如果队列已满不会抛出异常,会一直等待,知道有空闲位置可以添加元素
  • offer(元素,等待时间,时间单位)
    arrayBlockingQueue.offer("d",2,TimeUnit.SECONDS);如果队列已满会等待2秒,2秒过后如果还没有空闲位置可以添加元素,就退出返回false
  • 取元素 FIFO先进先出
  • remove
    arrayBlockingQueue.remove();如果队列为空会抛出没有异常 NoSuchElementException
  • poll
    arrayBlockingQueue.poll();如果队列为空不会抛出异常,会返回null值
  • take
    arrayBlockingQueue.take();如果队列为空,会一直等待,直到队列中有元素可以被取出
  • poll(等待时间,时间单位)
    arrayBlockingQueue.poll(2, TimeUnit.SECONDS);如果队列为空,会等待2秒,2秒过后如果队列中还没有元素可以取出,就退出返回null值
  • 查看队首元素
  • element
    arrayBlockingQueue.element();如果队列为空会抛出异常 NoSuchElementException
  • peek
    arrayBlockingQueue.peek();如果队列为空,会返回null值
  • 四组API

方式

抛出异常

有返回值

阻塞 等待

超时等待

添加

add()

offer()

put()

offer(等待时间,时间单位)

移除

remove()

poll()

take()

poll(等待时间,时间单位)

检测队首元素

element()

peek()

-

-

  • SysnchronousQueue 同步队列
  • 没有容量,添加一个元素必须取出一个元素,之后才能再次添加元素,只能放一个元素 不然会一直等待!
  • put
    blockingQueue.put("1");
  • take
    blockingQueue.take();

10、线程池(重点)

线程池:3大方法,7大参数,4种拒绝策略

  • 池化技术
    程序运行本质:占用系统的资源!优化资源的使用 ->池化技术
    线程池、连接池、对象池… 创建 销毁 十分浪费资源
    事先准备好资源,有人要用,就来我这里拿,用完之后还给我
  • 线程池的好处
  • 降低资源的消耗
  • 提高相应的速度
  • 方便管理
    线程服用、可以控制最大并发数、管理线程
  • 3大方法
ExecutorService threadPool1 = Executors.newSingleThreadExecutor(); //单个线程的线程池
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); //固定大小的线程池
ExecutorService threadPool3 = Executors.newCachedThreadPool(); //可伸缩的线程池,最大数量为Integer.MAX_VALUE会造成OOM内存泄漏

最好不要使用Excutors创建线程,可能会出现OOM内存泄漏,有资源耗尽的风险,而是通过ThreadPoolExecutor的方式

ExecutorService threadPool4 = new ThreadPoolExecutor(                2,                5,                 10,                TimeUnit.SECONDS,                new ArrayBlockingQueue<>(3),                Executors.defaultThreadFactory(),                new ThreadPoolExecutor.DiscardPolicy());
  • 7大参数
  • 核心线程池大小
  • 最大线程池大小
  • 定义方式
  • CPU密集型
  • 几核CPU就定义为几,可以保证CPU的效率最高
Runtime.getRuntime().availableProcessors();//获取CPU核数
  • IO密集型
  • 大于程序中十分耗IO的线程数,不会造成阻塞,一般2倍
  • 超时未使用就释放
  • 超时单位
  • 阻塞队列
  • 线程工厂 Executors.defaultThreadFactory()
  • 拒绝策略
  • 4种拒绝策略
  • AbortPolicy() 抛出异常 默认
  • DiscardPolicy() 直接丢弃
  • DiscardOldestPolicy() 抛弃最老的线程,提交新的线程
  • CallerRunsPolicy() 交给申请的线程执行

11、四大函数式接口(重点必须掌握)

  • 传统程序员:泛型、反射、枚举 (JDK1.5)
  • 新时代程序员:lambda表达式链式编程函数式接口Stream流式计算 (JDK1.8)
  • @FunctionalInterface函数式接口:只有一个方法的接口,简化编程,再新版本的框架底层大量应用,
  • 例如:Runnbale接口
@FunctionalInterfacepublic interface Runnable {    public abstract void run();}
  • 例如foreach的参数,典型的消费型函数式接口
@FunctionalInterfacepublic interface BiConsumer<T, U> {    void accept(T t, U u);}
  • 四大函数式接口
    只要是函数时接口,就能拿lambda表达式简化
  • Function函数型接口 输入参数T返回R类型的数据
@FunctionalInterfacepublic interface Function<T, R> {    R apply(T t);}
  • Predicate断定型接口 输入一个参数,返回一个布尔类型数据
@FunctionalInterfacepublic interface Predicate<T> {    boolean test(T t);}
  • Consumer消费型接口 输入一个参数,没有返回值
@FunctionalInterfacepublic interface Consumer<T> {    void accept(T t);}
  • Supplier供给型接口 不传入参数,返回一个数据T
@FunctionalInterfacepublic interface Supplier<T> {    T get();}

12、Stream流式计算

  • 什么是Stream流式计算
/** * 题目要求:一行代码实现 * 现在有6个用户,筛选 * 1.ID必须是偶数 * 2.年龄必须大于23岁 * 3.用户名转为大写字母 * 4.用户名倒着输出 * 5.只输出一位用户 */User u1 = new User(1,"a",21);User u2 = new User(2,"b",22);User u3 = new User(3,"c",23);User u4 = new User(4,"d",24);User u5 = new User(6,"e",26);User u6 = new User(8,"f",28);//存储List<User> list = Arrays.asList(u1, u2, u3, u4, u5,u6);//计算list.stream()        .filter(user -> {return user.getId()%2==0;})        .filter(user -> {return user.getAge()>23;})        .map(user -> {user.setName(user.getName().toUpperCase()); return user;})        .sorted((user1,user2)->{return user2.getName().compareTo(user1.getName());})        .limit(1)        .forEach(System.out::println);

13、ForkJoin

  • ForkJoin分支合并JDK1.7就存在了 在大数据量时,并行执行任务,提高效率
  • 在必要的情况下,将一个大任务,进行拆分(fork) 成若干个子任务(拆到不能再拆,这里就是指我们制定的拆分的临界值),再将一个个小任务的结果进行join汇总。
  • 特点
    工作窃取 (维护的都是双端队列)
  • 使用
  1. 创建ForkJoinPool,通过ForkJoinPool执行
ForkJoinPool forkJoinPool = new ForkJoinPool();
  1. 计算任务
forkjoinpool.execute(ForkJoinTask task)//执行,同步  无返回值
forkJoinPool.submit(ForkJoinTask task);//提交,异步  有返回值
  1. 计算类要继承ForkJoinTask的两个子类:RecursiveAction或者RecursiveTask(V),其中RecursiveAction任务没有返回值,RecursiveTask任务有返回值.这两个子类都有一个抽象的方法,叫做compute(),用来定义任务的逻辑.

14、异步回调

  • Future设计的初衷:对将来的某个时间的结果进行建模
  • 无返回值的异步调用CompletableFuture.runAsync()
//没有返回值的runAsync异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> {
    try {
        TimeUnit.SECONDS.sleep(2);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    System.out.println("runAsync");
});

System.out.println("================");

Void aVoid = completableFuture.get();//获取阻塞执行结果

//先输出================,等待2秒后输出runAsync
  • 有返回值的异步调用CompletableFuture.supplyASync()
//有返回值的supplyAsync异步回调CompletableFuture<Integer> uCompletableFuture = CompletableFuture.supplyAsync(() -> {    //int i = 10/0;    return 1024;});System.out.println("---------------");System.out.println(uCompletableFuture.whenComplete((t, u) -> {    System.out.println(t);//正常的返回结果    System.out.println(u);//错误信息}).exceptionally((e) -> {    e.getMessage();    return 233;//可以获取到错误的返回结果}).get());

15、JMM

  • 请你谈谈你对Volatile的理解
    Volatile时Java虚拟机提供的轻量级的同步机制
  1. 保证可见性
  2. 不保证原子性
  3. 禁止指令重排
  • 什么是JMM?
  • JMM是Java内存模型,不存在东西,是概念、约定
  • 关于JMM的一些同步的约定
  • 线程解锁前,必须把共享变量刷回主存
  • 线程加锁前,必须读取主存中的最新值到工作内存中
  • 加锁和解锁是同一把锁
  • 8种操作
  • lock(锁定)
  • unlock(解锁)
  • read(读取)
  • load(载入)
  • use(使用)
  • assign(赋值)
  • write(写入)
  • store(存储)
  • 使用规则
  • 不允许read和load、write和store操作之一单独出现。即使用了read必须load,使用了write必须stroe
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主存中诞生,不允许工作内存直接使用一个未被初始化的变量。即对变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock之后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存种此变量的值,在执行引擎使用这个变量前,必须重新进行load或assign操作初始化变量的值
  • 如果一个变量两没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把变量同步回主内存

16、Volatile

  • 保证可见性
//不加volatile线程就感知不到num值发生了变化private volatile static int num = 0;
  • 不保证原子性
  • 原子性:不可分割
  • 不使用synchronized和了lock怎么保证num++的原子性?
  • 使用JUC包下的原子类 比用锁更高效
  • AtomicInteger
private volatile static AtomicInteger num = new AtomicInteger();num.incrementAndGet(); //++i    使用的是CASnum.getAndIncrement(); //i++    使用的是CAS
  • AtomicLong
  • AtomicBoolean
  • 这些类的底层都与系统挂钩,在内存中修改值,Unsafe是很特殊的存在
  • 禁止指令重排
  • 指令重排: 计算机并不按照程序员写的去执行
  • 源代码–>编译器优化的重排–>指令并行也可能重排–>内存系统也会重排–>执行
    处理器在进行指令重排的时候会考虑数据之间的依赖性
int x = 1;//1int y = 2;//2x = x + 1;//3y = x * x;//4//我们所期望的 1234   但可能执行的时候 2134 1324//不可能是 4123

默认 a b x y 这4个值都为零

线程A

线程B

x = a;

y = b;

b = 1;

a = 2;

正常情况下: a=2 b=1 x=0 y=0

指令重排可能会: a=2 b=1 x=2 y=1

  • Volatile可以避免指令重排:
  • 内存屏障 CPU指令
  • 作用
  • 保证特定的操作的执行顺序
  • 可以保证某些变量的可见性(利用这些特性,volatile实现了可见性)
  • Volatile是可以保持可见性.不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

17、彻底玩转单例模式

  • 饿汉式单例
  • 可能会浪费空间
//饿汉式单例public class Hungry {    private Hungry(){}        private  final static  Hungry HUNGRY = new Hungry();        public static Hungry getInstance(){        return HUNGRY;    }}
  • 懒汉式单例
//懒汉式单例模式public class LazyMan {    private LazyMan(){}        private static LazyMan lazyMan;        public static LazyMan getInstance(){        if (lazyMan==null){            lazyMan = new LazyMan();        }        return lazyMan;    }}
  • 单线程下确实单例ok,多线程并发情况下无法单例,需要为class加锁,并且加锁 ===>双重检测锁模式的懒汉式单例 DCL懒汉式
//懒汉式双重检测锁DLC单例模式public class LazyMan {    private LazyMan(){}    private volatile static LazyMan lazyMan;    public static LazyMan getInstance() {        if (lazyMan == null) {            synchronized (LazyMan.class) {                if (lazyMan == null) {                    lazyMan = new LazyMan();//不是原子性操作                    /**                     * 1、分配内存空间                     * 2、执行构造方法,初始化对象                     * 3、把对象指向空间                     * 期望:123  可能132  此时lazyMan还没有完成构造                     * 避免指令重排需要加 volatile                     */                }            }        }        return lazyMan;    }}
//道高一尺,魔高一丈public class LazyMan {    private static  boolean qwe = false;    private LazyMan(){        synchronized (LazyMan.class) {            if (qwe==false){                qwe = true;            }else {                throw new RuntimeException("不要试图拿反射破坏单例");            }        }    }    private volatile static LazyMan lazyMan;    public static LazyMan getInstance() {        if (lazyMan == null) {            synchronized (LazyMan.class) {                if (lazyMan == null) {                    lazyMan = new LazyMan();                }            }        }        return lazyMan;    }        //反射破坏!    public static void main(String[] args) throws Exception {        //LazyMan instance1 = LazyMan.getInstance();        Field qwe = LazyMan.class.getDeclaredField("qwe");        qwe.setAccessible(true);        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);        declaredConstructor.setAccessible(true);        LazyMan instance1 = declaredConstructor.newInstance();        qwe.set(instance1, false);        LazyMan instance2 = declaredConstructor.newInstance();        System.out.println(instance1);        System.out.println(instance2);    }}
  • 静态内部类
//静态内部类public class Holder {    private  Holder(){}    public static Holder getInstance() {        return InnerClass.HOLDER;    }    public static class InnerClass{        private static final Holder HOLDER = new Holder();    }}

单例不安全

  • 枚举
public enum EnumSingle {    INSTANCE;        public EnumSingle getInstance(){        return INSTANCE;    }}
  • 反射不能破坏枚举
  • 枚举也是一个类,只是继承了枚举接口 枚举默认就是单例
  • 没有无参构造,只有一个有参构造 (String,int)

18、深入理解CAS

  • 什么是CAS
  • compareAndSet 比较并交换 CAS是CPU的并发原语
  • CAS:比较并交换当前工作内存种的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环(自旋锁)
  • CAS缺点:
  • 循环会耗时
  • 一次只能保证一个共享变量的原子性
  • Unsafe类
  • Java无法操作内存,Java可以调用C++使用native方法,C++可以调用内存,Unsafe就是Java的后门,可以通过这个类操作内存
  • 内存操作,效率很高
  • 自旋锁
  • ABA问题(狸猫换太子) 原子引用

19、原子引用

解决ABA问题 对应的思想—>乐观锁

AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(初始值,版本号 );

20、各种锁的理解

公平锁、非公平锁

  • 公平锁:非常公平,先来后到
  • 非公平锁:非常不公平,可以插队,(默认都是非公平锁)
Lock lock1 = new ReentrantLock();//默认非公平锁Lock lock2 = new ReentrantLock(true);//设置公平锁

可重入锁

  • 所有的锁都是可重入锁(递归锁)
  • 拿到外面的锁,就可以拿到里面的锁,自动获得

自旋锁

  • spinlock
  • 不断尝试,直到解锁为止

死锁

  • 死锁?
  • 两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞现象
  • 四要素
  • 互斥条件
  • 请求和保持条件
  • 不可剥夺条件
  • 环路等待条件
  • 解决死锁?
  • 破坏四要素
  • 死锁排查?
  • 查询堆栈信息
  • 使用jdk bin目录下,jps定位进程号 jps -l
  • 使用jdstack 进程号 找到死锁问题
  • 查看日志