Juc全网最全学习笔记【遇见狂神说】

B 站学习视频遇见狂神说《JUC并发编程最新版通俗易懂》

1、什么是JUC

源码 + 官方文档 面试高频问!
Juc全网最全学习笔记【遇见狂神说】_第1张图片

  • java.util 工具包、包、分类
  • 业务:普通的线程代码 Thread
  • Runnable 没有返回值、效率相比入 Callable 相对较低!

Juc全网最全学习笔记【遇见狂神说】_第2张图片

2、线程和进程

线程、进程

  • 进程:一个程序,QQ.exe Music.exe 程序的集合;一个进程往往可以包含多个线程,至少包含一个!

    • Java默认有2个线程,main和gc垃圾回收
  • 线程:开了一个进程 Typora,写字,自动保存(线程负责的)

  • 对于Java而言:Thread、Runnable、Callable

Java无法开启线程

    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
	//这是一个C++底层,Java是没有权限操作底层硬件的
    private native void start0();

并发、并行

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

  • CPU 一核 ,模拟出来多条线程,天下武功,唯快不破,快速交替并行(多个人一起行走)

并行:多个人一起走

  • CPU多核,多个线程可以同时执行,线程池
public class Test1{
    public static void main(String[] args){
        //获取CPU的核数
        //CPU密集型,IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

并发编程的本质:充分利用CPU的资源

线程有几个状态

public enum State {

   	//运行
       NEW,

   	//运行
       RUNNABLE,

   	//阻塞
       BLOCKED,

   	//等待
       WAITING,

   	//超时等待
       TIMED_WAITING,


   	//终止
       TERMINATED;
   }

wait/sleep 区别

1、 来自不同的类
wait => Object

sleep => Thread

一般企业中使用的休眠是:

TimeUnit.DAYS.sleep(1);//休眠一天
TimeUnit.SECONDS.sleep(1);//休眠1s

2、 关于锁的释放

wait 会释放锁

sleep 睡觉了,不会释放!
3、 使用的范围是不同的
wait必须使用在代码块中
.
sleep 可以再任何地方睡

4、是否需要捕获异常

wait 不需要捕获异常

sleep 必须要捕获异常

3、Lock锁(重点)

传统 Synchronized

/**
* 真正的多线程开发
* 线程就是一个单独的资源类,没有任何的附属操作!
*/
public class SaleTicketDemo01 {
   public static void main(String[] args) {
       //多线程操作
       //并发:多线程操作同一个资源类,把资源类丢入线程
       Ticket ticket = new Ticket();

       //@FunctionalInterface 函数式接口 jdk1.8之后 lambda表达式
       new Thread(()->{
           for(int i=0;i<40;i++){
               ticket.sale();
           }
       },"A").start();
       new Thread(()->{
           for(int i=0;i<40;i++){
               ticket.sale();
           }
       },"B").start();
       new Thread(()->{
           for(int i=0;i<40;i++){
               ticket.sale();
           }
       },"C").start();
   }
}
//资源类
//属性+方法
//oop
class Ticket{
   private int number=50;


   //卖票的方式
   // synchronized 本质:队列,锁
   public synchronized void sale(){
       if(number>0){
           System.out.println(Thread.currentThread().getName()+" 卖出了第"+number+" 张票,剩余:"+number+" 张票");
           number--;
       }
   }
}

Lock 接口

Juc全网最全学习笔记【遇见狂神说】_第3张图片
Juc全网最全学习笔记【遇见狂神说】_第4张图片
Juc全网最全学习笔记【遇见狂神说】_第5张图片

公平锁:十分公平:可以先来后到

非公平锁:十分不公平:可以插队 (默认为非公平锁

Synchronized 和 Lock 区别

  • ​ Synchronized 内置的Java关键字, Lock 是一个Java类
  • ​ Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁
  • ​ Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  • ​ Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去;
  • ​ Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以自己设置);
  • ​ Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

锁是什么,如何判断锁的是谁!

4、生产者和消费者问题

面试的:单例模式、排序算法、生产者和消费者、死锁

生产者和消费者问题 Synchronized 版

public class A {
    public static void main(String[] args) {
        Data data = new Data();

        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        },"A").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"B").start();
    }
}
class Data{
    //数字  资源类
    private int number = 0;

    //+1
    public synchronized void increment() throws InterruptedException {
        if(number!=0){
            //等待操作
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程 我+1完毕了
        this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
        if(number==0){
            //等待操作
            this.wait();
        }
        number--;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程  我-1完毕了
        this.notifyAll();
    }

}

问题存在,A B C D 4 个线程! 虚假唤醒

Juc全网最全学习笔记【遇见狂神说】_第6张图片

解决方案:if 改为 while 判断

/**
* 判断等待,业务,通知
*/
class Data2{//资源类  数字
   private int number = 0;

   Lock lock = new ReentrantLock();
   Condition condition = lock.newCondition();

   //condition.await();//等待
   //condition.signalAll();//唤醒全部

   //+1
   public void increment() throws InterruptedException {
       try {
           lock.lock();
           //业务代码
           while (number !=0){
               //等待操作
               condition.await();
           }
           number++;
           System.out.println(Thread.currentThread().getName()+"=>"+number);
           //通知其他线程,我+1完毕了
           condition.signalAll();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }

   }
   //-1
   public void decrement() throws InterruptedException {
       try {
           lock.lock();
           //业务代码
           while (number == 0){
               //等待操作
               condition.await();
           }
           number --;
           System.out.println(Thread.currentThread().getName()+"=>"+number);
           //通知其他线程,我-1完毕了
           condition.signalAll();
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           lock.unlock();
       }

   }
}

JUC版的生产者和消费者问题

通过Lock 找到 Condition

代码实现:

/**
 * 判断等待,业务,通知
 */
class Data2{//资源类  数字
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    //condition.await();//等待
    //condition.signalAll();//唤醒全部

    //+1
    public void increment() throws InterruptedException {
        try {
            lock.lock();
            //业务代码
            while (number !=0){
                //等待操作
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,我+1完毕了
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
    //-1
    public void decrement() throws InterruptedException {
        try {
            lock.lock();
            //业务代码
            while (number == 0){
                //等待操作
                condition.await();
            }
            number --;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            //通知其他线程,我-1完毕了
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }
}

5、8锁现象

如何判断锁的是谁!

锁会锁住:对象、Class

深刻理解我们的锁:

问题一:标准情况下,两个线程先打印 发短信 还是 打电话

Juc全网最全学习笔记【遇见狂神说】_第7张图片

结果是:

Juc全网最全学习笔记【遇见狂神说】_第8张图片

那么问题来了,为什么是这种顺序来打印的,是按顺序执行的吗?答案显然是错误的!

问题二:我们让打短信延迟4s

Juc全网最全学习笔记【遇见狂神说】_第9张图片

现在结果是什么情况呢?

结果依然是:发短信 打电话!

原因: synchronized 锁的对象是方法的调用者!两个方法使用的是同一把锁,谁先拿到,谁先执行!

问题三:添加一个普通方法,结果先执行哪一个呢?

Juc全网最全学习笔记【遇见狂神说】_第10张图片

结果:先执行hello()在打印发短信!原因是hello()是一个普通方法,并不是同步方法,不受Synchronized锁的影响,如果把发短信里的TimeUnit.SECONDS.sleep(4)去掉,那么就会顺序执行,限制性发短信再执行hello()。原因方法sendSms()hello()两者并不会产生影响,在sendSms()中加入延时时,在线程开始时,就会等待4s再执行,去掉之后,两个方法并不会有谁等待谁的关系,就会按照顺序进行执行!(个人理解)

问题四:如果使用两个对象,分别调用发短信 和 打电话 那么顺序是什么呢?

Juc全网最全学习笔记【遇见狂神说】_第11张图片

结果: 打电话 发短信。

原因: 在发短信中 延时了4s,再加上Synchronized锁的对象是方法的调用者,如果有两把锁,就会根据执行时间来决定打印顺序!

问题 5、6 如果在方法上加上static变成静态方法!结果又该如何?

Juc全网最全学习笔记【遇见狂神说】_第12张图片

结果: 发短信 打电话

原因: 对于static静态方法来说,对于整个类Class来说只有一份,对于不同的对象使用的是同一份方法,相当于这个方法是属于这个类的,如果静态方法static使用了Synchronized锁定,那么这个Synchronized锁会锁住整个对象!不管多少个对象,对于静态的锁都只有一把锁,谁先拿到这个锁谁先执行!其他进程都需要等待!

问题七:如果把两个方法设置为一个静态方法、一个同步方法,结果又将如何?

Juc全网最全学习笔记【遇见狂神说】_第13张图片

结果: 打电话 发短信

原因: 因为一个锁的是Class类模板,一个锁的是对象的调用者,call()不需要等待发短信,直接运行!

问题八:一个静态方法、一个同步方法,使用两个对象进行分别调用,顺序是什么呢?

Juc全网最全学习笔记【遇见狂神说】_第14张图片

结果: 打电话 发短信

原因: 因为两个对象,一样的原因,两把锁锁的不是同一个东西,所以后面的第二个对象不需要等待第一个对象去执行!

6、集合类不安全

List不安全

//java.util.ConcurrentModificationException 并发修改异常
public class ListTest{
    public static void main(String[] args){
        List<String> list = new ArrayList<>();
        for(int i = 1;i<=30;++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

执行结果:
Juc全网最全学习笔记【遇见狂神说】_第15张图片

结果:ArrayList在并发情况下是不安全的!

解决方案:

  • 切换成Vector就是线程安全的!
public class ListTest{
    public static void main(String[] args){
        List<String> list = new Vector<>();
        for(int i = 1;i<=30;++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

Juc全网最全学习笔记【遇见狂神说】_第16张图片

  • 使用Collections.synchronizedList(new ArrayList<>());
public class ListTest{
    public static void main(String[] args){
        List<String> list = Collections.synchronizedList(new ArrayList<>());
        for(int i = 1;i<=30;++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}
  • 使用JUC中的包:List list = new CopyOnWriteArrayList<>();
public class ListTest{
    public static void main(String[] args){
        List<String> list = new CopyOnWriteArrayList<>();
        for(int i = 1;i<=30;++i){
            new Thread(()->{
                list.add(UUID.randomUUID().toString().substring(0,5));
            },String.valueOf(i)).start();
        }
    }
}

CopyOnWriteArrayList:写入时复制!COW计算机程序设计领域的一种优化策略

多个线程调用的时候,list,读取的时候,固定的,写入(覆盖);在写入的时候避免覆盖,造成数据的问题!

CopyOnWriteArrayList 比 Vector 厉害在哪里?

  • VectoraddElement()方法使用的是Synchronized,一般使用Synchronized效率较低

Juc全网最全学习笔记【遇见狂神说】_第17张图片

  • CopyOnWriteArrayListadd方法使用的是lock
    Juc全网最全学习笔记【遇见狂神说】_第18张图片

Set不安全

Juc全网最全学习笔记【遇见狂神说】_第19张图片

List属于同级,由于List在并发情况下不安全,则Set也是不安全的

解决方案:

  • 使用Collections工具类的synchronized包装的Set
  • 使用CopyOnWriteArraySet写入复制的JUC解决方案
//java.util.ConcurrentModificationException
public class SetTest {
    public static void main(String[] args) {

        Set<String> set1 = new HashSet<>();
        //Set set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
        for (int i = 1; i <= 100; i++) {
            new Thread(()->{
                set.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(set);
            },String.valueOf(i)).start();
        }
    }
}

HashSet底层是什么?

Juc全网最全学习笔记【遇见狂神说】_第20张图片

通过底层来看,HashSet的底层其实就是一个HashMap

public HashSet() {
   map = new HashMap<>();
}

//add 本质其实就是一个map的key,map的key是无法重复的,所以使用的就是map存储
//hashSet就是使用了hashmap key不能重复的原理
public boolean add(E e) {
        return map.put(e, PRESENT)==null;
}
//PRESENT是什么? 是一个常量  不会改变的常量  无用的占位
private static final Object PRESENT = new Object();

Map不安全

//map是这样用的吗? 不是,工作中不使用HashMap
//默认等价于什么? new HashMap<>(16,0.75);
//Map<String, String> map = new HashMap<>();

HashMap默认加载因子是0.75,默认的初始容量是16

Juc全网最全学习笔记【遇见狂神说】_第21张图片

同样的HashMap基础类也存在并发修改异常

public class HashMapTest {

    public static void main(String[] args) {
        //map是这样用的吗? 不是,工作中不使用HashMap
        //默认等价于什么? new HashMap<>(16,0.75);
        //Map map = new HashMap<>();

        // Collections.synchronizedMap()
        Map<String, String> map = new ConcurrentHashMap<>();

        for (int i = 1; i <= 30; i++) {
            new Thread(()->{
                map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,5));
                System.out.println(map);
            },String.valueOf(i)).start();
        }
    }
}

解决方案:

  • 使用Collections.synchronizedMap(new HashMap<>());处理
  • 使用new ConcurrentHashMap<>()进行并发处理

7、Callable(简单)

Juc全网最全学习笔记【遇见狂神说】_第22张图片

  • 可以有返回值
  • 可以抛出异常
  • 方法不同 run()/call()

代码测试

public class CallableTest{
    public static void main(String[] args){
        for(int i = 1;i<10;++i){
            new Thread(new MyThread()).start;
        }
    }
}
class MyThread implements Runnable{
    @Override
    public void run(){
        System.out.println(Thread.currentThread().getName());
    }
}

使用Callable进行多线程操作:

Juc全网最全学习笔记【遇见狂神说】_第23张图片
Callable泛型T就是Call运行方法的返回值类型

Callable如何放入到Thread里面呢?

Juc全网最全学习笔记【遇见狂神说】_第24张图片

对于Thread运行,只能传入Runnable类型的参数

Juc全网最全学习笔记【遇见狂神说】_第25张图片

FutureTask中可以接受Callable参数

Juc全网最全学习笔记【遇见狂神说】_第26张图片

这样我们就可以先把Callable放入FutureTask中,如何再把FutureTask放入到Thread就可以了!

public class CallableTest {
    public static void main(String[] args)throws Exception {
        //new Thread(new Runnable()).start();
        //new Thread(new FutuerTask()).start
        //构造器
        //new Thread(new FutuerTask( Callable )).start

        for (int i = 1; i <= 10; i++) {
            MyThread thread = new MyThread();
            //适配类:FutureTask
            FutureTask<String> futureTask = new FutureTask<>(thread);
            //放入Thread使用
            new Thread(futureTask,String.valueOf(i)).start();
            //获取返回值
            String s = futureTask.get();
            System.out.println("返回值"+s);
        }
    }
}

class MyThread implements Callable<String>{

    @Override
    public String call() throws Exception {
        System.out.println("Call:"+Thread.currentThread().getName());
        return "String"+Thread.currentThread().getName();
    }
}

这样我们就可以使用Callable来进行多线程编程了。并且我们发现可以有返回值 了,并且可以抛出异常

Juc全网最全学习笔记【遇见狂神说】_第27张图片

8、常用的辅助类(必须会!)

8.1 CountDownLatch

Juc全网最全学习笔记【遇见狂神说】_第28张图片

其实就是一个减法计数器,对于计数器归零之后在进行后面的操作,这是一个计数器

//计数器
public class CountDownLatchDemo {
    public static void main(String[] args) {

        //总数是6 必须要执行的任何的时候,再使用!
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"走了");
                countDownLatch.countDown();// 数量-1
            },String.valueOf(i)).start();
        }

        try {
            countDownLatch.await();//等待计数器归 0  ,然后再向下执行
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("关门");
    }
}

主要方法:

  • countDown减一操作
  • await等待计数器归零

await等待计数器为0,就唤醒,再继续向下执行。

8.2 CyclickBarrier

在这里插入图片描述

其实就是一个加法计数器

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /*
        *集齐七颗龙珠召唤神龙
        * */
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤神龙成功!");
        });

        for (int i = 1; i <= 7 ; i++) {
            final int temp = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"收集了"+temp+"颗龙珠");
                try {
                    cyclicBarrier.await();//等待
                } catch(Exception e){
                    e.printStackTrace();
                }
            }).start();

        }

    }
}

8.3 Semaphore

Semaphore:信号量

代码模拟抢车位

public class SemaphoreDemo {
    public static void main(String[] args) {
        /**
         * 参数:线程数量   应用场景:限流!
         */
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <= 3; i++) {
            new Thread(()->{
                //acquire() 得到
                try {
                    semaphore.acquire();//抢到车位
                    System.out.println(Thread.currentThread().getName()+"抢到了车位");
                    TimeUnit.SECONDS.sleep(2);
                    System.out.println(Thread.currentThread().getName()+"离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    //release() 释放
                    semaphore.release();//释放
                }
            },String.valueOf(i)).start();
        }
    }
}

原理:

semaphore.acquire()获取资源,如果资源已经使用完了,就等待资源释放后再进行使用!

semaphore.release()释放,会将当前的信号量释放+1,然后唤醒等待的线程!

应用场景:

  • 多个共享资源互斥的使用
  • 并发限流,控制最大的线程数

9、阻塞队列

Juc全网最全学习笔记【遇见狂神说】_第29张图片
Juc全网最全学习笔记【遇见狂神说】_第30张图片
Juc全网最全学习笔记【遇见狂神说】_第31张图片

BlockingQueue

Juc全网最全学习笔记【遇见狂神说】_第32张图片

什么情况下会使用阻塞队列?

  • 多线程并发处理 线程A调用线程B,则A必须等待线程B执行完之后才能执行
  • 线程池

学会使用队列

方式 抛出异常 不会抛出异常,有返回值 阻塞 等待 超时等待
添加 add() offer() put() offer(,)
移除 remove() poll() take() poll(,)
检测队首元素 element() peek() - -
/**
* 抛出异常
*/
public static void test1(){
   //参数:队列的大小
   ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
   // java.lang.IllegalStateException: Queue full 队列满
   System.out.println(blockingQueue.add("a"));
   System.out.println(blockingQueue.add("b"));
   System.out.println(blockingQueue.add("c"));

   System.out.println("========================");
   // java.util.NoSuchElementException
   System.out.println(blockingQueue.remove());
   System.out.println(blockingQueue.remove());
   System.out.println(blockingQueue.remove());
}
/**
 * 不抛出异常
 */
public static void test2(){
    //队列的大小
    ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
    //
    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));
    //System.out.println(blockingQueue.offer("d"));//false 不抛出异常!

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

    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());//null 不抛出异常!
}
/*
 * 等待,阻塞(一直阻塞)
 * */
public static void test3() throws InterruptedException {
    ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);

    blockingQueue.put("a");
    blockingQueue.put("b");
    blockingQueue.put("c");
    //blockingQueue.put("d");//队列没有位置了,一直阻塞

    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    System.out.println(blockingQueue.take());
    //System.out.println(blockingQueue.take()); 没有这个元素,一直等待
}
/*
 * 等待,阻塞(等待超时)
 * */
public static void test4() throws InterruptedException {
    ArrayBlockingQueue<Object> blockingQueue = new ArrayBlockingQueue<>(3);
    System.out.println(blockingQueue.offer("a"));
    System.out.println(blockingQueue.offer("b"));
    System.out.println(blockingQueue.offer("c"));
    //System.out.println(blockingQueue.offer("d", 3, TimeUnit.SECONDS));//超时2秒
    System.out.println("==========================");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll(2,TimeUnit.SECONDS));
}

SynchronousQueue 同步队列

/**
 * 同步队列
 * 和其他的BlockingQueue 不一样,SynchronousQueue 不存储元素
 * put了一个元素,必须从里面先take取出来,否则不能在put进去值!
 */
public class SynchronousQueueTest {
    public static void main(String[] args) {
        SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();//同步队列

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName()+" put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName()+" put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName()+" put 3");
                synchronousQueue.put("3");
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T1").start();
        new Thread(()->{
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+ synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName()+synchronousQueue.take());
            } catch (Exception e) {
                e.printStackTrace();
            }
        },"T2").start();

    }
}

10、线程池(重点)

池化技术

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

线程池、连接池、对象池、内存池///… 创建、销毁十分浪费资源

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

线程池的好处:

  • 降低资源的消耗
  • 提高响应的速度
  • 方便管理

线程复用,可以控制最大并发数,管理线程

线程池:三大方法

Juc全网最全学习笔记【遇见狂神说】_第33张图片

/**
 * @function Executors 工具类、3大方法
 * @author 派 大 星
 * @date 2022/3/31 22:49
 */
public class Demo01 {
    public static void main(String[] args) {
        // 单个线程
        // ExecutorService threadPool = Executors.newSingleThreadExecutor();

        // 创建一个固定的线程池的大小
        // ExecutorService threadPool = Executors.newFixedThreadPool(5);

        // 可伸缩的,遇强则强,遇弱则弱
        ExecutorService threadPool = Executors.newCachedThreadPool();

        try {
            for (int i = 1; i <= 100; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            // 线程池用完,程序执行完关闭线程池
            threadPool.shutdown();
        }
    }
}

7大参数

源码分析:

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亿 oom溢出
                                  60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}

// 本质: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;
}

Juc全网最全学习笔记【遇见狂神说】_第34张图片

手动创建一个线程池

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 7:12
 * 四种拒绝策略
 */
public class ThreadPoolExecutorDemo {
    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());


        try {
            for (int i = 1; i <= 9; i++) {
                //使用自定义线程池创建线程
                //最大承载:Deque + max(双端队列 + 最大线程池数)
                threadPoolExecutor.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭线程
            threadPoolExecutor.shutdown();
        }
    }
}

四种拒绝策略

Juc全网最全学习笔记【遇见狂神说】_第35张图片

/**
 * new ThreadPoolExecutor.AbortPolicy() 银行满了,还有人进来,不处理这个人,抛出异常
 * new ThreadPoolExecutor.CallerRunsPolicy() 哪来的去哪里!
 * new ThreadPoolExecutor.DiscardPolicy() 队列满了,丢掉任务,不会抛出异常!
 * new ThreadPoolExecutor.DiscardOldestPolicy() 队列满了。尝试去和最早的竞争(竞争成功,执行,竞争不成功,则丢掉任务),也不会抛出异常!
 */

小结和拓展

了解:IO密集型、CPU密集型

// 最大线程到底如何定义?
// 1. CPU 密集型 CPU几核,就是几,可以保持CPU的效率最高!Runtime.getRuntime().availableProcessors()
// 2. IO 密集型 > 判断你程序中十分消耗资IO的线程
// 程序 15个大型任务,io十分占用资源

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

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

函数式接口:只有一个方法的接口

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

Function函数式接口

Juc全网最全学习笔记【遇见狂神说】_第36张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 8:05
 * Function 函数型接口,有一个输入参数,有一个输出
 * 只要是函数型接口,可以用lambda表达式简化
 *
 */
public class Demo01 {
    public static void main(String[] args) {
        // 工具类:输出输入的值
        //
//        Function function = new Function() {
//            @Override
//            public String apply(String s) {
//                return s;
//            }
//        };
        // lambda表达式简化
        Function<String, String> function = (str)->{return str;};

        System.out.println(function.apply("123"));
    }
}

Predicate断定型接口:有一个输入参数,返回值只能是 布尔值!

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 19:42
 * 断定性接口:有一个输入参数,返回值只能是 布尔值!
 */
public class Demo02 {
    public static void main(String[] args) {
        //判断字符串是否为空
//        Predicate predicate = new Predicate() {
//            @Override
//            public boolean test(String s) {
//                return s.isEmpty();
//            }
//        };

        Predicate<String> predicate = (str)->{
            return str.isEmpty();
        };
        System.out.println(predicate.test(""));
    }
}

Consumer消费型接口

Juc全网最全学习笔记【遇见狂神说】_第37张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 19:48
 * Consumer 消费型接口:只有输入,没有返回值!
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer consumer = new Consumer() {
//            @Override
//            public void accept(String s) {
//                System.out.println(s);
//            }
//        };

        Consumer<String> consumer =(str)->{
            System.out.println(str);
        };

        consumer.accept("dadada");
    }
}

Supplier供给型接口

Juc全网最全学习笔记【遇见狂神说】_第38张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 19:52
 * Supplier 消费型接口:没有参数,只有返回值!
 */
public class Demo04 {
    public static void main(String[] args) {
//        Supplier supplier = new Supplier() {
//            @Override
//            public String get() {
//                return "null";
//            }
//        };
        Supplier<String> supplier =()->{return "xxxx";};
        System.out.println(supplier.get());
    }
}

12、Stream流式计算

什么是Stream流式计算?

大数据:存储+计算

集合、Mysql 本质就是存储东西!

Juc全网最全学习笔记【遇见狂神说】_第39张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 20:14
 * 题目要求:一分钟内完成此题,只能用一行代码实现!
 * 现有5个用户!筛选
 * 1. ID必须是偶数
 * 2. 年龄必须大于23
 * 3. 用户名转为大写字母
 * 4. 用户名字母倒着排序
 * 5. 只输出一个用户
 */
public class Test {
    public static void main(String[] args) {
        User user1 = new User(1,"a",21);
        User user2 = new User(2,"b",22);
        User user3 = new User(3,"c",23);
        User user4 = new User(4,"d",24);
        User user5 = new User(6,"e",25);
        List<User> users = Arrays.asList(user1, user2, user3, user4, user5);
        //集合就是存储
        users.stream()
                // 过滤 filter(Predicate predicate);
                .filter(u->{return u.getId()%2==0;})
                .filter(u->{return u.getAge()>23;})
                // map(Function mapper);
                .map(u->{return u.getName().toUpperCase();})
                //sorted(Comparator comparator);
                .sorted((u1,u2)->{return u2.compareTo(u1);})
                .limit(1)
                .forEach(System.out::println);
    }
}

13、ForkJoin

什么是ForkJoin

ForkJoin在JDK1.7 ,并行执行任务!提高效率,大数据量!

Juc全网最全学习笔记【遇见狂神说】_第40张图片

ForkJoin特点:工作窃取

这个里面维护的都是双端队列

Juc全网最全学习笔记【遇见狂神说】_第41张图片

如何理解工作窃取:现有两个线程A、B,假设B提前执行结束,但是B不能一直等待A执行结束,所以B会窃取A的任务进行执行

ForkJoin如何使用

在这里插入图片描述
Juc全网最全学习笔记【遇见狂神说】_第42张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 21:04
 * 求和计算任务
 * 如何使用ForkJoin
 * 1. ForkJoinPool 通过它来执行
 * 2. 计算任务 ForkJoinPool.execute(ForkJoinTask task)
 * 3. 计算类必须继承 ForkJOinTask
 */
public class ForkJoinDemo extends RecursiveTask<Long> {

    private Long start;
    private Long end;
    //临界值
    private Long temp = 10000L;

    public ForkJoinDemo(Long start, Long end) {
        this.start = start;
        this.end = end;
    }

   public void test(){

   }

   //计算方法
    @Override
    protected Long compute() {
        if (end-start < temp){
            Long sum = 0L;
            for (Long i = start; i <= end; i++) {
                sum+=i;
            }
            return sum;
        }else {//使用ForkJoin
            //中间值
            long middle = (start + end) / 2;
            ForkJoinDemo task1 = new ForkJoinDemo(start, middle);
            //拆分任务,把任务压入线程队列
            task1.fork();
            ForkJoinDemo task2 = new ForkJoinDemo(middle+1, end);
            //拆分任务,把任务压入线程队列
            task2.fork();
            return task1.join() + task2.join();
        }
    }
}

测试:

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 21:35
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        test3();
    }

    public static void test1(){
        //3724
        Long sum = 0L;
        long start = System.currentTimeMillis();
        for (int i = 1; i <= 10_0000_0000; i++) {
            sum+=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }

    /**
     * 使用ForkJoin调优的
     * 3922
     */
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();

        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTask<Long> task = new ForkJoinDemo(0L, 10_0000_0000L);
        //提交任务
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        Long sum = submit.get();
        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }

    /**
     * 185
     */
    public static void test3(){
        long start = System.currentTimeMillis();
        //Stream并行流
        long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);

        long end = System.currentTimeMillis();
        System.out.println("sum="+sum+"时间:"+(end-start));
    }
}

14、异步回调

Future设计的初衷:对将来的某个事件的结果进行建模

Juc全网最全学习笔记【遇见狂神说】_第43张图片

/**
 * @author 派 大 星
 * @function
 * @date 2022/4/1 22:42
 *
 * 异步调用:CompletableFuture
 * 异步执行
 * 成功回调
 * 失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值的 runAsync 异步回调
//        CompletableFuture completableFuture = CompletableFuture.runAsync(()->{
//            try {
//                TimeUnit.SECONDS.sleep(2);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//            System.out.println(Thread.currentThread().getName()+"runAsync => Void");
//        });
//        System.out.println("111111");
//        //阻塞获取执行结果
//        completableFuture.get();
        // 有返回值的 supplyAsync 异步回调
        //ajax 成功和失败的回调
        // 返回的是错误信息
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName()+"supplyAsync => Integer");
            int i = 10/0;
            return 1024;
        });
        Integer integer = completableFuture.whenComplete((t, u) -> {
            //t 为正常的返回结果
            System.out.println("t=>" + t);
            //u 为失败的返回结果
            System.out.println("u=>" + u);
        }).exceptionally((e) -> {
            System.out.println(e.getMessage());
            return 233;
        }).get();
        System.out.println(integer);
    }
}

15、JMM

请你谈谈你对Volatile的理解

Volatile是Java虚拟机提供 的轻量级的同步机制

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

什么是JMM

JMM:Java内存模型,不存在的东西!

关于JMM的一些同步的约定:

  • 线程解锁前,必须把共享变量立刻刷回主存
  • 线程加锁前,必须读取主存中的最新值到工作内存中!
  • 加锁和解锁是同一把锁

线程:工作内存主内存

Juc全网最全学习笔记【遇见狂神说】_第44张图片

Juc全网最全学习笔记【遇见狂神说】_第45张图片

内存交互操作有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操作之前,必须把此变量同步回主内存

public class JmmDemo {
    private static int num = 0;
    public static void main(String[] args) {//main线程

        // 线程1
        new Thread(()->{
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

问题:程序不知道主内存的值已经被修改过了

Juc全网最全学习笔记【遇见狂神说】_第46张图片

16、Volatile

1、保证可见性

public class JmmDemo {
    /**
     *  不加volatile 程序就会死循环
     *  加volatile 可以保证可见性
     */
    private volatile static int num = 0;
    public static void main(String[] args) {//main线程

        // 线程1 对主内存的变化是不知道的
        new Thread(()->{
            while (num == 0){

            }
        }).start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        num = 1;
        System.out.println(num);
    }
}

2、不保证原子性

原子性:不可分割

public class VDemo02 {
    /**
     *  volatile 不保证原子性 
     */
    private volatile static int num = 0;

    public static void add(){
        num++;
    }

    public static void main(String[] args) {
        //理论上结果为20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+num);

    }
}

如果不加lock 和 Synchronized ,怎么样保证原子性

Juc全网最全学习笔记【遇见狂神说】_第47张图片

原子类为什么这么高级

public class VDemo02 {
    /**
     *  volatile 不保证原子性
     */
    private static AtomicInteger num = new AtomicInteger();

    public static void add(){
        // AtomicInteger的+1操作
        num.getAndIncrement();
    }

    public static void main(String[] args) {
        //理论上结果为20000
        for (int i = 1; i <= 20; i++) {
            new Thread(()->{
                for (int j = 0; j < 1000; j++) {
                    add();
                }
            }).start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName()+" "+num);

    }
}

指令重排

什么是指令重排:你写的程序,计算机并不是按照你写的那样去执行的。

源代码 -> 编译器优化的重排 -> 指令并行也可能重排 -> 内存系统也会重排 --> 执行

处理器在进行指令重排的时候,考虑:数据之间的依赖性!

int x = 1; //1
int y = 4; //2
x = x + 4; //3
y = x * x; //4
我们所期望的:1234 但是可能执行的时候会变成 2134 1324
可不可能是 4123

可能造成影响的结果:a b x y 这四个值默认都是 0

线程A 线程B
x = a y = b
b = 1 a = 2

正常的结果 :x = 0; y = 0;但是可能由于指令重排

线程A 线程B
x = a y = b
b = 1 a = 2

指令重排导致的诡异结果:x = 2;y = 1;

Volatile可以避免指令重排:

内存屏障。CPU指令,作用:

  • 保证特定的操作的执行顺序
  • 可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)

Juc全网最全学习笔记【遇见狂神说】_第48张图片

Volatile是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生!

17、彻底玩转单例模式

内存屏障在单例模式中使用的最多!

饿汉式

/**
 * @author 派 大 星
 * @function 饿汉式单例
 * @date 2022/4/2 21:09
 */
public class Hungry {

    /**
     * 可能会浪费空间
     */
    private byte[] data1 = new byte[1024*1024];
    private byte[] data2 = new byte[1024*1024];
    private byte[] data3 = new byte[1024*1024];
    private byte[] data4 = new byte[1024*1024];

    private Hungry(){

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }
}

DCL懒汉式

/**
 * @author 派 大 星
 * @function 懒汉式单例模式
 * @date 2022/4/2 21:48
 */
public class LazyMan {
    private LazyMan(){
        synchronized (LazyMan.class){
            if (lazyMan != null){
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
        System.out.println(Thread.currentThread().getName()+" ok");
    }

    private static LazyMan lazyMan;

    /**
     * 双重检测锁模式的 懒汉式单例 DCL懒汉式
     */
    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 instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();
        // 破除私有权限
        declaredConstructor.setAccessible(true);
        LazyMan instance2 = declaredConstructor.newInstance();
        System.out.println(instance);
        System.out.println(instance2);
    }

}
/**
 * 1. 分配内存空间
 * 2. 执行构造方法,初始化对象
 * 3. 把这个对象指向这个空间
 *
 * 123
 * 132 A
 *     B 此时lazyMan还没有完成构造
 */

静态内部类

/**
 * @author 派 大 星
 * @function 静态内部类
 * @date 2022/4/2 22:02
 */
public class Holder {

    private Holder(){

    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }

    public static class InnerClass{
        private final static Holder HOLDER = new Holder();
    }
}

单例不安全,反射

枚举

/**
 * @author 派 大 星
 * @function enum是一个什么?本身也是一个Class类
 * @date 2022/4/2 22:11
 */
public enum EnumSingle {

    INSTANCE;

    public static EnumSingle getInstance(){
        return INSTANCE;
    }
}


class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance = EnumSingle.INSTANCE;
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        // 破除私有权限
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();
        System.out.println(enumSingle);
        System.out.println(instance);

    }
}

18、深入理解CAS

什么是CAS

public class CASDemo {

    //CAS
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022);
        // 期望值 ,更新值
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新 ,CAS 是CPU的并发原语
        System.out.println(atomicInteger.compareAndSet(2022, 2023));
        System.out.println(atomicInteger.get());
        atomicInteger.getAndIncrement();
        System.out.println(atomicInteger.compareAndSet(2022, 2023));
    }
}

Unsafe类

Juc全网最全学习笔记【遇见狂神说】_第49张图片
Juc全网最全学习笔记【遇见狂神说】_第50张图片
Juc全网最全学习笔记【遇见狂神说】_第51张图片
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

缺点:

  • 循环会耗时
  • 一次性只能保证一个共享变量的原子性
  • ABA问题

CAS:ABA问题(狸猫换太子)

Juc全网最全学习笔记【遇见狂神说】_第52张图片

解释:现有A、B线程对资源A进行操作(操作同一资源),线程A中含有CAS操作,
当A = 1时,将1 修改为2 。线程B中同样含有CAS操作,当A = 1时,
将A替换成3,但是B的线程执行速度很快,在进行CAS操作时,将A修改为3后,又将3修改为了1,
此时线程A再进行CAS操作时,A的值虽然为1,但是A的值已经被修改过了!
public class CASDemo {

    //CAS
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(2022);
        // 期望值 ,更新值
        // public final boolean compareAndSet(int expect, int update)
        // 如果我期望的值达到了,那么就更新,否则,就不更新 ,CAS 是CPU的并发原语
        //=================捣乱的线程=====================
        System.out.println(atomicInteger.compareAndSet(2022, 2023));
        System.out.println(atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(2023, 2022));
        System.out.println(atomicInteger.get());

        //=================期望的线程=====================

        System.out.println(atomicInteger.compareAndSet(2022, 6666));
        System.out.println(atomicInteger.get());
    }
}

19、原子引用

解决ABA问题,引用原子引用!对应的思想(乐观锁)

带版本号的原子操作!

public class atomicReferenceDemo {
    public static void main(String[] args) {
        // AtomicStampedReference 注意:如果泛型是一个包装类,注意对象的引用问题
        // 正常的业务操作,这里引用的都是一个个对象
        AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(1,1);

        // CAS compareAndSet: 比较并交换!
        // 乐观锁的原理一样!
        new Thread(()->{
            //获取版本号
            int stamp = atomicStampedReference.getStamp();
            System.out.println("a1 =>"+stamp);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a2 =>"+stamp);

            System.out.println(atomicStampedReference.compareAndSet(2, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
            System.out.println("a3 =>"+stamp);

        },"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, stamp, stamp + 1));
            System.out.println("b2 =>"+atomicStampedReference.getStamp());

        },"b").start();
    }
}

Integer使用了对象缓存机制,默认范围是 -128~127,推荐使用静态工厂方法valueOf获取对象的实例,而不是new ,因为valueOf使用缓存,而new 一定会创建新的对象分配新的内存空间!
Juc全网最全学习笔记【遇见狂神说】_第53张图片

20、各种锁的理解

1 公平锁、非公平锁

公平锁:非常公平,比如一个队列不能插队的,必须先来后到

非公平锁:非常不公平,可以插队(默认都是非公平锁)比如l两个任务耗时分别是3s、3h,不可能让3s一直等待3h的完成后再执行

public ReentrantLock() {
    sync = new NonfairSync();
}

// 重载方法
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

2 可重入锁

可重入锁(递归锁)

Juc全网最全学习笔记【遇见狂神说】_第54张图片

Synchronized锁

public class Demo01 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sms();
        },"A").start();

        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone{

    public synchronized void sms(){
        System.out.println(Thread.currentThread().getName()+"sms");
        //这里也有一把锁
        call();
    }

    public synchronized void call(){
        System.out.println(Thread.currentThread().getName()+"call");
    }
}

Lock锁

public class Demo02 {
    public static void main(String[] args) {
        Phone2 phone = new Phone2();
        new Thread(()->{
            phone.sms();
        },"A").start();

        new Thread(()->{
            phone.sms();
        },"B").start();
    }
}

class Phone2{
    Lock lock = new ReentrantLock();

    public void sms(){
        // 细节问题:lock.lock(); lock.lock(); Lock 锁必须配对,否则就会死在里面
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"sms");
            //这里也有一把锁
            call();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public void call(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"call");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

3 自旋锁

Juc全网最全学习笔记【遇见狂神说】_第55张图片

自定义自旋锁

public class SpinLockDemo {

    /**
     * int 0
     * Thread null
     */
    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 static void main(String[] args) {
//        ReentrantLock reentrantLock = new ReentrantLock();
//        reentrantLock.lock();
//        reentrantLock.unlock();

        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(3);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinLockDemo.myUnLock();
            }
        },"T1").start();

        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(()->{
            spinLockDemo.myLock();
            try {
                TimeUnit.SECONDS.sleep(1);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                spinLockDemo.myUnLock();
            }
        },"T2").start();
    }
}

4 死锁

死锁是什么?

Juc全网最全学习笔记【遇见狂神说】_第56张图片

死锁测试:怎么排除死锁

public class DeadLockDemo {
    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 lockA;
    private String lockB;

    public MyThread(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {

        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"lock:"+lockA+" => "+lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+" lock:"+lockB+" => "+lockA);
            }
        }

    }
}

解决问题

  • 使用jps -l 定位进程号

Juc全网最全学习笔记【遇见狂神说】_第57张图片

  • 使用 jstack 进程号找到死锁问题

Juc全网最全学习笔记【遇见狂神说】_第58张图片

面试或工作中排查问题

  • 查看日志
  • 查看堆栈信息

你可能感兴趣的:(java)