java--JUC快速入门(彻底搞懂JUC)

目录

java–JUC快速入门(彻底搞懂JUC)

1、学习多线程之前需要知道的一些概念。

1.1 JUC是什么?

1.2 进程与线程的区别:

1.3 并发与并行的区别:

1.4 线程有六个状态:

1.5 wait/sleep的区别:

2、JUC的结构

3、Lock锁(重点)

传统synchronized

Lock 接口

实现类

reentrantLock构造器

4、集合类不安全

​编辑5、Callable()

6、常用的辅助类

7、读写锁

8、阻塞队列

9、线程池

什么是线程池?

线程池的好处:

线程池: 三大方法

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

13、Stream流式计算

14、ForkJoin

归并思想

15、异步回调

16、JMM

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

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

JMM对这八种指令的使用,制定了如下规则:

17、Volatile

volatile可以避免指令重排:

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

18、彻底玩转单例模式

19、深入理解CAS

大厂你必须要深入研究底层!有所突破! 修内功,操作系统,计算机网络原理

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就

缺点:

20、原子引用

注意:

Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

21、各种锁的理解

1、公平锁、非公平锁

2、可重入锁

3、自旋锁

4、死锁


1、学习多线程之前需要知道的一些概念。

1.1 JUC是什么?

JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题! 我们在面试过程中也会经常问到这类问题!

1.2 进程与线程的区别:

进程 : 一个运行中的程序的集合; 一个进程往往可以包含多个线程,至少包含一个线程

java默认有几个线程? 两个 main线程 gc线程

线程 : 线程(thread)是操作系统能够进行运算调度的最小单位。

1.3 并发与并行的区别:

并发(多线程操作同一个资源,交替执行) CPU一核, 模拟出来多条线程,天下武功,唯快不破,快速交替 并行(多个人一起行走, 同时进行) CPU多核,多个线程同时进行 ; 使用线程池操作

1.4 线程有六个状态:
public enum State {
       // 新生
        NEW,
​
        // 运行
        RUNNABLE,
​
        // 阻塞
        BLOCKED,
​
        // 等待
        WAITING,
​
        //超时等待
        TIMED_WAITING,
​
        //终止
        TERMINATED;
    }
​
1.5 wait/sleep的区别:
  1. 来自不同的类: wait来自object类, sleep来自线程类

  2. 关于锁的释放:wait会释放锁, sleep不会释放锁

  3. 使用的范围不同: wait必须在同步代码块中, sleep可以在任何地方睡眠

2、JUC的结构

在这里插入图片描述

1,tools(工具类):又叫信号量三组工具类,包含有

1)CountDownLatch(闭锁) 是一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待

2)CyclicBarrier(栅栏) 之所以叫barrier,是因为是一个同步辅助类,允许一组线程互相等待,直到到达某个公共屏障点 ,并且在释放等待线程后可以重用。

3)Semaphore(信号量) 是一个计数信号量,它的本质是一个“共享锁“。信号量维护了一个信号量许可集。线程可以通过调用 acquire()来获取信号量的许可;当信号量中有可用的许可时,线程能获取该许可;否则线程必须等待,直到有可用的许可为止。 线程可以通过release()来释放它所持有的信号量许可。

2,executor(执行者):是Java里面线程池的顶级接口,但它只是一个执行线程的工具,真正的线程池接口是ExecutorService,里面包含的类有:

1)ScheduledExecutorService 解决那些需要任务重复执行的问题

2)ScheduledThreadPoolExecutor 周期性任务调度的类实现

3,atomic(原子性包):是JDK提供的一组原子操作类,

包含有AtomicBoolean、AtomicInteger、AtomicIntegerArray等原子变量类,他们的实现原理大多是持有它们各自的对应的类型变量value,而且被volatile关键字修饰了。这样来保证每次一个线程要使用它都会拿到最新的值。

4,locks(锁包):是JDK提供的锁机制,相比synchronized关键字来进行同步锁,功能更加强大,它为锁提供了一个框架,该框架允许更灵活地使用锁包含的实现类有:

1)ReentrantLock 它是独占锁,是指只能被独自占领,即同一个时间点只能被一个线程锁获取到的锁。

2)ReentrantReadWriteLock 它包括子类ReadLock和WriteLock。ReadLock是共享锁,而WriteLock是独占锁。

3)LockSupport 它具备阻塞线程和解除阻塞线程的功能,并且不会引发死锁。

5,collections(集合类):主要是提供线程安全的集合, 比如:

1)ArrayList对应的高并发类是CopyOnWriteArrayList,

2)HashSet对应的高并发类是 CopyOnWriteArraySet,

3)HashMap对应的高并发类是ConcurrentHashMap等等

下面及具体来是学习一下多线程创建及使用方法:

普通的线程代码, 之前都是用的thread或者runnable接口; 具体实现如下:

public class demo01 {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        threadDemo.start();
        new Thread(new ThreadDemo2()).start();
    }
}
​
class ThreadDemo extends Thread{
    @Override
    public void run() {
        System.out.println("普通线程已开启(继承Thread)");
    }
}
class ThreadDemo2 implements Runnable{
​
    @Override
    public void run() {
        System.out.println("普通线程已开启(实现Runnable接口)");
    }
}
​
​

程序运行结果:

在这里插入图片描述

3、Lock锁(重点)

传统synchronized

synchronized是Java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  1. 修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;

  2. 修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;

  3. 修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;

  4. 修改一个类,其作用的范围是synchronized后面括号括起来的部分,作用主的对象是这个类的所有对象。

Lock 接口

在这里插入图片描述

实现类

在这里插入图片描述

reentrantLock构造器
    public ReentrantLock() {
        sync = new NonfairSync(); //无参默认非公平锁
    }
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();//传参为true为公平锁
    }
 

公平锁: 十分公平: 可以先来后到,一定要排队 非公平锁: 十分不公平,可以插队(默认)

public class SaleTicketDemo {
​
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
​
        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();
​
    }
}
​
class Ticket {
​
    private int ticketNum = 30;
    private Lock lock = new ReentrantLock();
​
    public void sale() {
        lock.lock();
        try {
            if (this.ticketNum > 0) {
                System.out.println(Thread.currentThread().getName() + "购得第" + ticketNum-- + "张票, 剩余" + ticketNum + "张票");
            }
            //增加错误的发生几率
            Thread.sleep(10);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
​
}
​
​

synchronized和lock锁的区别

  1. synchronized内置的java关键字,Lock是一个java类

  2. synchronized无法判断获取锁的状态, Lock可以判断是否获取到了锁

  3. synchronized会自动释放锁,Lock必须要手动释放锁!如果不是释放锁,会产生死锁

  4. synchronized 线程1(获得锁,阻塞),线程2(等待); Lock锁就不一定会等待下去

  5. synchronized 可重入锁,不可以中断的,非公平的; Lock锁,可重入的,可以判断锁,非公平(可自己设置);

  6. synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码

Condition 精准的通知和唤醒线程

Condition是个接口,基本的方法就是await()和signal()方法; Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() 调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用 Conditon中的await()对应Object的wait();

Condition中的signal()对应Object的notify();

Condition中的signalAll()对应Object的notifyAll()。

在这里插入图片描述

Condition常见例子(生产者消费者模式(完成加一减一各一次操作)):

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
​
public class PC {
    public static void main(String[] args) {
        a a = new a();
        new Thread(()->{
            for (int i =0;i<10;i++){
                a.increment();
            }
        },"A").start();
        new Thread(()->{
            for (int i =0;i<10;i++){
                a.decrease();
            }
        },"B").start();
​
​
    }
​
}
class  a{
    public int nummber=0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public   void  increment(){
        lock.lock();
        try {
            while(nummber!=0){
                condition.await();
            }
            nummber++;
            System.out.println(Thread.currentThread().getName()+">>"+nummber);
            condition.signalAll();
        }
         catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
          lock.unlock();
        }
​
    }
    public  void decrease(){
        lock.lock();
        try {
            while(nummber!=1){
                condition.await();
            }
            nummber--;
            System.out.println(Thread.currentThread().getName()+">>"+nummber);
            condition.signalAll();
        }
        catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

运行结果: 在这里插入图片描述

4、集合类不安全

list 不安全

//java.util.ConcurrentModificationException 并发修改异常!
public class ListTest {
​
    public static void main(String[] args) {
        //并发下 arrayList 是不安全的
        /**
         * 解决方案
         * 1. 使用vector解决
         * 2. List<String> arrayList = Collections.synchronizedList(new ArrayList<>());
         * 3. List<String> arrayList = new CopyOnWriteArrayList<>();
         */
        //copyOnWrite 写入时复制  COW 计算机程序设计领域的一种优化策略
        //多个线程调用的时候, list, 读取的时候固定的,写入的时候,可能会覆盖
        //在写入的时候避免覆盖造成数据问题
        //CopyOnWriteArrayList 比 vector牛逼在哪里
​
        //读写分离
        List<String> arrayList = new CopyOnWriteArrayList<>();
        for (int i = 0; i < 100; i++) {
            new Thread(()->{
                arrayList.add(UUID.randomUUID().toString().substring(0,5));
                System.out.println(arrayList);
            },String.valueOf(i)).start();
        }
    }
}
​

set 不安全

/**
 * 同理可证
 */
public class SetTest {
​
    public static void main(String[] args) {
​
//        Set<String> set = new HashSet<>();
        //如何解决hashSet线程安全问题
        //1. Set<String> set = Collections.synchronizedSet(new HashSet<>());
        Set<String> set = new CopyOnWriteArraySet<>();
​
        for (int i = 0; i < 100; i++) {
            new Thread(() -> {
                set.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(set);
            }, String.valueOf(i)).start();
        }
    }
}

hashSet底层是什么? hashMap

public HashSet() {
    map = new HashMap<>();
}
​
// add 的本质就是 map 的 key key是无法重复的
public boolean add(E e) {
    return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//这是一个不变的值

HashMap 不安全 map的基本操作

在这里插入图片描述5、Callable()

在这里插入图片描述

  1. 可以有返回值

  2. 可以抛出异常

  3. 方法不同, run() => call()

    在这里插入图片描述

    在这里插入图片描述

public class CallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask,"a").start();
        System.out.println(futureTask.get());

    }
}

class MyThread implements Callable<Integer> {


    @Override
    public Integer call() throws Exception {
        System.out.println("call()方法被调用了");
        return 1024;
    }
}

6、常用的辅助类

CountDownLatch

在这里插入图片描述

//计数器
public class demo02 {
    public static void main(String[] args) throws InterruptedException {
        //相当于计数器
        CountDownLatch countDownLatch = new CountDownLatch(5);
        //计数器总数是5,当减少为0,任务才继续向下执行
        for (int i = 1; i <6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"==>start");
                countDownLatch.countDown(); // 数量减一
            }).start();
        } 
        countDownLatch.await(); //等待计数器归零,然后再向下执行
        System.out.println("main线程继续向下执行");
    }
}

结果:

在这里插入图片描述

原理:

countDownLatch.countDown(); //数量减1

countDownLatch.await();// 等待计数器归零,然后再向下执行

每次有线程调用countDown()数量-1,假设计数器变为0,countDownLatch.await();就会被唤醒,继续执行 cyclicBarrier

在这里插入图片描述

加法计数器

public class CyclicBarrierDemo {

    public static void main(String[] args) {
        /**
         * 集齐77个龙珠召唤神龙
         */
        // 召唤龙珠的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, ()->{
            System.out.println("召唤神龙成功! ");
        });
        for (int i = 0; i < 7; i++) {
            final int temp = i;
            //lambda 能拿到i吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");


                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

运行结果:

在这里插入图片描述

Semaphore

在这里插入图片描述

public class SemaphoreTest {

    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < 6; i++) {
            int temp = i;
            new Thread(()->{
                try {
                    semaphore.acquire(); //获取
                    System.out.println(temp + "号车抢到车位");
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); //释放
                    System.out.println(temp + "号车离开车位");
                }
            }).start();
        }
    }
}

运行结果:

在这里插入图片描述

原理:

semaphore.acquire(); //获取信号量,假设如果已经满了,等待信号量可用时被唤醒

semaphore.release(); //释放信号量

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

7、读写锁

ReadWriteLock

在这里插入图片描述

package com.czp.lock;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;


/**
 * 独占锁(写锁) 一次只能由一个线程占有
 * 共享锁(读锁) 一次可以有多个线程占有
 * readWriteLock
 * 读-读 可以共存
 * 读-写  不能共存
 * 写-写 不能共存
 */
public class ReadWriteLock {

    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();

        //写入操作
        for (int i = 0; i < 6; i++) {
            int temp = i;
            new Thread(() -> {
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }

        //读取操作
        for (int i = 0; i < 6; i++) {
            int temp = i;
            new Thread(() -> {
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

class MyCacheLock {

    private volatile Map<String, Object> map = new HashMap<>();
    //读写锁
    private java.util.concurrent.locks.ReadWriteLock lock = new ReentrantReadWriteLock();

    // 存,写入的时候只有一个人操作
    public Object get(String key) {
        lock.readLock().lock();
        Object o = null;
        try {
            System.out.println(Thread.currentThread().getName() + "读取");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            o = map.get(key);
            System.out.println(Thread.currentThread().getName() + "读取ok" + o);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
        return o;
    }

    public void put(String key, Object value) {
        lock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName() + "写入" + key);
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入完毕");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

}

class MyCache {

    private volatile Map<String, Object> map = new HashMap<>();

    public Object get(String key) {
        System.out.println(Thread.currentThread().getName() + "读取");

        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName() + "读取ok" + o);

        return o;
    }

    public void put(String key, Object value) {
        System.out.println(Thread.currentThread().getName() + "写入" + key);
        map.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入完毕");
    }

}

8、阻塞队列

在这里插入图片描述

在这里插入图片描述

Blockqueue

在这里插入图片描述

在这里插入图片描述

什么情况下我们会使用阻塞队列,多线程并发处理,线程池! 如何使用队列? 添加 移除 四组API

在这里插入图片描述

   /**
     * 抛出异常
     */
    public static void test1() {
        //队列的大小
        ArrayBlockingQueue queue = new ArrayBlockingQueue<>(3);
        System.out.println(queue.add("a"));
        System.out.println(queue.add("b"));
        System.out.println(queue.add("c"));
        //java.lang.IllegalStateException: Queue full
        //System.out.println(queue.add("d"));
        System.out.println("----------------------");
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        System.out.println(queue.remove());
        //java.util.NoSuchElementException
        System.out.println(queue.remove());
        //抛出异常

    }
   /**
     * 有返回值没有异常
     */
    public static void test2(){
        ArrayBlockingQueue queue = new ArrayBlockingQueue(3);

        System.out.println(queue.offer("a"));
        System.out.println(queue.offer("b"));
        System.out.println(queue.offer("c"));
//        System.out.println(queue.offer("d"));       //offer 不抛出异常
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
//        System.out.println(queue.poll());   //null 不抛出异常
    }
    /**
     * 等待阻塞
     */
    public static void test3() throws InterruptedException {
        ArrayBlockingQueue queue = new ArrayBlockingQueue(3);
        queue.put("a");
        queue.put("b");
        queue.put("c");
//        queue.put("c");  队列没有位置就会阻塞
        System.out.println(queue.take());
        System.out.println(queue.take());
        System.out.println(queue.take());
    }

SynchronizedQueue 同步队列 没有容量, 进去一个元素,必须等待取出来之后,才能再往里面放一个元素 put take

/**
 * 同步队列
 * 和其他的lockQueue 不一样, SynchronousQueue 不存储元素
 */
public class SyncQueue {

    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 (InterruptedException 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 (InterruptedException e) {
                e.printStackTrace();
            } finally {
            }
        },"T2").start();
    }
}

9、线程池

什么是线程池?

线程池:一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。

线程池的好处:
  1. 降低资源消耗

  2. 提高响应速度

  3. 方便管理

线程池: 三大方法

创建线程池的三大方法如下:

  ExecutorService service = Executors.newSingleThreadExecutor();//单个线程
  /*public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }*/
  ExecutorService service = Executors.newFixedThreadPool(5);//创建一个固定的线程池的大小
  /* public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }*/
  ExecutorService service = Executors.newCachedThreadPool();//缓存线程池,可伸缩的
  /*public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }*/

但是在阿里巴巴开发手册中提到👇:

在这里插入图片描述

在上面三大方法中,本质所有线程池最终都调用的ThreadPoolExecutor来创建线程池的,而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.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

七大参数举例说明:

在这里插入图片描述

核心线程大小表示上述图中银行一般情况下开设的窗口(线程数),阻塞队列相当于银行的侯客区,当线程数大于侯客厅+核心线程数的数量时,线程数继续增大时,线程池数的使用依次增加,直至达到最大线程池的大小,达到最大处理线程数还增加的话就会使用拒绝策略!(最大处理线程数=最大线程数+阻塞队列大小)(说的不是很明白,不理解建议可以看一下狂神的juc 链接:22、池化技术及线程池使用_哔哩哔哩_bilibili自定义创建线程池:

ExecutorService threadPool =
 new ThreadPoolExecutor(3, //核心线程池大小
                        5, //最大并发数
                        10, //超时时间
                        TimeUnit.SECONDS, //时间单位
                        new LinkedBlockingQueue<>(),//线程等候队列
                        Executors.defaultThreadFactory(), //线程创建工厂
                        new ThreadPoolExecutor.DiscardOldestPolicy());//拒绝策略

最大线程到底应该如何定义? 1、CPU密集型 几核,就是几,可以保证CPU的效率最高 java获取cpu核数的方法:

 Runtime.getRuntime().availableProcessors()

2、IO 密集型 判断程序中十分耗I/O的线程, 大于两倍

四种默认的拒绝策略

在这里插入图片描述

/**
 * new ThreadPoolExecutor.AbortPolicy() 超出最大处理线程抛出异常(最大处理线程数=最大线程数+阻塞队列大小)
 * new ThreadPoolExecutor.CallerRunsPolicy() 从哪个线程创建就由那个线程执行(如main线程创建,就在main线程执行)
 * new ThreadPoolExecutor.DiscardPolicy() 队列满了不会抛出异常
 * new ThreadPoolExecutor.DiscardOldestPolicy() 尝试去和第一个竞争,也不会抛出异常
 */

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

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

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

在这里插入图片描述

Function函数式接口

在这里插入图片描述

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

在这里插入图片描述

Consumer 消费型接口

在这里插入图片描述

Supplier 供给型接口

在这里插入图片描述

13、Stream流式计算

什么是Stream流式计算

大数据:存储 + 计算 集合、MySQL 本质就是存储东西的; 计算都应该交给流来操作!

在这里插入图片描述

在这里插入图片描述

14、ForkJoin

什么是 ForkJoin

归并思想

ForkJoin 在 JDK 1.7 , 并行执行任务!提高效率。大数据量! 大数据:Map Reduce (把大任务拆分为小任务)

在这里插入图片描述

ForkJoin 特点:工作窃取

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

在这里插入图片描述

15、异步回调

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

16、JMM

请你谈谈你对 Volatile 的理解

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

  1. 保证可见性

  2. 不保证原子性

  3. 禁止指令重排

什么是JMM

JMM : Java内存模型,不存在的东西,概念!约定!

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

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

8种操作:

在这里插入图片描述

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

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

在这里插入图片描述

17、Volatile

1、保证可见性

在这里插入图片描述

2、不保证原子性

原子性 : 不可分割 线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。

在这里插入图片描述

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

在这里插入图片描述

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

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

这些类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在!

指令重排

什么是 指令重排:你写的程序,计算机并不是按照你写的那样去执行的。 源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行 处理器在进行指令重排的时候,考虑:数据之间的依赖性!

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

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

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

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

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

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

volatile可以避免指令重排:

内存屏障。CPU指令。作用: 1、保证特定的操作的执行顺序! 2、可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)

在这里插入图片描述

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

18、彻底玩转单例模式

饿汉式 DCL懒汉式,深究!

饿汉式

在这里插入图片描述

DCL 懒汉式

在这里插入图片描述

在这里插入图片描述

静态内部类

在这里插入图片描述

单例不安全,反射 枚举

在这里插入图片描述

在这里插入图片描述

枚举类型的最终反编译源码:

在这里插入图片描述

19、深入理解CAS

什么是 CAS

大厂你必须要深入研究底层!有所突破! 修内功,操作系统,计算机网络原理

在这里插入图片描述

Unsafe 类

在这里插入图片描述

在这里插入图片描述

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就

一直循环!

缺点:

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

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

在这里插入图片描述

在这里插入图片描述

20、原子引用

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

带版本号 的原子操作!

在这里插入图片描述

在这里插入图片描述

注意:
Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

21、各种锁的理解

1、公平锁、非公平锁

公平锁: 非常公平, 不能够插队,必须先来后到! 非公平锁:非常不公平,可以插队 (默认都是非公平)

2、可重入锁

可重入锁(递归锁)

在这里插入图片描述

Synchronized

在这里插入图片描述

Lock 版

在这里插入图片描述

3、自旋锁

spinlock

在这里插入图片描述

我们来自定义一个锁测试

在这里插入图片描述

测试

在这里插入图片描述

4、死锁

死锁是什么

在这里插入图片描述

死锁测试,怎么排除死锁:

在这里插入图片描述

解决问题 1、使用 jps -l 定位进程号 在这里插入图片描述

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

  • 3
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值