JUC

文章目录

  • JUC
    • 1、什么是JUC?
      • 1.1、进程和线程
      • 1.2、并发&并行
      • 1.3、wait和sleep的区别
    • 2、锁(*重点)
        • 2.1、synchronized和Lock的区别
      • 2.2、生产者&消费者问题
      • 2.3、那么锁是什么?如何判断锁的是谁?(8锁现象)
    • 3、集合类不安全
    • 4、Callable接口
    • 5、常用的辅助类(必会)
      • 5.1、CountDownLatch(减法计数器)
      • 5.2、CyclicBarrier(加法计数器)
      • 5.3、semaphore(信号量)
    • 6、读写锁(共享锁,排他锁)
    • 7、阻塞队列
      • 7.1、对阻塞队列的理解
      • 7.2、什么情况下我们会使用阻塞队列: 多线程并发处理,线程池!
      • 7.3、学会使用队列
      • 7.4、SynchronousQueue同步队列
    • 8、线程池(*重点)
      • 8.1、池化技术
      • 8.2、线程池的好处
      • 8.1、三大方法
      • 8.2、七大参数
      • 8.3、手动创建一个线程池
      • 8.4、四种拒绝策略(*)
      • 8.5、 最大核心线程池大小设置多大合适?(调优)
    • 9、四大函数式接口(必需掌握)
      • 9.1、什么是函数式接口?(==只有一个方法的接口==)
      • 9.2、Function 函数式接口
    • 10、Stream流式计算
      • 10.1、什么是流式计算?
    • 11、ForkJoin(分支合并)
      • 11.1、什么是ForkJoin?
      • 11.2、ForkJoin的特点:==工作窃取==
      • 11.3、怎么使用ForkJoin呢?
    • 12、异步回调
    • 13、JMM
      • 什么是JMM?
    • 14、Volatile(轻量级的同步机制)
    • 15、彻底玩转单例模式
    • 16、深入理解CAS
      • 16.1、什么是CAS ?
      • 16.2、Unsafe类
      • 16.3、ABA问题(狸猫换太子)
    • 17、原子引用
    • 18、各种锁的理解
      • 18.1、乐观锁&悲观锁
        • 18.1.1、悲观锁(互斥同步锁)
        • 18.1.2、悲观锁主要分为共享锁和排他锁
        • 18.1.3、悲观锁有哪些劣势?
        • 18.1.3、乐观锁(非互斥同步锁)
        • 18.1.4、乐观锁悲观锁对比
      • 18.2、公平锁、非公平锁
      • 18.3、可重入锁
      • 18.4、自旋锁
      • 18.5、死锁

1、什么是JUC?

Java.util.concurrent 在并发编程中使用的工具类

1.1、进程和线程

进程:是操作系统中正在运行的应用程序,是程序的集合,是操作系统资源分配的基本单位。一个进程往往可以包含多个线程,至少要1个。

线程:是进程的执行单元或说执行场景,用来执行具体的任务和功能,是CPU调度和分派的基本单位。

进程是操作系统调度和资源分配的最小单位,线程是CPU调度和分派的最小单位。

java默认有几个线程?至少2个:main主线程,GC垃圾回收线程

**java真的可以开启线程吗?**开不了。

它是先通过调用start()方法:public synchronized void start()

该方法里先是把该线程加入到一个线程组中:group.add(this)

然后调用了start0()方法,这是个本地方法,它是调用底层的C++程序

要明白一点,java是无法直接操作硬件的。而是通过这些本地方法去调用底层的c或c++程序来操作硬件。

1.2、并发&并行

并发(多线程操作同一资源)

  • CPU一核,模拟出多线程,多个线程间频繁切换,形成并发假象。

并行(同一时间点多个线程同时执行,真正的并发)

  • CPU多核,同一时间点多个线程同时执行,各自执行个的。

并发编程的本质:充分利用CPU的资源,提高程序的执行效率

java线程有几个状态?

public enum State {NEW,//新建状态RUNNABLE,//运行状态BLOCKED,//阻塞状态WAITING,//等待(死死地等)TIMED_WAITING,//超时等待(超过一定时间就不等了)TERMINATED;//终止状态}

  1. 新建状态(New):新建一个线程对象。

  2. 就绪/可运行状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

  3. 运行状态(Running):就绪状态的线程获得CPU并执行程序代码。

  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

     等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep的状态超时、join等待线程终止或者超时、以及I/O处理完毕时,线程重新转入就绪状态。
    

死亡状态(Dead):线程执行完成或者因异常退出run方法,该线程结束生命周期。

**wait() 与 notify() **

wait():使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。

wait(long,int):对于超时时间更细力度的控制,单位为纳秒。

notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。

notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。

notify()和notifyAll()的区别:
① notify()随机唤醒等待队列中一个线程,notifyAll()唤醒正在等待队列中全部线程。
② notify()可能引发异常,比如一个生成者多个消费者情况下,消费者消费完随即唤醒一个线程,恰好也是消费线程,就会引发异常。所以建议使用notifyAll()

1.3、wait和sleep的区别

  1. 来自不同的类:

    wait是Object的方法

    sleep是Thread的静态方法

  2. 关于锁的释放:

    wait会释放锁

    sleep不释放锁(抱着锁睡)

  3. 使用范围:

    wait必须在同步代码块中

    sleep可以在任何地方睡

2、锁(*重点)

Java中隐式锁:synchronized;显式锁:Lock

2.1、synchronized和Lock的区别

  1. 出身不同:

    synchronized是Java中的关键字,是由JVM来维护的。是JVM层面的锁。

    Lock是JDK5以后才出现的具体的类。使用lock是调用对应的API。是API层面的锁。

    sync是底层是通过monitorenter进行加锁(底层是通过monitor对象来完成的,其中的wait/notify等方法也是依赖于monitor对象的。只有在同步块或者是同步方法中才可以调用wait/notify等方法的。因为只有在同步块或者是同步方法中,JVM才会调用monitory对象的);通过monitorexit来退出锁的

    而lock是通过调用对应的API方法来获取锁和释放锁的。

    概述,可以把sync理解为官二代或者是星二代,从娘胎出来自带光环的。Lock就是我们普通努力上进的人。

  2. 使用方式不同

    Sync是隐式锁。Lock是显示锁。

    所谓的显示和隐式就是在使用的时候,使用者要不要手动写代码去获取锁和释放锁的操作。

    我们大家都知道,在使用sync关键字的时候,我们使用者根本不用写其他的代码,然后程序就能够获取锁和释放锁了。那是因为当sync代码块执行完成之后,系统会自动的让程序释放占用的锁。sync是由系统维护的,如果非逻辑问题的话话,是不会出现死锁的。

    在使用Lock的时候,使用者需要手动的获取和释放锁。如果没有释放锁,就有可能导致出现死锁的现象。手动获取锁方法:lock.lock()。释放锁:unlock方法。需要配合try/finaly语句块来完成。

    用生活中的一个case来形容这个不同:官二代和普通人的你在进入机关大院的时候待遇。官二代不需要出示什么证件就可以进入,但是你需要手动出示证件才可以进入。

  3. 等待是否可中断

    sync是不可中断的。一个线程获得了锁,其他线程,必须傻傻等待该线程释放锁,不能去中断该线程,除非该线程抛出异常或者正常运行完成。其他线程才能获得锁。

    Lock可以中断的。中断方式:

    ​ 1:调用设置超时方法 tryLock(long timeout ,timeUnit unit)

    ​ 2:调用lockInterruptibly()放到代码块中,然后调用interrupt()方法可以中断

    生活中小case来理解这一区别:官二代一般不会做饭。都会去餐厅点餐等待着餐厅出餐。普通人的你既可以去餐厅等待,如果等待时间长的话,你就可以回去自己做饭了。

  4. 加锁的时候是否可以公平

    java默认的是非公平锁。为什么?因为非公平锁比较公平。通常情况下,非公平锁的吞吐量要比公平锁的吞吐量高。

    ReentrantLock实现了Lock接口,加锁和解锁都需要显式写出,注意一定要在适当时候unlock
    和synchronized相比,ReentrantLock用起来会复杂一些。在基本的加锁和解锁上,两者是一样的,所以无特殊情况下,推荐使用synchronized。ReentrantLock的优势在于它更灵活、更强大,增加了轮训、超时、中断等高级功能。
    ReentrantLock的内部类Sync继承了AQS,分为公平锁FairSync和非公平锁NonfairSync。公平锁:线程获取锁的顺序和调用lock的顺序一样,FIFO;
    非公平锁:线程获取锁的顺序和调用lock的顺序无关,全凭运气。ReentrantLock默认使用非公平锁是基于性能考虑,公平锁为了保证线程规规矩矩地排队,需要增加阻塞和唤醒的时间开销。如果直接插队获取非公平锁,跳过了对队列的处理,速度会更快。
    

    sync:非公平锁。

    lock:两者都可以的。默认是非公平锁。在其构造方法的时候可以传入Boolean值。

    ​ true:公平锁

    ​ false:非公平锁

    生活中小case来理解这个区别:官二代一般都不排队,喜欢插队的。普通人的你虽然也喜欢插队。但是如果遇到让排队的情况下,你还是会排队的。

  5. 锁绑定多个条件来condition

    sync:没有。要么随机唤醒一个线程;要么是唤醒所有等待的线程。

    Lock:用来实现分组唤醒需要唤醒的线程,可以精确的唤醒,而不是像sync那样,不能精确唤醒线程。lock的粒度更细。

  6. 从性能比较

  7. 从使用锁的方式比较


2.2、生产者&消费者问题

/*
* 生产者消费者模式
* */
public class A {public static void main(String[] args) {Data data = new Data();
//        生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increase();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();//        消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decrease();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}
}//资源类
class Data{private  int number = 0;//+1public synchronized void increase() throws InterruptedException {while (number!=0){//这里必须用while循环。来防范虚假唤醒的情况。不能用if!因为一个生产一个消费时if还不会出现问题,但是多个生产,多个消费时,if就会出现问题,因为if只进行一次判断,//等待this.wait();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知来消费this.notify();}//-1public synchronized  void decrease() throws InterruptedException {while (number == 0){//等待this.wait();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知生产this.notify();}
}

/*
* 生产者消费者模式
*想实现的效果就是,A执行完B执行,B执行完C执行,C执行完D执行,D执行完A执行
* */
public class A {public static void main(String[] args) {Data data = new Data();
//        生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increaseA();} catch (InterruptedException e) {e.printStackTrace();}}},"A").start();//        消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decreaseB();} catch (InterruptedException e) {e.printStackTrace();}}},"B").start();}//        生产线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.increaseC();} catch (InterruptedException e) {e.printStackTrace();}}},"C").start();//        消费线程new Thread(()->{for (int i = 0; i < 10; i++) {try {data.decreaseD();} catch (InterruptedException e) {e.printStackTrace();}}},"D").start();}
}//资源类
class Data{private  int number = 0;Lock lock  = new ReentrantLock();//如何精准唤醒?就是通过new多个condition监视器,每个condition监视一个线程Condition conditionA = lock.newCondition();Condition conditionB = lock.newCondition();Condition conditionC = lock.newCondition();Condition conditionD = lock.newCondition();public void increaseA() throws InterruptedException {lock.lock();try{while (number!=0){conditionA.await();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知B来消费conditionB.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}public void decreaseB() throws InterruptedException {lock.lock();try{while (number == 0){conditionB.await();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知C来生产conditionC.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}  }public void increaseC() throws InterruptedException {lock.lock();try{while (number!=0){conditionC.await();}number++;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知D来消费conditionD.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}//-1public void decreaseD() throws InterruptedException {lock.lock();try{while (number == 0){conditionD.await();}number--;System.out.println(Thread.currentThread().getName()+"=>"+number);//通知A来生产conditionA.signal();}catch(Exception e){e.printStackTrace();}finally{lock.unlock();}}
}

2.3、那么锁是什么?如何判断锁的是谁?(8锁现象)

记住一点:在java多线程中,

一个对象一把锁,100个对象100把锁;(synchronized加在方法上锁的是this,即这个对象)

一个类只有一把锁; (synchronized加在静态方法上锁的是类)。

栗子

3、集合类不安全

ArrayList是非线程安全的。在多线程并发的情况会出现问题(比如ConcurrentModificationException并发修改异常),怎么变得安全呢?有一下三种方法:

第三种就是JUC方式,用List list = new CopyOnWriteArrayList();

CopyOnWriteArrayList比Vactor牛在哪?看源码:

  • Vactor的add方法中用的是synchronized,而synchronized是重锁,只要有synchronized的方法它的效率就会比较低。

  • CopyOnWriteArrayList的add方法中用的是lock锁,并且它是通过写入时赋值的方式添加元素,(即先复制一份老数组,把新元素加进来后再把整个数组set回去的方式添加元素)

写入时复制,这是一种 读写分离 的思想,因为由源码可知它是把原数组复制到一个新数组当中,然后把要添加的元素添加到新数组中,最后再把新数组set回去。当然这个过程进行了加锁以保证写的时候只有一个线程,而写的过程中(即添加元素的过程),其他线程就可以并发地读原来旧的数组,这就实现了读写分离。那么此时并发下就不会出错了。

内部是调用了Arrays.copyOf()方法,而Arrays.copyOf()底层又是调用了System.arraycopy(),这个方法底层调用了一个本地方法,是复制了一个数组。

4、Callable接口

因为Thread只接受Runnable,Callable怎么让Thread接受自己从而启动线程呢?

当然只能通过Runnable,那又怎么和Runnable搭上关系呢?

通过Runnable的实现类FutureTask(相当于适配器类)

public class TestCallable {public static void main(String[] args) throws ExcutionException,InterruptedException{// new Thread(new Runnable()).start();// new Thread(new FutureTask<V>()).start();// new Thread(new FutureTask<V>( Callable )).start();myThread thread = new myThread();FutureTask futureTask = new FutureTask(thread)//适配器类,把Callable接口实现类实例包装一下new Thread(futureTask,"A").start();Integer o = (Integer)futureTask.get();//获取返回值,这个方法可能会阻塞System.out.println(o);}
}
//Callable接口实现类
class myThread implements Callable<Integer>{public Integer call(){//返回类型要与接口泛型一致System.out.println("call...");return 1024;//返回值}
}
  • 优点:call方法可以有返回值,call方法可以抛异常,结果有缓存
  • 缺点:调用get()方法获取返回值时,可能会产生阻塞,导致程序执行效率降低。

5、常用的辅助类(必会)

5.1、CountDownLatch(减法计数器)

//减法计数器
//CountDownLatch是一种通用的同步工具
public class CountDownLatch_test {public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);//设置计数for (int i = 0; i <= 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+" Go out");countDownLatch.countDown();//数量-1},String.valueOf(i)).start();}countDownLatch.await();//等待计数归零,再向下执行(计数没归零之前,任何线程都过不去这道门槛。大家一起过这道门)System.out.println("Close Door");}
}

原理:

countDownlatch.countDown(); //数量 -1

countDownlatch.await(); //等待计数归零,然后再向下执行(相当于一个门闩)

每次有线程调用countDown()时 计数 -1,其他线程阻塞谁也无法通过await这道门闩,当计数器为0 ,所有被阻塞的线程释放,门闩打开程序可以向下执行。

5.2、CyclicBarrier(加法计数器)

//允许一组线程全部等待彼此到达共同障碍点的同步辅助。
public class CyclicBarrier_test {public static void main(String[] args) {//集齐7颗龙珠召唤神龙CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("神龙召唤成功!");//只有计数达到7才会执行该操作});for (int i = 1; i <= 7; i++) {final int temp = i;new Thread(()->{System.out.println(Thread.currentThread().getName()+"收集了第"+temp+"颗龙珠");try{cyclicBarrier.await();//等待} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}

5.3、semaphore(信号量)

计数信号量。信号量通常用于限制线程数(限流),信号量维持一组许可证,每个线程必须从信号量获取许可证,再去使用项目,当线程完成该项目后,它将返回到池中,并将许可证返回到信号量允许另一个线程获取该项目。

比如有6辆车(线程),目前只有3个车位(信号量)

/*
比如有6辆车(线程),目前只有3个车位(信号量)
只能同时供3辆车停放,其余3辆等待,走一辆进去一辆,交替停车。
* */
public class Semaphore_test {public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);//设置信号量个数for (int i = 1; i <=6 ; i++) {new Thread(()->{try {semaphore.acquire();//acquire()得到System.out.println(Thread.currentThread().getName()+"抢到车位");TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+"离开车位");} catch (InterruptedException e) {e.printStackTrace();} finally {semaphore.release();//release()释放}},String.valueOf(i)).start();}}
}

6、读写锁(共享锁,排他锁)

/*
* 独占锁(写锁)一次只能被一个线程占有
* 共享锁(读锁)多个线程可以同时占有
* */
public class ReadWriteLock_test {public static void main(String[] args) {MyCacheLock myCacheLock = new MyCacheLock();for (int i = 1; i <= 5 ; i++){//开启5个线程写入final int temp = i;new Thread(()->{myCacheLock.put(temp+"",temp+"");},String.valueOf(i)).start();}for (int i = 1; i <= 5 ; i++){//开启5个线程读取final int temp = i;new Thread(()->{myCacheLock.get(String.valueOf(temp));},String.valueOf(i)).start();}}
}
//资源类
class MyCacheLock{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);map.put(key,value);//写入System.out.println(Thread.currentThread().getName()+"写入OK");}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);Object o = map.get(key);//读取System.out.println(Thread.currentThread().getName()+"读取OK");}catch (Exception e){e.printStackTrace();}finally {readWriteLock.readLock().unlock();//释放读锁}}
}

7、阻塞队列

7.1、对阻塞队列的理解

7.2、什么情况下我们会使用阻塞队列: 多线程并发处理,线程池!

7.3、学会使用队列

添加,移除

四组API

方式 抛出异常 不抛出异常 阻塞等待 超时等待
添加 add() offer() put offer(“a”,2,TimeUnit.SECONDS)
移除 remove() poll take poll(2,TimeUnit.SECONDS))
检测队首元素 element() peek - -

7.4、SynchronousQueue同步队列

没有容量:进去一个元素,必须等待取出来后,才能再往里放一个元素!

put(),take()

/*
* 同步队列和其他BlockingQueue不一样,SynchronousQueue不存过多元素只存一个。
* put进队列了一个元素,必须要take出来,否则不能再put进去。
* */
public class SynchronousQueue_Demo {public static void main(String[] args) {BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列//开两个线程,一个线程存一个线程取。new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" put 1");blockingQueue.put("1");System.out.println(Thread.currentThread().getName()+" put 2");blockingQueue.put("2");System.out.println(Thread.currentThread().getName()+" put 3");blockingQueue.put("3");} catch (InterruptedException e) {e.printStackTrace();}},"T1").start();new Thread(()->{try {TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());TimeUnit.SECONDS.sleep(2);//等待3秒System.out.println(Thread.currentThread().getName()+" take "+blockingQueue.take());} catch (InterruptedException e) {e.printStackTrace();}},"T2").start();}
}

8、线程池(*重点)

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

8.1、池化技术

程序的运行,本质:占用系统的资源!优化资源的使用!=>池化技术

线程池,连接池,内存池,常量池,对象池…等等。因为创建、销毁十分浪费资源,所以有了池化技术。

池化技术:事先准备好一些资源,有人要用,就来我这里拿,用完之后还给我。

8.2、线程池的好处

  1. 降低资源的消耗
  2. 提高响应的速度
  3. 因为线程放在了池子里,方便管理

线程池就是线程复用、可以控制最大并发数

8.1、三大方法

使用 **Executors 工具类**去创建线程池的三大方法:

public class Demo1 {public static void main(String[] args) {//ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程//ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池ExecutorService threadPool = Executors.newCachedThreadPool();//创建一个可伸缩的线程池,遇强则强遇弱则弱try {for (int i = 0; i < 10; i++) {threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+"ok");});}} catch (Exception e) {e.printStackTrace();} finally {//线程池用完(程序结束),要关闭线程池threadPool.shutdown();}}
}

8.2、七大参数

三大方法的源码分析:

public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,//最大约等于21亿60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}//可见三者底层实际调用的都是ThreadPoolExecute()方法
//ThreadPoolExecute()中有7大参数
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;
}

来看看阿里巴巴关于线程池的规范:

比如银行办理业务。假设1,2窗口是开的,3,4, 5窗口是关的。候客区相当于一个阻塞队列。此时,又来三个人办理业务,而候客区又满了,就会触发3,4,5窗口开放营业接客。

这种情况下:1,2就相当于corePoolSize核心线程池大小;候客区相当于阻塞队列;3,4,5相当于maximumPoolSize最大核心线程池大小;如果这时候再来一个人,就会有拒绝策略来处理他。

8.3、手动创建一个线程池

​ 超过8时就会执行拒绝策略,在这里会抛出异常。

8.4、四种拒绝策略(*)

  • AbortPolicy (默认): (银行满了)再有新任务来,不处理,直接抛RejectExecutionException异常阻止系统正常运行。
  • CallerRunsPolicy:“调用者运行的一种机制”。该策略不会抛弃任务,也不会抛出异常,而是将新来的任务回退给调用者,让调用者去处理。
  • DiscaredPolicy:队列满了,该策略默默地丢弃新来的任务,不予任何处理,也不抛出异常。(如果允许任务丢失,这是最好的一种策略。)
  • DiscaredOldestPolicy:队列满了,当新来任务被拒绝添加时,会抛弃任务队列中最旧的任务也就是最先加入队列的任务,再尝试把这个新任务添加进去。也不会抛出异常。

自定义拒绝策略
使用RejectedExecutionHandler接口,重写rejectedExecution方法。

        ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 5, 0L,TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(10),Executors.defaultThreadFactory(), new RejectedExecutionHandler() {@Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { //r:请求执行的任务  executor:当前的线程池//打印丢失的任务System.out.println(r.toString() + " is discard");}});

8.5、 最大核心线程池大小设置多大合适?(调优)

两种情况:IO密集型,CPU密集型

  • CUP密集型

    看电脑是几核cpu,java并发是一个线程占用一个CPU,几核CPU就支持几个线程同时执行,这样能充分发挥cpu性能

    查看CPU核数也很容易:1、任务管理器->性能:可见我的是12个

2、打开计算机管理->设备管理器:

但是每个用户的电脑情况不一样,我们肯定不能把这个值写死,怎么办呢?通过代码获取:

max = Runtime.getRuntime().availableProcessors();//获取CPU的核数
  • IO密集型

    IO十分耗时又占用资源的!程序中有几个IO任务就相当于有几个大型任务。

    所以设置最大核心线程池大小的时候,我们就要大于大型任务的个数,以保证有多余的线程去执行其他的任务。

    比如我们程序中有 15 IO任务,那 最大核心线程池大小 > 15

9、四大函数式接口(必需掌握)

新时代java程序员必会:lambda表达式、链式编程、函数式接口、Steam流式计算

9.1、什么是函数式接口?(只有一个方法的接口)

例如比较典型的就是Runnable接口

@FunctionalInterface
public interface Runnable {public abstract void run();
}
//java中函数式接口超级多FunctionalInterface
//它能简化编程模型,在新版本的框架底层大量应用。
//forEach(消费者类型的函数式接口)

9.2、Function 函数式接口

/*
* Function 函数接口,有一个输入参数,返回类型是第二个泛型参数
* */
public class Function_test {public static void main(String[] args) {Function<String,String> function = new Function<String, String>() {//匿名内部类方式实现@Overridepublic String apply(String s) {return s;}};System.out.println(function.apply("abc"));//只要是函数式接口都可以用lambda表达式简化Function<String,String> function2 = (s)->{return s;};System.out.println(function2.apply("abc"));}
}

/*
* Predicate 断定接口,有一个输入参数,返回结果是布尔值
* */
public class Function_test {public static void main(String[] args) {Predicate<String> predicate = new Predicate<String>() {//匿名内部类方式实现@Overridepublic boolean test(String s) {return s.isEmpty();}};System.out.println(predicate.test(""));//True//只要是函数式接口都可以用lambda表达式简化Predicate<String> predicate2 = (s)->{return s.isEmpty();};System.out.println(function2.test(""));}
}

/*
* Consumer 消费型接口,有一个输入参数,无返回类型;
* */Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}};consumer.accept("abcdefg");//只要是函数式接口都可以用lambda表达式简化Consumer<String> consumer2 = (s)->{System.out.println(s);};consumer2.accept("abcdefg");

/*
* Supplier 供给型接口,无输入参数,返回类型指定泛型;
* */Supplier<Integer> supplier = new Supplier<Integer>() {@Overridepublic Integer get() {return 1024;}};System.out.println(supplier.get());//只要是函数式接口都可以用lambda表达式简化Supplier<Integer> supplier2 = ()->{return 1024;};System.out.println(supplier2.get());

可见消费型接口,只消费不返回;供给型接口,只返回不消费。

函数式接口哪里用得到?Stream流式计算!

10、Stream流式计算

10.1、什么是流式计算?

大数据的计算模式主要分为批量计算(batch computing)、流式计算(stream computing)、交互计算(interactive computing)、图计算(graph computing)等。其中,流式计算和批量计算是两种主要的大数据计算模式,分别适用于不同的大数据应用场景。

流数据(或数据流)是指在时间分布和数量上无限的一系列动态数据集合体,数据的价值随着时间的流逝而降低,因此必须实时计算给出秒级响应。流式计算,顾名思义,就是对数据流进行处理,是实时计算。批量计算则统一收集数据,存储到数据库中,然后对数据进行批量处理的数据计算方式。主要体现在以下几个方面:

1、数据时效性不同:流式计算实时、低延迟, 批量计算非实时、高延迟。

2、数据特征不同:流式计算的数据一般是动态的、没有边界的,而批处理的数据一般则是静态数据。

3、应用场景不同:流式计算应用在实时场景,时效性要求比较高的场景,如实时推荐、业务监控…批量计算一般说批处理,应用在实时性要求不高、离线计算的场景下,数据分析、离线报表等。

4、运行方式不同,流式计算的任务持续进行的,批量计算的任务则一次性完成。

大数据:存储+计算

存储:集合、数据库等

计算:交给流来操作!

/*
* 题目要求:一分钟完成此题,只能用一行代码实现!
* 现有5个用户!筛选
* 1、ID必须是偶数!
* 2、年龄必须大于23!
* 3、用户名转为大写字母!
* 4、用户名字母倒着排序!
* 5、只输出一个用户!
* */
public class Demo1 {public static void main(String[] args) {User u1 = new User(1,"zhangsan",21);User u2 = new User(2,"lisi",23);User u3 = new User(3,"wanger",26);User u4 = new User(4,"maliu",25);User u5 = new User(5,"zhaoqi",35);User u6 = new User(6,"zhaoqi",29);//集合就是存储元素的List<User> list = Arrays.asList(u1,u2,u3,u4,u5,u6);//交给Stream流来计算//一个例子涵盖了:lambda表达式、函数式接口、链式编程、Steam流式计算list.stream().filter((u)->{return u.getId()%2==0;})//ID必须是偶数!.filter((u)->{return u.getAge() > 23;})//年龄必须大于23!.map((u)->{return u.getName().toUpperCase();})//用户名转为大写字母!.sorted((uu1,uu2)->{return uu2.compareTo(uu1);})//用户名字母倒着排序!.limit(1)//只输出一个用户!.forEach(System.out::println);}
}
//这几个方法的参数分别是哪种类型的函数式接口?
//filter(Predicate断定型的函数式接口),map(Function函数型接口),sorted(Compartor比较型接口),forEach(消费者类型的函数式接口)

11、ForkJoin(分支合并)

11.1、什么是ForkJoin?

并行执行任务!提高效率。必须在大数据量下才会使用!

把大任务拆分为多个子任务。 把子任务的结果合并成最初问题的结果(分治策略)

11.2、ForkJoin的特点:工作窃取

空闲线程主动去执行高负载线程的任务。从而提高效率。

怎么提高效率的?它内部实际上维护的是双端队列。如下图A可以从上面取,B可以从下面取。两端可以同时取。

11.3、怎么使用ForkJoin呢?

ForkJoinPool.execute() 或者 ForkJoinPool.submit()

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;
import java.util.stream.LongStream;
/*
* 计算1~10亿的求和任务
*初级(for循环) ,中级(ForkJoin),高级(Stream并行流)
* 如何使用ForkJoin?
* 1、ForkJoinPool,用它来执行.
* 2、计算任务 ForkJoinPool.execute(ForkJoinTask task)
* 3、计算类要继承ForkJoinTask
* */
public class ForkJoin_Demo {public static void main(String[] args) throws ExecutionException, InterruptedException {test1();test2();test3();}//普通程序员public static void test1(){Long sum = 0L;long begin = System.currentTimeMillis();for (Long i = 1L; i <= 10_0000_0000L; i++) {sum += i;}long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}//中级程序员使用ForkJoinpublic static void test2() throws ExecutionException, InterruptedException {long begin = System.currentTimeMillis();ForkJoinPool forkJoinPool = new ForkJoinPool();ForkJoin_task task = new ForkJoin_task(0L, 10_0000_0000L);//创建一个ForkTask任务ForkJoinTask<Long> submit = forkJoinPool.submit(task);Long sum = submit.get();long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}//高级程序员Stream并行流public static void test3(){long begin = System.currentTimeMillis();//一行代码搞定;   rangeClosed是(]左开右闭;parallel并行;reduce累加求和;:: JDK8中的关键字 方法引用Long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);long end = System.currentTimeMillis();System.out.println("sum= "+sum+" 时间: "+(end-begin));}
}//定义一个ForkJoinTask任务
class ForkJoin_task extends RecursiveTask<Long> {private Long start;private Long end;private Long boundary = 10000L;//临界值,超过临界值就分成两个任务public ForkJoin_task(Long start, Long end) {this.start = start;this.end = end;}//计算方法@Overrideprotected Long compute() {if ((end-start) < boundary){//正常使用for循环计算Long sum = 0L;for (Long i = start; i <= end; i++) {sum += i;}return sum;}else{//使用ForkJoin分支合并计算(递归)Long middle = (start+end)/2;//中间值//拆分成两个子任务ForkJoin_task task1 = new ForkJoin_task(start, middle);task1.fork();//把拆分的子任务压入线程的工作任务队列(这个队列是个双端队列)ForkJoin_task task2 = new ForkJoin_task(middle+1, end);task2.fork();//把拆分的子任务压入线程的工作任务队列(这个队列是个双端队列)return task1.join() + task2.join();//合并结果}}
}

12、异步回调

13、JMM

什么是JMM?

8种操作

14、Volatile(轻量级的同步机制)

  • 保证可见性
  • 禁止指令重排(保证有序性)
  • 不保证原子性

深入理解volatile关键字:https://blog.csdn.net/weixin_43587472/article/details/106342353

我们知道synchronized和Lock都能保证原子性,而volatile不能保证原子性。但是有没有什么方法可以既不使用synchronized和Lock

又能保证原子性呢?

使用原子类,解决原子性问题

这些类的底层都是调用的本地方法和操作系统之间挂钩,所以效率很高效!在内存中修改值!

15、彻底玩转单例模式

饿汉式;懒汉式

//饿汉式单例
//浪费空间,无论你用不用类一加载加创建好对象了。
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(){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyMan();}}return lazyMan;}

DCL懒汉式:双重检测锁模式的懒汉单例

//懒汉式单例
//需要用到的时候在实例化对象
public class LazyMan {private LazyMan(){System.out.println(Thread.currentThread().getName());}//私有化构造函数private volatile static LazyMan lazyMan;//私有化实例对象//双重检测锁模式的懒汉单例 DCL双重锁定检查(Double Check Lock)懒汉式public static LazyMan getInstance(){if (lazyMan == null){synchronized (LazyMan.class){if (lazyMan == null){lazyMan = new LazyMan();//不是原子性操作。/** 这行代码会经过三个步骤:* 1,分配内存空间* 2,执行构造方法初始化对象* 3,把这个对象指向这个空间** 如果指令按照顺序执行倒也无妨,但JVM为了优化指令,提高程序运行效率,允许指令重排序。* 如此,在程序真正运行时以上指令执行顺序可能是这样的:* (a)给instance实例分配内存;* (b)将instance对象指向分配的内存空间;* (c)初始化instance的构造器;* 这时候,当线程一执行(b)完毕,在执行(c)之前,线程二走到第一个if(此时还没开始抢锁),这时候instance判断为非空,* 此时线程二直接来到return instance语句,拿走instance然后使用,接着就顺理成章地报错(对象尚未初始化)。* 具体来说就是synchronized虽然保证了线程的原子性(即synchronized块中的语句要么全部执行,要么一条也不执行),* 但单条语句编译后形成的指令并不是一个原子操作(即可能该条语句的部分指令未得到执行,就被切换到另一个线程了)。* 根据以上分析可知,解决这个问题的方法是:禁止指令重排序优化,即使用volatile变量。* */}}}return lazyMan;}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{LazyMan.getInstance();}).start();}}
}

静态内部类方式

//静态内部类方式
public class Holder {private Holder(){System.out.println(Thread.currentThread().getName());}public static Holder getInstance(){return InnerClass.HOLDER;}public static class InnerClass{private static final Holder HOLDER = new Holder();//创建实例}public static void main(String[] args) {for (int i = 0; i < 10; i++) {new Thread(()->{Holder.getInstance();},String.valueOf(i)).start();}}
}

枚举方式

//枚举本身就是一个单例类
public enum  EnumSingle {INSTANCE;public static EnumSingle getInstance(){return INSTANCE;}
}

单例不安全,因为反射可以破坏私有

LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);//打破私有
LazyMan instance2 = declaredConstructor.newInstance();//反射方式构造实例对象System.out.println(instance1);
System.out.println(instance2);

16、深入理解CAS

16.1、什么是CAS ?

CAS:Compare And Swap,即比较再交换。是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization).

jdk5增加了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronized同步锁的一种乐观锁。JDK 5之前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。

对CAS的理解,CAS是一种无锁算法,CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

CAS比较与交换的伪代码可以表示为:

do{

备份旧数据;

基于旧数据构造新数据;

}while(!CAS( 内存地址,备份的旧数据,新数据 ))

在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的 JVM 支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。

由此可见,AtomicInteger.incrementAndGet的实现用了乐观锁技术,调用了类sun.misc.Unsafe库里面的 CAS算法,用CPU指令来实现无锁自增。所以,AtomicInteger.incrementAndGet的自增比用synchronized的锁效率倍增。

Java中AtomicInteger中incrementAndGet和getAndIncrement的区别

//从源码分析
public final int getAndIncrement() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return current;}
}public final int incrementAndGet() {for (;;) {int current = get();int next = current + 1;if (compareAndSet(current, next))return next;}
}

通过代码可以看出:

getAndIncrement返回的是当前值;
incrementAndGet返回的是加1后的值。

16.2、Unsafe类

我们知道AtomicInteger有个getAndIncrement方法。该方法底层调用的是Unsafe类的getAndAddInt方法,而getAndAddInt内部是一个自旋锁

CAS:比较当前工作内存中的值和主存中的值,如果这个值是期望值,则执行操作!如果不是会一直循环!

缺点:

1,循环会耗时

2,一次性只能保证一个共享变量的原子性

3,会存在ABA问题

16.3、ABA问题(狸猫换太子)

什么是ABA问题?

假设原来是A,先修改成B,再修改回成A

假如有两个线程A,B。假如A,B都读到了a=1;这时候B先对这个a进行了CAS操作,第一次cas(1,3)第二次cas(3,1)。对于线程A来说,a表面上并没有变,而实际上线程B已经对a做了手脚。

像这样的现象是我们不希望的,我们希望谁动了这个线程一定要告诉我。如何解决这个问题呢?原子引用。

CAS可以引发ABA问题,怎么解决?——>原子引用AtomicStampedReference)在比较并交换的过程加上时间戳。

17、原子引用

public class ASR {public static void main(String[] args) {AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference(1,1);new Thread(()->{int stamp = atomicStampedReference.getStamp();//获取版本号(时间戳)System.out.println("a1=>" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}//执行成功会输出true否则false//执行后时间戳加1System.out.println(atomicStampedReference.compareAndSet(1, 2,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("a2=>" + atomicStampedReference.getStamp());System.out.println(atomicStampedReference.compareAndSet(2, 1,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("a3=>" + atomicStampedReference.getStamp());},"a").start();new Thread(()->{int stamp = atomicStampedReference.getStamp();System.out.println("b1=>" + stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomicStampedReference.compareAndSet(1, 6,atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));System.out.println("b1=>" + atomicStampedReference.getStamp());},"b").start();}
}

18、各种锁的理解

18.1、乐观锁&悲观锁

18.1.1、悲观锁(互斥同步锁)

顾名思义它是悲观的,它总是假设最坏的情况,当每次要对数据进行修改的时候都认为会发成并发冲突,别人会修改,所以为了避免冲突同时被其他人修改,每次拿数据的时候都会上锁以防止并发。这样别人想拿到这个数据就会阻塞直到释放锁后拿到这个锁。JAVA中synchronized和Lock都是采用的悲观锁。(具有强烈的独占和排他特性)

18.1.2、悲观锁主要分为共享锁和排他锁
  • 共享锁 【Shared lock】又称为读锁,简称S锁。顾名思义,共享锁就是多个线程或事务对于同一数据可以共享一把锁,(相当于对于同一把门,它拥有多个钥匙一样)都能访问到数据,但是只能读不能修改和删除。一个线程或事务对数据A加上锁后,其他线程或事务只能对数据A加共享锁,不能加排它锁。共享锁下,其他线程可以并发读取查询数据,不能修改、增加、删除数据(资源共享)

  • 排他锁【Exclusive lock】又称为写锁或独占锁,简称X锁。顾名思义,排他锁就是不能与其他锁并存,如果线程T获取了数据A排他锁,线程T就独占数据A,只允许线程T对数据A进行读取和修改,其他线程不能再获取数据A的任何锁,包括共享锁和排他锁,直到线程T释放该锁。(独占资源)

18.1.3、悲观锁有哪些劣势?
  1. 阻塞、唤醒。因为阻塞唤醒会涉及到排队和时间,从而导致性能下降。(性能劣势)
  2. 永久阻塞或死锁。比如现在持有锁的线程被永久阻塞了,比如说是无限循环,这会导致这个持有锁的线程永远不会释放锁
  3. 优先级。阻塞是优先级越高,持有锁的优先级就越低。从而导致优先级翻转。

悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。

18.1.3、乐观锁(非互斥同步锁)

顾名思义它是乐观的,它采取了更加宽松的加锁机制。它总是假设最好的情况。每次去拿数据的时候都认为别人不会修改,所以不会上锁。但是更新的时候会判断一下在此期间别人有没有去更新这个数据。可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量。在Java中java.util.concurrent.atomic包下的原子类就是使用了乐观锁的一种实现方式CAS算法实现的。

18.1.4、乐观锁悲观锁对比
  • 开销对比:

    悲观锁的原始开销大于乐观锁。

  • 适用场景:

    乐观锁,并发写入少,大量读操作;

    悲观锁,并发写入多的情况,临界区代码复杂,竞争激烈等;

18.2、公平锁、非公平锁

公平锁:是指多个线程按照申请锁的顺序来获取锁,先来后到,不能插队!非常公平。

非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。非常不公平,可以插队!(java默认是非公平锁,因为实际上非公平锁相对公平些)

Lock lock = new ReentrantLock();//默认非公平锁
Lock lock = new ReentrantLock(true);//改成公平锁

18.3、可重入锁

可重入锁(递归锁):**可重入锁指的是可以重复调用、可以递归调用的锁,并且不发生死锁。**在外层使用锁之后,在内层仍然可以使用,获取到外层锁自动获取到内层锁,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。ReentrantLock和synchronized都是可重入锁。

synchronized版:

Lock版:

补充:什么是AQS?

AQS:AbstractQueuedSynchronizer抽象队列式同步器。是除了java自带的synchronized关键字之外的锁机制。AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包

AQS的核心思想:如果被请求的共享资源空闲,则将当前请求线程设为有效工作线程,并将共享资源设为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制。这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列,虚拟的双向队列即不存在队列实例,仅存在节点之间的关联关系。

AQS是将每一条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node),来实现锁的分配。

用大白话来说,AQS就是基于CLH队列,用volatile修饰共享变量state,线程通过CAS去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒。注意:AQS是自旋锁:在等待唤醒的时候,经常会使用自旋(while(!cas()))的方式,不停地尝试获取锁,直到被其他线程获取成功。

实现了AQS的锁有:自旋锁、互斥锁、读锁写锁、条件产量、信号量、栅栏都是AQS的衍生物

18.4、自旋锁

什么是自旋锁:

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。

自旋锁与互斥锁的异同点:

相同点:其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。

不同点:但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态(即会阻塞)。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,”自旋”一词就是因此而得名。

自定义一个锁

public class ZiXuanSuo {AtomicReference<Thread> atomicReference = new AtomicReference<>();//加锁public void myLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==>"+"myLock");//自旋锁while (!atomicReference.compareAndSet(null,thread));}//解锁public  void myUnLock(){Thread thread = Thread.currentThread();System.out.println(Thread.currentThread().getName()+"==>"+"myUnLock");atomicReference.compareAndSet(thread,null);}
}

测试

public class Test {public static void main(String[] args) {ZiXuanSuo lock = new ZiXuanSuo();new Thread(()->{lock.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();} finally {lock.myUnLock();}},"T1").start();try {TimeUnit.SECONDS.sleep(1);//保证T1先执行完} catch (InterruptedException e) {e.printStackTrace();}new Thread(()->{lock.myLock();try {TimeUnit.SECONDS.sleep(2);} catch (Exception e) {e.printStackTrace();} finally {lock.myUnLock();}},"T2").start();}
}

18.5、死锁

死锁是什么?

各自占有自己的锁,又试图去获取对方的锁,这种现象就会造成死锁。

public class TestDeadLock {public static void main(String[] args) {String lockA = "lockA";String lockB = "lockB";new Thread(new myThread(lockA,lockB),"T1").start();new Thread(new myThread(lockB,lockA),"T2").start();}
}
class myThread implements Runnable{private String lock1;private String lock2;public myThread(String lockA, String lockB) {this.lock1 = lock1;this.lock2 = lock2;}@Overridepublic void run() {//死锁就是嵌套锁synchronized(lock1){System.out.println(Thread.currentThread().getName()+"lock:"+lock1+"=>get"+lock2);try{//让线程休眠一下保证线程都各自先拿到一把锁TimeUnit.SECONDS.sleep(2);}catch (InterruptedException e){e.printStackTrace();}synchronized (lock2){System.out.println(Thread.currentThread().getName()+"lock:"+lock2+"=>get"+lock1);}}}
}

解决死锁问题

在diea终端

  1. 使用jps -l定位进程号

  1. 使用jstack 进程号查看堆栈信息

面试或者工作中排查问题

  1. 查看日志
  2. 查看堆栈信息

Java多线程之JUC相关推荐

  1. Java多线程之JUC包:Semaphore源码学习笔记

    若有不正之处请多多谅解,并欢迎批评指正. 请尊重作者劳动成果,转载请标明原文链接: http://www.cnblogs.com/go2sea/p/5625536.html Semaphore是JUC ...

  2. JAVA多线程之wait/notify

    本文主要学习JAVA多线程中的 wait()方法 与 notify()/notifyAll()方法的用法. ①wait() 与 notify/notifyAll 方法必须在同步代码块中使用 ②wait ...

  3. Java多线程之Callable、Future和FutureTask

    Java多线程之Callable接口 自己想总结一下的,看到一篇总结的更好的博客,就转载了,突然感觉真轻松,哈哈哈哈 文章转载于:Matrix海子:Java并发编程:Callable.Future和F ...

  4. Java多线程之Synchronized和Lock的区别

    Java多线程之Synchronized和Lock的区别 目录: 原始构成 使用方法 等待是否可以中断 加锁是否公平 锁绑定多个条件Condition 小结:Lock相比较Synchronized的优 ...

  5. Java多线程之CAS缺点

    Java多线程之CAS缺点 目录: 循环时间开销很大 只能保证一个共享变量的原子操作 引来ABA问题及解决方案(重点) 1. 循环时间开销很大 通过看源码,我们发现有个do while,如果CAS失败 ...

  6. Java多线程之CAS深入解析

    Java多线程之CAS深入解析 目录: CAS是什么 CAS底层原理Unsafe深入解析 CAS缺点 引子:蚂蚁花呗一面:讲一讲AtomicInteger,为什么要用CAS而不是synchronize ...

  7. Java多线程之volatile详解

    Java多线程之volatile详解 目录: 什么是volatile? JMM内存模型之可见性 volatile三大特性之一:保证可见性 volatile三大特性之二:不保证原子性 volatile三 ...

  8. Java多线程之Semaphore用法

    Java多线程之Semaphore用法 本文目录: Semaphore基本概念 Semaphore使用案例:3个停车位,6辆车去抢,走一辆,抢一个停车位. 1. Semaphore基本概念 在信号量上 ...

  9. Java多线程之CyclicBarrier用法

    Java多线程之CyclicBarrier用法 本文目录 CyclicBarrier的基本概念 CyclicBarrier的案例:集齐7颗龙珠就可以召唤神龙 1. CyclicBarrier的基本概念 ...

最新文章

  1. java文件用editplus乱码,EditPlus设置编码后,编译时仍然出现乱码
  2. 面试题整理5 顺时针打印矩阵
  3. activemq启动wrapper stopped
  4. 使用mybatis-generator自动生成代码的方法介绍及踩坑
  5. linux cron源码下载,LINUX计划任务管理_AT与crontab
  6. TCP/IP 三次握手
  7. 阿里的easyexcel
  8. java 权重 分配_一种按权重分配的Java算法
  9. ExtremeComponents源码解析(一)
  10. ofd文件怎么打开?怎么转换成pdf格式发票?ofd文件打开教程
  11. catia相合约束怎么反向_洛丽塔服饰怎么画?教你如何画动漫人物的衣服?
  12. 亲密关系“恋爱心理学”
  13. 逆水寒服务器什么时候能维护好,逆水寒更新了什么内容 8月23日官方维护内容及时间一览...
  14. 论文阅读笔记--Federated Continual Learning with Weighted Inter-client Transfer
  15. three.js判断两个向量(角度)夹角误差是否小于某个值
  16. 76篇 ICCV 2019 论文实现代码
  17. 什么是REST ful?
  18. python列表替换元素_24_Pandas.DataFrame,Series元素值的替换(replace)
  19. 常见的服务器操作系统和工作站操作系统
  20. [笔记]ESP32 踩坑 任务看门狗超时的问题---Task watchdog got triggered

热门文章

  1. 光谱相似指数Spectral Similarity Index(SSI)计算
  2. python calu()函数_python面向对象
  3. MATLAB-005 无法实例化为对象?全网找不到解决方案?
  4. 如何搭建真正能洞悉数据的领导驾驶舱
  5. 网页设计Hero Image
  6. 51单片机之外部中断拙见
  7. SSM+公寓管理系统 毕业设计-附源码171958
  8. LM2576在嵌入式系统中的应用
  9. 中考计算机加试及格多少分,中考总分多少 中考各科分数是多少
  10. 学习3ds max—做自行车车轮