JUC并发编程---狂神说Java学习笔记

JUC并发编程

狂神说Java-JUC并发编程学习视频地址

解释归属备注
util
Callable可赎回concurrent
concurrent并发
TimeUnit时间单位
DAYSTimeUnit
SECONDSTimeUnit
Condition条件,精准通知lock
Reentrant可重入的lock
notify all通知所有人synchronized
await等待(加锁)Condition
signal信号(解锁)Condition
Vector矢量集合锁
Collections收藏集合锁
CopyOnWriteArrayList<>写入时复制集合锁
call呼叫Callable
Future tas适配类Runnable
CountDownLatch减计数器(倒计时器)辅助类
CyclicBarrier加法计数器辅助类
Semaphore信号量辅助类
acquire()得到Semaphore
release(释放Semaphore
ReadWriteLock读写锁lock
Queue排队
lockingQueue阻塞阻塞队列
ArrayDeque双端队列阻塞队列
BlockingQueue阻塞队列FIFO
Array数组
remove去除ArrayBlockingQueue抛出异常,boolean
element首元素ArrayBlockingQueue抛出异常,boolean
offer给与ArrayBlockingQueue不抛,有返回值
poll得到不抛,有返回值
peek偷看不抛,有返回值
take阻塞,等待
SynchronousQueue同步队列阻塞队列写<->读
Executors执行器线程池
SingleExecutors单线程
Fixed固定Executors固定大小
Cached缓存Executors可伸缩
shutdown关闭Executors线程池关闭
ThreadPoolExecutor线程池执行器ThreadPoolExecutor
corePoolSize核心线程池大小ThreadPoolExecutor
maximumPoolSize最大线程池大小ThreadPoolExecutor
keepAliveTime保持活动时间ThreadPoolExecutor
workQueue阻塞队列大小ThreadPoolExecutor
Rejectedr…Handler拒绝策略ThreadPoolExecutor拒绝后的动作
AbortPolicy终止策略Rejected…Handler不处理,抛出
CallerRuns呼叫方运行哪来去哪去
Discard丢弃丢弃,不抛异常
DiscardOldest丢弃最旧的和最早的试竞争,不抛
execute执行ThreadPoolExecutor
Runtime运行时
availableProcessors可用处理器获取电脑的核数
foreach函数包foreach
function函数接口foreach输入值1,返回值1
Predicate断定型接口foreach输入值1,返回boolean
Consumer消费型接口foreach只有输入,没有返回
Supplier供给型接口foreach没有参数,只有返回值
filter过滤器stream继承Predicate
map绘制stream继承Function
toUpperCase改为大写stream
sorted分类stream继承Consumer
limit限量stream
forEach对于每个stream继承Consumer
ForkJoin双端队列ForkJoin工作窃取
fork交叉ForkJoin把任务压入线程列队
join参加ForkJoin结果合并
ForkJoinTaskForkJoin父类ForkJoin任务要继承
ForkJoinPoolForkJoin执行ForkJoin
.execute计算任务:执行ForkJoin执行没有返回结果
.submit计算任务:提交ForkJoin执行有返回结果
rangeClosed范围已关闭,半包括Stream
parallel并行Stream
reduce归约计算Stream
Future异步回调,未来Future未来建模
CompletableFuture完全未来Future比较常用的实现类
runAsync异步运行Future没有返回值
supplyAsync异步供给Future有返回值
whenComplete完成时Future正常,错误
Volatile不稳定JMM同步机制
Read读取内存交互操作
Load载入内存交互操作
Use使用内存交互操作
assign赋值内存交互操作
store存储内存交互操作
activeCount活动计数Thead
enum枚举
compareAndSet比较并交换CAS
expect期望值CAS
update更新值CAS
getStamp获得版本号CAS
ReentrantLock公平锁各种锁
spinlock自旋锁各种锁

准备工作

确认IEDA jdk版本号

在这里插入图片描述![在这里插入图片描述](https://img-blog.csdnimg.cn/20210705093845509.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3J6ejY1NDUyMDY0,size_16,color_FFFFFF,t_70
在这里插入图片描述

  • 修改IDEA中准备环境

1、什么是JUC

学习方向:源码+官方文档,面试高频问

在这里插入图片描述

图 官方文档java.util

java.util 工具包、包、分类

业务:普通的线程代码 Thread

Runnable 没有返回值、效率相比 Callable 相对较低!

在这里插入图片描述

图 Callable

在这里插入图片描述

图 Lock

  • 以前多线程所学的 CallableLock 都存在于java.util.concurrent包下

2、线程和进程

线程、进程,如果不能使用一句话说出来的技术,不扎实!

进程:一个程序,QQ.exe Music.exe 程序的集合;

一个进程往往可以包含多个线程,至少包含一个!

Java默认有几个线程?2个 mian、GC

线程例如:开了一个进程Typora,写字,自动保存,这些功能都是线程负责

对于Java而言:Thread、Runable、Callable之前我们使用这三个开启线程。

提问?JAVA真的可以开启线程吗? 开不了的!

start调用的 native 是本地方法,属于底层的 C++,Java 无法直接操作硬件

代码:start源码

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 */
            }
        }
    }

    private native void start0();

并发、并行

并发编程:并发、并行

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

  • CPU 只有一核,模拟出来多条线程,天下武功,唯快不破。那么我们就可以使用CPU快速交替,来模拟多线程。

并行: 多个人一起行走

  • CPU多核,多个线程可以同时执行。 我们可以使用线程池

代码:获取CPU的核数

public class Test1 {
    public static void main(String[] args) {
        //获取CPU的核数
        //CPU 密集型,IO密集型
        System.out.println(Runtime.getRuntime().availableProcessors());
    }
}

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

程序就是在现有的基础上更有效的利用CPU的资源

线程有几种状态

6种

代码:线程状态源码

   * @since   1.5
     * @see #getState
     */
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
       //新生
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
       //运行
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
       //阻塞
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
       //等待,一直等
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
       //超时等待,限时等待
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
       //终止
        TERMINATED;
    }

wait / sleep 区别

  1. 来自不同的类

    wait => Object

    sleep => Thread

    一般情况企业中使用休眠是:

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

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

  2. 关于锁的释放

​ wait 会释放锁;

​ sleep睡觉了,不会释放锁;

  1. 使用的范围是不同的

    wait 必须在同步代码块中;

    sleep 可以在任何地方睡;

  2. 是否需要捕获异常

    wait是不需要捕获异常;

    sleep必须要捕获异常;

3、Lock锁(重点)

传统的Synchronized

多线程 学习阶段 vs 公司开发

学习阶段的多线程

代码:多线程开启

package JVMclass.lock01;

/*
* 学习阶段的多线程
* 1.创造一个多线程
* 2.重写run方法
* 3.new Thread开启
* 4....     
*/
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        new Thread(new MyThread()).start();
    }

}
class MyThread implements Runnable{
    @Override
    public void run() {

    }
}

公司开发生成资源类,再用多线程开启其中的某一方法

代码:资源类多线程开启

package JVMclass.lock01;
//卖票系统
/*
* 真正的多线程开发,公司中的开发,降低耦合性
* 线程就是一个单独的资源类,没有任何附属的操作
* 1.属性、方法
*/
public class SaleTicketDemo01 {
    public static void main(String[] args) {
        //并发:多线程操作同一个资源类
        Ticket ticket = new Ticket();

        //@FunctionalInterface 函数式接口,jdk1.8 lambda表达式(参数)->{代码},”name“
        new Thread(()->{
            for (int i = 0; i < 60; i++) {
                ticket.sale();
            }
            },"A").start();
        new Thread(()->{ for (int i = 0; i < 60; i++) {
            ticket.sale();
        }},"B").start();
        new Thread(()->{ for (int i = 0; i < 60; i++) {
            ticket.sale();
        }},"C").start();
    }
}
//资源类 OOP面向对象编程
//如果直接implements Runnable接口,他将只是单独的线程了,会有较高的耦合性

class Ticket {
    //属性 方法
    private  int number =50;
    //卖票的方式
    //synchronized本质:排队、锁
    public synchronized void sale(){
        if(number>0){
            System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);

        }
    }
}

Lock 接口

在这里插入图片描述

图 Lock帮助文档

  • Condition (精确通知)

  • Lock 锁

  • ReadWriteLock 读写锁

Lock 锁

在这里插入图片描述

图 加锁,解锁使用格式

在这里插入图片描述

图 Lock 三种实现类

  • ReentrantLock 可重入锁(常用)

  • ReadLock 读锁

  • WriteLock 写锁

ReentrantLock 可重入锁(常用)

在这里插入图片描述

图 ReentrantLock源码,公平锁,非公平锁

公平锁: 十分公平,必须先来后到~;

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

代码:ReentrantLock 可重入锁

package com.juc.test;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Test1 {

        public static void main(String[] args) {
            //并发:多线程操作同一个资源类,把资源类丢入线程
            Ticket2 ticket = new Ticket2();
            //简化
            new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"A").start();
            new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"B").start();
            new Thread(()->{ for (int i = 0; i < 60; i++) ticket.sale(); },"C").start();

        }

    //Lock三部曲
    /*
    * 1.new ReentrantLock();
    * 2. .lock();   //加锁
    * 3. .unlock(); //解锁    (解锁需要在代码块中)
     */
    static class Ticket2 {
        //属性,方法
        private  int number =50;

        Lock lock = new ReentrantLock();

        public synchronized void sale(){
            lock.lock();    //加锁

            try {
                //业务代码
                if(number>0){
                    System.out.println(
                            Thread.currentThread().getName()+"卖出了第"+(number--)+"票,剩余:"+number);
                }

            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                lock.unlock();  //解锁
            }
        }
    }
}

Synchronized 和 Lock区别

1、Synchronized 内置的Java关键字,Lock是一个Java类

2、Synchronized 无法判断获取锁的状态,Lock可以判断

3、Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

4、Synchronized 线程1(获得锁->阻塞)、线程2(等待->一直等待);

​ lock就不一定会一直等待下去,lock会有一个trylock去尝试获取锁,不会造成长久的等待。

5、Synchronized 是可重入锁,不可以中断的,非公平的;

​ Lock,可重入的,可以判断锁,可以自己设置公平锁和非公平锁;

6、Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码;

锁是什么?如何判断锁的是谁

4、生成者和消费者问题

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

代码:生产,消费,+1,-1

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();
}
}
/*
* 1.当程序第一次过来number = 0时,如果执行B~decrement(),由于if成立,B等待,不能执行。
* 经过increment(),发现if不成立,所以直接++,并通知其他线程可执行,B~decrement()解冻。
* 2.第二次当number = 1时,经过如果A~increment(),if成立,所以A等待。
* B执行,经过decrement(),if不成立,所以--,并解冻A~increment()
* 3.第三次number = 0,由于A~increment()已经被解冻,并且if不成立,所以++
*...
*/
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线程,现在如果我有四个线程A B C D!

当四个线程时,或更多线程时,使用 if 时会出现虚假唤醒,多次加、减

解决方案: if 改为while即可,防止虚假唤醒

在这里插入图片描述

图 虚假唤醒问题

代码:if 改为while,防止虚假唤醒

package com.juc.test.PC;

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();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"C").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"D").start();
    }
}
class Data{
    //数字  资源类
    private int number = 0;
    //+1
    public synchronized void increment() throws InterruptedException {
        while (number!=0){
            //等待操作
            this.wait();
        }
        number++;
        System.out.println(Thread.currentThread().getName()+"=>"+number);
        //通知其他线程 我+1完毕了
        this.notifyAll();
    }

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

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

通过Lock 找到 Condition

在这里插入图片描述

图 Synchronized等待通知,Lock的Condition等待通知

在这里插入图片描述

图 Condition 内的Lock 等待通知

代码 Lock Condition 等待 通知

package com.juc.test.PC;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class B  {
    public static void main(String[] args) {
        Data2 data = new Data2();
        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();
        new Thread(()->{for(int i=0;i<12;i++) {
            try {
                data.increment();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"C").start();
        new Thread(()->{for(int i=0;i<10;i++) {
            try {
                data.decrement();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }},"D").start();
    }
}
class Data2{
    private int number = 0;

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    //condition.await(); 加锁
    //condition.signal(); 解锁

    public  void increment() throws InterruptedException {
        lock.lock();
        try {
            //业务代码
            while (number!=0){
                condition.await();
            }
            number++;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    //-1
    public  void decrement() throws InterruptedException {
        lock.lock();    //加锁
        try {
            while (number==0){
                condition.await();  //等待
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"=>"+number);
            condition.signalAll();    //通知
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();  //解锁
        }
    }
}//signalAll();全部唤醒,.signal指定唤醒

问题:

  • Lock 和 Synchronized 并无两样
  • 多线程是随机的,多为先运行AB,再运行CD,但我们想让它A,B,C,D有序的运行
  • 所以…

任何一个新的技术,绝对不是仅仅为了覆盖原来的技术,它会更有优势,或得到了补充

Condition的优势:精准的通知和唤醒的线程!

如果我们要指定通知的下一个进行顺序怎么办呢? 我们可以使用Condition来指定通知进程~

代码:用Condition来指定通知进程

/**
 * A 执行完 调用B
 * B 执行完 调用C
 * C 执行完 调用A
   */
public class C {
public static void main(String[] args) {
    Data3 data3 = new Data3();
    new Thread(()->{
        for(int i=0;i<10;i++){
            data3.printA();
        }
    },"A").start();
    new Thread(()->{
        for(int i=0;i<10;i++){
            data3.printB();
        }
    },"B").start();
    new Thread(()->{
        for(int i=0;i<10;i++){
            data3.printC();
        }
    },"C").start();
}
}

class Data3{
    //资源类
    private Lock lock=new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int number = 1; //1A 2B 3C
    public void printA(){
    lock.lock();
    try {
        //业务 判断 -> 执行 -> 通知
        while(number!=1){
            //等待
            condition1.await();
        }
        //操作
        System.out.println(Thread.currentThread().getName()+"-》AAAAAA");
        //唤醒指定的线程
        number=2;
        condition2.signal(); // 唤醒2

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}
public void printB(){
    lock.lock();
    try {
        //业务 判断 -> 执行 -> 通知
        while (number!=2){
            condition2.await();
        }
        System.out.println(Thread.currentThread().getName()+"-》BBBBBB");
        //唤醒3
        number=3;
        condition3.signal();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
}
public void printC(){
    lock.lock();
    try {
        //业务 判断 -> 执行 -> 通知
        while(number!=3){
            condition3.await();
        }
        System.out.println(Thread.currentThread().getName()+"-》CCCCCC");
        //唤醒1
        number=1;
        condition1.signal();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        lock.unlock();
    }
  }
}
    

5、8锁现象

如何判断锁的是谁!锁到底锁的是谁?

锁会锁住:对象、Class

深刻理解我们的锁

  • 问题1:先“发短信”还是先“打电话”?
  • 问题2:“发短信“有延迟后,先“发短信”还是先“打电话”?

代码:synchronized,发短信,打电话

//先“发短信”还是先“打电话”?import java.util.concurrent.TimeUnit;public class Test1 {    public static void main(String[] args) {        //同一个锁        Phone phone = new Phone();        new Thread(()->{phone.sendSms();},"A").start();        try {            TimeUnit.SECONDS.sleep(1);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        new Thread(()->{phone.call();},"B").start();    }}class Phone{    //synchronized 锁的对象是方法的调用者    //两个方法用的是同一个锁,谁先拿到谁先执行    public synchronized void sendSms(){        try {            TimeUnit.SECONDS.sleep(4);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("发短信");    }    public synchronized void call(){        System.out.println("打电话");    }}

为什么即使又了延迟,还一直都是先“发短信”后“打电话”呢?

由于“A”和“B”同时使用的一个锁,所以谁先获得锁,谁就会先执行

  • 问题3:一个延迟锁方法“发短信”,一个普通方法“hello”,谁先执行
  • 问题4:两个对象,先“发短信”还是先“打电话”?

代码:两把锁

package com.juc.test.Lock8;import java.util.concurrent.TimeUnit;public class Test1 {    public static void main(String[] args) {        //两个对象        Phone phone1 = new Phone();        Phone phone2 = new Phone();        new Thread(()->{phone1.sendSms();},"A").start();        try {            TimeUnit.SECONDS.sleep(1);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        new Thread(()->{phone2.call();},"B").start();    }}class Phone{    public synchronized void sendSms(){        try {            TimeUnit.SECONDS.sleep(4);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("发短信");    }    public synchronized void call(){        System.out.println("打电话");    }    public void hello(){        System.out.println("hello");    }}

当出现普通方法和两把锁时,就不再受锁的影响,由于“发短信”有延迟,所以都是后者先执行

  • 问题5:增加static,先“发短信”后“打电话”?
  • 问题6:增加static,有两个对象,先“发短信”后“打电话”?

代码:static 静态同步方法

import java.util.concurrent.TimeUnit;public class Test3 {    public static void main(String[] args) {        //两个对象的class类模板只有一个,static,锁的是class        Phone3 phone1 = new Phone3();        Phone3 phone2 = new Phone3();        new Thread(()->{phone1.sendSms();},"A").start();        try {            TimeUnit.SECONDS.sleep(1);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        new Thread(()->{phone2.call();},"B").start();    }}class Phone3{    //synchronized 锁的对象是方法的调用者    //static 静态方法 是在类一开始加载的时候,就已经存在了    //锁的是Class    //hone3 唯一的一个Class对象,所以类似使用着同一把锁    public static synchronized void sendSms(){        try {            TimeUnit.SECONDS.sleep(4);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("发短信");    }    public static synchronized void call(){        System.out.println("打电话");    }}

synchronized 锁的对象是方法的调用者,static 静态方法 是在类一开始加载的时候,就已经存在了,锁的是Class,hone3 唯一的一个Class对象,所以类似使用着同一把锁

  • 问题7:一个static 静态同步方法,一个普通同步方法,同一个对象,先“发短信”后“打电话”?
  • 问题8:一个static 静态同步方法,一个普通同步方法,二个对象,先“发短信”后“打电话”?

代码:静态同步方法和普通同步方法,两个对象

import java.util.concurrent.TimeUnit;public class Test4 {    public static void main(String[] args) {        Phone4 phone1 = new Phone4();        Phone4 phone2 = new Phone4();        new Thread(()->{phone1.sendSms();},"A").start();        try {            TimeUnit.SECONDS.sleep(1);  //等待时间        } catch (Exception e) {            e.printStackTrace();        }        new Thread(()->{phone2.call();},"B").start();    }}class Phone4{    //静态的同步方法 锁的是 Class 类模板    public static synchronized void sendSms(){        try {            TimeUnit.SECONDS.sleep(4);        } catch (Exception e) {            e.printStackTrace();        }        System.out.println("发短信");    }    //普通的同步方法 锁的是调用者    public  synchronized void call(){        System.out.println("打电话");    }}

两个都先执行“打电话”,由于不管是静态的同步方法和普通的同步方法,还是两个对象,它们都使用的不同的锁,后者不需要等待第一个执行完再执行,由于延迟影响,所以先执行后者

小结

new 出来的 this 是具体的一个对象

static Class 是唯一的一个模板

6、集合类不安全

6.1、List不安全

代码:单线程集合传统写法

import java.util.Arrays;
import java.util.List;

public class ListTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("1", "2", "3");
        list.forEach(System.out::println);
    }

代码:ArrayList<>多线程集合,但是报异常

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

解决方案:

  1. 改为使用 Vector<> List<String> lsit = new Vector<>();
  2. 使用 Collections.synchronizedList 工具类转换List<String> lsit = Collections.synchronizedList(new ArrayList<>());
  3. 使用 Lock 锁List<String> lsit = new CopyOnWriteArrayList<>();

在这里插入图片描述

图 CopyOnWriteArrayList 帮助文档

  • CopyOnWriteArrayList:写入时复制! COW 计算机程序设计领域的一种优化策略
  • 多个线程调用的时候,list,读取的时候是固定的,但写入(存在覆盖操作);
  • 在写入的时候避免覆盖,造成数据错乱的问题;
  • 读写分离的思想

CopyOnWriteArrayList比Vector厉害在哪里?

  • Vector底层是使用synchronized关键字来实现的:效率特别低下。

  • CopyOnWriteArrayList使用的是Lock锁,效率会更加高效!

小狂神的学习方法推荐:1、先会用;2、货比三家,寻找其他解决方案;3、分析源码

6.2、Set 不安全

代码:HashSet<>()多线程,报异常java.util.ConcurrentModificationException

public class SeyTset {
    public static void main(String[] args) {

        Set<String> set = new HashSet<>();

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

解决方案:

  1. 使用工具类转换Set<String> set = Collections.synchronizedSet(new HashSet<>());
  2. 使用 Lock 锁Set<String> set = new CopyOnWriteArraySet<>();

HashSet底层是什么?

//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();

6.3、Map 不安全

回顾map的基本操作

Map<String, String> map = new HashMap<>(16,0.75);

  • map 是这样用的吗? 不是,工作中不使用这个

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

//异常java.util.ConcurrentModificationException 并发修改异常
public static void main(String[] args) {
        
        Map<String, String> map = new HashMap<>();
        //加载因子、初始化容量
        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<>());处理;
  • 使用ConcurrentHashMap进行并发处理

研究ConcurrentHashMap底层原理

7、Callable(简单)

在这里插入图片描述

图 Callable 帮助文档

1、可以有返回值;
2、可以抛出异常;
3、方法不同,run()/call()

代码测试

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class callableTest {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //Runnable 方法
        // new Thread(new MyThread()).start();
        
        /*Thread 接口只有Runnable,
        但Runnable 无法和Callable 相连,怎么启动 Callable?
        利用 FutureTas 适配类
        Runnable 可以和 FutureTask 相连
        FutureTask 可以和 Callable 相连
        new Thread(new FutureTask<>()).start();
        new Thread(new FutureTask<>(Callable)).start();
         */

        MyThread thread = new MyThread();
        FutureTask futureTask = new FutureTask(thread); //适配类
        new Thread(futureTask,"A").start();
		//new Thread(futureTask,"B").start();
        //当两个线程启动时,只会打印出一个,结果会被缓存,提高效率
        
        //获取 callable 的返回结果
        Integer o = (Integer) futureTask.get();
        //如果返回值是比较耗时的操作,这个get 方法可能会产生阻塞,所以把他放到最后
        //或者使用异步通信来处理
        System.out.println(o);
    }
}
class MyThread implements Callable<Integer> {
    //泛型的参数,等于方法的返回值

    public Integer call() {
        System.out.println("call");
        return 1024;
    }
}

FutureTask 适配类
在这里插入图片描述
在这里插入图片描述

图 FutureTask 帮助文档

FutureTask 可以将new Thread(new FutureTask<>(Callable)).start();

FutureTask细节:

  • 有缓存
  • 结果可能需要等待,会阻塞

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

8.1、 CountDownLatch

“减法计数器”

在这里插入图片描述

图 CountDownLatch 帮助文档

package com.juc.test;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    public static void main(String[] args) throws InterruptedException {
        //总数是6,必须要执行任务的时候,再使用
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"Go out");
                countDownLatch.countDown(); //数量-1
            },String.valueOf(i)).start();	//基本数据型态转换成 String
        }
        countDownLatch.await();     //等待计数器归零,然后再向下执行

        System.out.println("Close Door");
    }
}

原理

countDownLatch.countDown(); 数量-1

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

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

8.2、CyclicBarrier

在这里插入图片描述

图 CyclicBarrier 帮助文档

“加法计数器”

package com.juc.test.callableClass;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

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;     //由于Thread不能直接获取i,所以需要final进行转换
            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();
        }
    }
}

如果 for 循环结束都没达到计数器要求,程序就会一直卡在哪里

final int temp = i;由于 for()和 Thread()作用域的关系,i 不能直接在线程中使用,需要使用 final 修饰变成常量(还是不太懂)

分开两次 for 循环

package com.juc.test.callableClass;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {
    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
            System.out.println("召唤龙珠成功!");
        });
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;     //由于Thread不能直接获取i,所以需要final进行转换
            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();
        }
        for (int i = 6; 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();
        }
    }
}

8.3 、Semaphore

Semaphore:信号量

在这里插入图片描述

图 Semaphore 帮助文档

package com.juc.test.callableClass;

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

public class SemaphoreDemo {
    public static void main(String[] args) {
        //线程数量:停车位,主要功能限流
        Semaphore semaphore = new Semaphore(3);

        for (int i = 1; i <=6 ; i++) {
            new Thread(()->{
                //acquire() 得到
                //release() 释放
                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 {
                    semaphore.release();
                }

            },String.valueOf(i)).start();
        }
    }
}

原理

emaphore.acquire(); 获得,假设如果已经满了,等待,等待被释放为止

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

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

9、读写锁

ReadWriteLock

在这里插入图片描述

先对于不加锁的情况:

如果我们做一个我们自己的cache缓存。分别有写入操作、读取操作;

我们采用五个线程去写入,使用十个线程去读取。
我们来看一下这个的效果,如果我们不加锁的情况!

不加锁

import java.util.HashMap;
import java.util.Map;

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        //写
        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            new Thread(()->{
            myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        //读取
        for (int i = 1; i <=7 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }

}

//自定义缓存
class MyCache{
    private  volatile Map<String,Object> map = new HashMap<>();

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

    //取,写
    public void get(String key){
        System.out.println(Thread.currentThread().getName()+"读取"+key);
        Object o = map.get(key);
        System.out.println(Thread.currentThread().getName()+"读取OK");
    }
}

写入没有按顺序完成写入,会被其他线程插入其中

ReadWriteLock 锁

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/*	
	独占锁 (写锁) 一次只能被一个线程占用
	共享锁	(读锁) 多个线程可以同时占有
	ReadWriteLock
	读 - 读 可以共存
	读 - 写 不能共存
	写 - 写 不能共存
*/

public class ReadWriteLockDemo {
    public static void main(String[] args) {
        MyCacheLock myCache = new MyCacheLock();
        //写
        for (int i = 1; i <=10 ; i++) {
            final int temp = i;
            new Thread(()->{
            myCache.put(temp+"",temp+"");
            },String.valueOf(i)).start();
        }

        //读取
        for (int i = 1; i <=10 ; i++) {
            final int temp = i;
            new Thread(()->{
                myCache.get(temp+"");
            },String.valueOf(i)).start();
        }
    }
}

class MyCacheLock{
    private  volatile Map<String,Object> map = new HashMap<>();
    //private Lock lock = new ReentrantLock(); 旧模式加锁
    //应该使用读写锁更加细粒度的控制
    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();
        }

    }

}

synchronized 和 Lock 锁都可以解决,但是没有 ReadWriteLock 更细粒

10、阻塞队列

10.1、阻塞 队列

在这里插入图片描述

图 阻塞 队列 原理

在这里插入图片描述

图 BlockingQueue 帮助文档

BlockingQueue 不是新的东西

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

  • 多线程并发处理
  • 线程池

在这里插入图片描述

图 BlockingQueue上端Queue 帮助文档

ArrayDeque 双端队列,就是可以两端对向输出

在这里插入图片描述

图 BlockingQueue树

学会使用队列

添加、移除

10.2、四组API

  1. 抛出异常
  2. 不会抛出异常
  3. 阻塞 等待
  4. 超时 等待
方式抛出异常不会抛出异常,有返回值阻塞 等待超时 等待
添加addofferputoffer
移除removepolltakepoll
检查队列首元素elementpeek--

抛出异常解决方案

package com.juc.test.bq;

import java.util.concurrent.ArrayBlockingQueue;

public class Test {
    public static void main(String[] args) {
        new Test().test1();
    }
    /*
        抛出异常
     */
    public  void test1(){
        //队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        //blockingQueue.add 返回一个boolean 如果添加成功返回 true
        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
        //没有可添加的位置抛出异常:IllegalStateException: Queue full 队列已满
        //System.out.println(blockingQueue.add("D"));
        System.out.println("====================");
        
        //查看队首元素
        System.out.println(blockingQueue.element());
        
        //弹出 队列先进先出
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        //没有可移除的元素抛出异常:NoSuchElementException 没有元素的
        System.out.println(blockingQueue.remove());
    }
}

不会抛出异常,有返回值

 /*
        不会抛出异常,有返回值
     */
public void test2(){
        //队列的大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("b"));
        System.out.println(blockingQueue.offer("c"));
        //没有可添加的位置:false 不抛出异常
        //System.out.println(blockingQueue.offer("d"));
    
    	//查看队首元素
        System.out.println(blockingQueue.peek());
    
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        //没有可移除的元素:null 不抛出异常
        //System.out.println(blockingQueue.poll());
      
    }

阻塞 等待

public void test3() throws InterruptedException {
    //队列的大小
    ArrayBlockingQueue 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 void test4() throws InterruptedException {
    //队列的大小
    ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
    blockingQueue.offer("a");
    blockingQueue.offer("b");
    blockingQueue.offer("c");
    //等待超过2秒就退出 添加内容 秒数 时间单位
    blockingQueue.offer("d",2, TimeUnit.SECONDS);
    System.out.println("===============");
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    System.out.println(blockingQueue.poll());
    //等待超过2秒就退出
    blockingQueue.poll(2,TimeUnit.SECONDS);
}

10.3、同步队列

SynchronousQueue 同步队列

import java.util.concurrent.BlockingDeque;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

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

        //put()  写
        //take() 读

        //写
        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();
            }
        },"T2").start();

    }
}

学了技术,不会用?主要还是看得少,做的少

多次测试后发现会有多次put输出情况
在这里插入图片描述

bibi[彳亍口巴粉了]解释:

在这里插入图片描述

我理解:

  • T1-sout1 打印—

  • T1-put1 执行

  • T2-=>1 输出

  • T1-sout2 打印—

  • T1-put2 执行

  • T1-sout3 打印–

  • T1-put3 阻塞(T1执行两次,打印两次,但实际put3阻塞中)

  • T2-=>2 输出

  • T1-put3 执行 (没有返回值,看不出来)

  • T2-=>2 输出 (输出两次)

11、线程池(重点)

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

池化技术

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

线程池、JDBC的连接池、内存池、对象池 等等。。。

资源的创建、销毁十分消耗资源,尽量减少开与关

  • 最小池 :2
  • 最大池:max

**池化技术:**事先准备好一些资源,如果有人要用,就来我这里拿,用完之后还给我,以此来提高效率

线程池的好处:

1、降低资源的消耗

2、提高响应的速度

3、方便管理

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

11.1、线程池

线程池:三大方法

在这里插入图片描述

图 阿里巴巴java开发规范

ExecutorService threadPool = Executors.newSingleThreadExecutor();单个线程
ExecutorService threadPool2 = Executors.newFixedThreadPool(5); 创建一个固定的线程池的大小
ExecutorService threadPool3 = Executors.newCachedThreadPool();可伸缩的

单个线程

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {
    public static void main(String[] args) {
        ExecutorService service = Executors.newSingleThreadExecutor();  //单个线程
        //Executors.newFixedThreadPool(5);  //创建一个固定的线程池,数量大小
        //Executors.newCachedThreadPool();  //可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i <10 ; i++) {
                //使用了线程池之后,使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            service.shutdown();
        }
    }
}

多线程

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {
    public static void main(String[] args) {
       // ExecutorService service = Executors.newSingleThreadExecutor();  //单个线程
        ExecutorService service =Executors.newFixedThreadPool(5);  //创建一个固定的线程池,数量大小
        //Executors.newCachedThreadPool();  //可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i <10 ; i++) {
                //使用了线程池之后,使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            service.shutdown();
        }
    }
}

可变线程

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Demo01 {
    public static void main(String[] args) {
        //ExecutorService service = Executors.newSingleThreadExecutor();  //单个线程
        //ExecutorService service =Executors.newFixedThreadPool(5);  //创建一个固定的线程池,数量大小
        ExecutorService service =Executors.newCachedThreadPool();  //可伸缩的,遇强则强,遇弱则弱

        try {
            for (int i = 0; i <100 ; i++) {
                //使用了线程池之后,使用线程池来创建线程
                service.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            service.shutdown();
        }
    }
}

11.2、7大参数

源码分析,ThreadPoolExecutor

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>());
    }

public ThreadPoolExecutor(int corePoolSize,		//核心线程池大小
                              int maximumPoolSize,	//最大核心线程池大小
                              long keepAliveTime,	//超时,无人调用Mox线程池就被释放
                              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;
    }

7大参数示意图

在这里插入图片描述

  • 最先开发核心Core区域运行
  • 多余的人在阻塞区队列
  • 待阻塞区满后,开启Max区运行
  • 待Core和Max区和阻塞队列都满以后,再进入的人就将被进行拒绝策略

11.3、手动创建线程池

package com.juc.test.bq;

import java.util.concurrent.*;
/*
ThreadPoolExecutor.AbortPolicy());  //拒绝策略,不处理这个人并抛出异常
ThreadPoolExecutor.CallerRunsPolicy());  //那来的去哪里,如果是主线程,就被main方法输出
ThreadPoolExecutor.DiscardPolicy());  //队列满了,丢掉任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了, 尝试去和最早的竞争,也不会抛出异常
 */

public class Demo01 {
    public static void main(String[] args) {
        //自定义线程池 !ThreadPoolExecutor()
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                3,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3) ,   //阻塞队列
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardOldestPolicy());  //队列满了, 尝试去和最早的竞争,也不会抛出异常

        try {
            for (int i = 1; i <=11 ; i++) {
                //使用了线程池之后,使用线程池来创建线程
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName()+" ok");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        } finally {
            //线程池用完,程序结束,关闭线程池
            threadPool.shutdown();
        }
    }
}

11.4、四种拒绝策略

在这里插入图片描述

ThreadPoolExecutor.AbortPolicy()); 拒绝策略,不处理这个人并抛出异常
ThreadPoolExecutor.CallerRunsPolicy()); 那来的去哪里,如果是主线程,就被main方法输出
ThreadPoolExecutor.DiscardPolicy()); 队列满了,丢掉任务,不会抛出异常
ThreadPoolExecutor.DiscardOldestPolicy()); 队列满了, 尝试去和最早的竞争,也不会抛出异常

11.4、CPU密集型和IO密集型!:(调优)

如何去设置线程池的最大大小如何去设置

1、CPU密集型:电脑的核数是几核就选择几;选择maximunPoolSize的大小

System.out.println(Runtime.getRuntime().availableProcessors());

2、I/O密集型:

在程序中有15个大型任务,io十分占用资源;I/O密集型就是判断我们程序中十分耗I/O的线程数量,大约是最大I/O数的一倍到两倍之间。

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

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

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

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}
  • @FunctionalInterface 高级多
  • 简化编程模式,在新版本的框架底层大量应用
  • foreach 函数包(消费者类函数接口)

在这里插入图片描述

12.1、Function函数式接口

Function 函数式接口,有一个输入参数,有一个输出

在这里插入图片描述

import java.util.function.Function;

/*
    Function 函数式接口,有一个输入参数,有一个输出
    只要是函数型接口 可以 用 Lambda 表达式简化
 */

public class Demo01 {
    public static void main(String[] args) {
       /*
        Function function = new Function<String,String>() {
        @Override
            public String apply(String str) {
                return str;
            }
        };
        */
        Function<String,String> function = (str)->{
            return str;
        };
        System.out.println(function.apply("asb"));
    }
}

12.2、Predicate断定型接口

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

import java.util.function.Predicate;

public class Demo02 {
    //断定型接口:有一个输入参数,返回值只能是 布尔值
    //判断字符串是否为空
    public static void main(String[] args) {
        /*Predicate <String> predicate = new Predicate<String>() {
            @Override
            public boolean test(String str) {
              if (str.isEmpty()){

                }
                return str.isEmpty();
            }
        };*/
        Predicate <String> predicate =str->{
            return str.isEmpty();
        };

        System.out.println(predicate.test(""));
    }
}

12.3、Consumer消费型接口

在这里插入图片描述

package JVMclass.function;

import java.util.function.Consumer;

/*
    Consumer 消费型接口:只有输入,没有返回值
 */
public class Demo03 {
    public static void main(String[] args) {
//        Consumer <String> consumer = new Consumer<String>() {
//           @Override
//            public void accept(String str) {
//                System.out.println(str);
//            }
//        };
        Consumer <String> consumer = str ->{
            System.out.println(str);
        };
        consumer.accept("sad");
    }
}

12.4、Supplier供给型接口

在这里插入图片描述


/*
    Supplier 供给型接口  没有参数 只有返回值
 */
public class Demo04 {
    public static void main(String[] args) {
        /*Supplier <Integer> supplier = new Supplier<Integer>() {
            @Override
            public Integer get() {
                System.out.println("get()");
                return 1024;
            }
        };*/

        Supplier <Integer> supplier = ()->{
            System.out.println("get()");
            return 1024;
        };
        System.out.println(supplier.get());
    }

}

13、Stream流式计算

什么是Stream流失计算

大数据:存储+计算!

存储:集合、MySQL(本质就是存储东西的)

计算:流式计算~(计算都应该交给流来操作)

在这里插入图片描述

//有参,get,set,toString
public class User {
    private int id;
    private String name;
    private int age;

    //有参构造器Constructor
    public User(int id, String name, int age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }
    //toString
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
    //get\set
    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

import java.util.Arrays;
import java.util.List;
/*
    1、ID 必须为偶数
    2、大于23age
    3、用户名转为大写字母
    4、用户名字母倒着排序
    5、只输出一个用户
 */

public class Test {
    public static void main(String[] args) {
        User u1 = new User(1,"a",21);
        User u2 = new User(2,"b",22);
        User u3 = new User(3,"c",23);
        User u4 = new User(4,"d",24);
        User u5 = new User(5,"e",25);
        User u6 = new User(6,"f",26);
        //集合就是存储
        List<User> list = Arrays.asList(u1,u2,u3,u4,u5,u6);

        //计算交给Stream流
        //Lambda 表达式、链式编程、函数式接口、Stream流式计算
        list.stream()
                //断定型接口:有一个输入参数,返回值只能是 布尔值
                .filter(u->{return u.getId()%2==0;})
                //断定型接口:有一个输入参数,返回值只能是 布尔值
                .filter(user ->{return user.getAge()>23;})
                //Function 函数式接口,有一个输入参数,有一个输出
                .map(user -> {return user.getName().toUpperCase();})//toUpperCase改为大写
                //Comparator比较器
                .sorted((uu1,uu2)->{return uu2.compareTo(uu1);})
                //限量
                .limit(1)
                //Consumer 消费型接口:只有输入,没有返回值
                .forEach(System.out::println);  //输出
    }
}

14、ForkJoin

什么是ForkJoin?

ForkJoin 在JDK1.7,并行执行任务!提高效率~。在大数据量速率会更快!

大数据中:Map Reduce 核心思想->把大任务拆分为小任务!

在这里插入图片描述

图 ForkJoi流程图

ForkJoin 特点: 工作窃取!

实现原理是:双端队列!从上面和下面都可以去拿到任务进行执行!

在这里插入图片描述

ForkJoin的操作

import java.util.concurrent.RecursiveTask;

/*
    求和计算的任务
    3000(普通for循环) 6000(ForkJoin) 9000(Stream并行流)
 */
/*
    如何使用forkJoin
    1.forkjoinPool 通过它来执行
    2.计算任务forkjoinPool.execute(ForkJoinTask task)
    3.计算类要继承ForkJoinTask
 */
public class forkJoinDemo extends RecursiveTask<Long> {
    private long start; //最小值
    private long end;   //最大值


    //临界值 切换ForkJoin的值
    private long temp = 100000L;

    public forkJoinDemo(long start, long end) {
        this.start = start;
        this.end = end;
    }

    public forkJoinDemo() {

    }

    //计算方法
    @Override
    protected Long compute() {
        if ((end-start)<temp){//小于临界值就直接用for循环计算
            long sum = 0;
            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();    //fork()拆分任务,把任务压入线程列队
           forkJoinDemo task2 = new forkJoinDemo(middle+1,end);
           task2.fork();    //拆分任务,把任务压入线程列队
            //join 结果合并
            return task1.join()+task2.join();
        }
    }
}

测试:

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.stream.LongStream;

/*
    求和计算的任务
    3000(普通for循环) 6000(ForkJoin) 9000(Stream并行流)
 */
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //test1();    //时间:967  1839
        //test2();    //时间:1045 1711  数值增大,临界值调整,forkJoin越来越快
        test3();    //时间:1382
    }
    //3000(普通for循环)
    public static void test1(){
        long sum = 0;
        long start = System.currentTimeMillis();
        for (long i = 1L; i <=20_0000_0000L ; i++) {
            sum +=i;
        }
        long end = System.currentTimeMillis();
        System.out.println("sum1="+sum+" 时间:"+(end-start));
    }
    //6000(ForkJoin)
    public static void test2() throws ExecutionException, InterruptedException {
        long start = System.currentTimeMillis();
        //需要使用ForkJoinPool 通过它来执行
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //下面需要传入ForkJoin任务,所以先创建ForkJoin任务
        forkJoinDemo task = new forkJoinDemo(0L, 20_0000_0000L);
        //.execute()是执行,没有返回值;.submit()提交,有返回值
        ForkJoinTask<Long> submit = forkJoinPool.submit(task);
        long sum = submit.get();    //get值会阻塞等待
        long end = System.currentTimeMillis();
        System.out.println("sum2="+sum+" 时间:"+(end-start));
    }
        //Stream并行流,
    public static void test3(){
        long start = System.currentTimeMillis();
        //rangeClosed (]半包括; parallel并行;reduce归约计算
        long sum = LongStream.rangeClosed(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);
        long end = System.currentTimeMillis();
        System.out.println("sum3="+sum+" 时间:"+(end-start));
    }
}
  1. forkjoinPool 通过它来执行
  2. 计算任务forkjoinPool.execute(ForkJoinTask task)
  3. 计算类要继承ForkJoinTask!

小结

  • ForkJoin 必须大数据量,不然不如for

  • ForkJoin 代码更多,大量数据和临界值调整情况下效率比for高

  • Stream 代码更简洁,效率更高,需要更深入的学习

    long sum = LongStream.rangeClosed(0L, 20_0000_0000L).parallel().reduce(0, Long::sum);

    rangeClosed(]半包括

    parallel并行

    reduce归约计算

15、异步回调

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

在这里插入图片描述

在这里插入图片描述

其实就是前端 --> 发送ajax异步请求给后端
平时都使用CompletableFuture

没有返回值的异步回调-runAsync

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/*
    异步执行
    成功回调
    失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //没有返回值的 runAsync 异步回调
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            //发起一个异步任务
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ">void");
        });

        System.out.println("111");
        //输出执行结果
        future.get(); //获取阻塞执行结果

    }
}

有返回值的异步回调-supplyAsync

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
/*
    异步执行
    成功回调
    失败回调
 */
public class Demo01 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //有返回值的 supplyAsync 异步回调
        //ajax,成功和失败的回调
        //返回的是错误信息
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
            System.out.println(Thread.currentThread().getName() + "supplyAsync->Integer");
            int i = 10/0;
            return 1024;
        });
        System.out.println(future.whenComplete((t, u) -> {    //异步成功
            System.out.println("t=>" + t);  //正常的返回结果
            System.out.println("u=>" + u);  //错误信息:java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
        }).exceptionally((e) -> {         //异步失败
            System.out.println(e.getMessage());
            return 233;
        }).get());


    }
}

whenComplete: 有两个参数,一个是t 一个是u

T:是代表的 正常返回的结果;

U:是代表的 抛出异常的错误信息;

如果发生了异常,get可以获取到exceptionally返回的值;

16.JMM

请你谈谈你对Volatile 的理解

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

  1. 保证可见性 —>与JMM挂钩
  2. 不保证原子性
  3. 禁止指令重排

什么是JMM?

JMM:JAVA内存模型,不存在的东西,是一个概念,也是一个约定!

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

1、线程解锁前,必须把共享变量立刻刷回主存

​ (线程的调用,是从主存中拷贝一份到自己的工作内存中)

2、线程加锁前,必须读取主存中的最新值到工作内存中;

3、加锁和解锁是同一把锁;

线程分: 工作内存、主内存

内存交互8种操作

  • Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
  • load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
  • Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
  • assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
  • store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
  • write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
  • lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
  • unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;

在这里插入图片描述

JMM对这8种指令的规定:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

问题:B线程修改主存后,A线程不知道主存中的值已经被修改过了!

在这里插入图片描述

17.Volatile

测试:B线程修改主存后,A线程不知道主存中的值已经被修改过了

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    private  static Integer number = 0;
    public static void main(String[] args) {
        new Thread(()->{
            while (number==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(number=1);
    }
}

在这里插入图片描述

1、保证可见性

package com.juc.test.VolatileClass;

import java.util.concurrent.TimeUnit;

public class JMMDemo {
    // 如果不加volatile 程序会死循环
    // 加了volatile是可以保证可见性的
    private volatile static Integer number = 0;
    public static void main(String[] args) {
        new Thread(()->{
            while (number==0){
            }
        }).start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(number=1);
    }
}

2、不保证原子性

原子性:不可分割

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

/**

 * volatile 不保证原子性

 * number <=2w

 * */
   public class VDemo02 {

   private static volatile int number = 0;
       
	//synchronized 保证原子性可以保证
   public static void add(){
       number++; 
       //++ 不是一个原子性操作,是两个~3个操作
       //
   }
public static void main(String[] args) {
       //理论上number  = 20000
for (int i = 1; i <= 20; i++) {
    new Thread(()->{
        for (int j = 1; j <= 1000 ; j++) {
            add();
        }
    }).start();
}
//Thread.activeCount 还存活的线程数量
while (Thread.activeCount()>2){
    //main  gc 这两个线程在默认执行
    Thread.yield();
}
System.out.println(Thread.currentThread().getName()+",num="+number);
}
}

在这里插入图片描述

number++不是一个原子性操作,是两个~3个操作

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

在这里插入图片描述

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

代码如下:

public class VDemo02 {
    //原子类的Integer
private static volatile AtomicInteger number = new AtomicInteger();

public static void add(){
//        number++;
        number.incrementAndGet();  //AtomicInteger +1 方法
    //底层是CAS保证的原子性
    }
public static void main(String[] args) {

    for (int i = 1; i <= 20; i++) {
        new Thread(()->{
            for (int j = 1; j <= 1000 ; j++) {
                add();
            }
        }).start();
    }

    while (Thread.activeCount()>2){
        //main  gc
        Thread.yield();
    }
    System.out.println(Thread.currentThread().getName()+",num="+number);
}
}

这些类的底层都直接和操作系统挂钩!是在内存中修改值。

Unsafe类是一个很特殊的存在,CAS

3、禁止指令重排

  • 什么是指令重排?

我们写的程序,计算机并不是按照我们自己写的那样去执行的

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

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

int x=1; //1
int y=2; //2
x=x+5;   //3
y=x*x;   //4
//我们期望的执行顺序是 1_2_3_4  可能执行的顺序会变成2134 1324
//可不可能是 4123? 不可能的

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

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

正常的结果: x = 0; y =0

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

可能在线程A中会出现,先执行b=1,然后再执行x=a

在B线程中可能会出现,先执行a=2,然后执行y=b

顺序对各自线程没有影响,但是会对其他线程有影响

那么就有可能结果如下:x=2; y=1(理论上会产生)

volatile可以避免指令重排

volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。

内存屏障:CPU指令

作用:

  1. 保证特定的操作的执行顺序;
  2. 可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)

在这里插入图片描述

图 内存屏障实现原理

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

面试官:那么你知道在哪里用这个内存屏障用得最多呢?单例模式

18、玩转单例模式

饿汉式、DCL懒汉式

饿汉式

/**
 * 饿汉式单例
 	一上来就加载
   */
   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懒汉式

//懒汉式单例模式
//用的时候在加载
public class LazyMan {
private static boolean Q = false;

private LazyMan(){	
    System.out.println(Thread.currentThread().getName()+" ok");
}
    
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
    //单线程下if是ok的
	//但是如果是并发就会出现问题
    if(lazyMan==null){
        //所以需要加锁,进行双重检测
        synchronized (LazyMan.class){
            //然后再去判断
            if(lazyMan==null){
                lazyMan=new LazyMan();
                
            }
        }
    }
    return lazyMan;
    //不是一个原则性操作
                /**不是原则性操作会出现以下步骤:
                 *	
                 * 1、分配内存空间
                 * 2、执行构造方法,初始化对象
                 * 3、把这个对象指向这个空间
                 *
                 *  有可能出现指令重排问题
                 *  比如执行的顺序是1 3 2 等
                 *  我们就可以添加volatile保证指令重排问题
                 */
}
}

懒汉式不是一个原则性操作

  • 不是原则性操作会出现以下步骤:
  1. 分配内存空间
  2. 执行构造方法,初始化对象
  3. 把这个对象指向这个空间
  • 有可能出现指令重排问题
    • 比如执行的顺序是1 3 2 等
    • 我们就可以添加volatile保证指令重排问题

静态内部类

//静态内部类
public class Holder {
    private Holder(){
}
public static Holder getInstance(){
    return InnerClass.holder;
}
public static class InnerClass{
    private static final Holder holder = new Holder();
}
}
//也不安全, 因为有反射

单例不安全,因为有反射

  • 第一次破坏
public static void main(String[] args) throws Exception {
        LazyMan instance = LazyMan.getInstance();
        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);    //无视私有构造器
        LazyMan instance2 = declaredConstructor.newInstance();	//新建实例
        System.out.println(instance);
        System.out.println(instance2);
    }
  1. LazyMan instance = LazyMan.getInstance();先通过内部类获得私有属性
  2. 利用.class.getDeclaredConstructor(null)获得空参构造器
  3. declaredConstructor.setAccessible(true); //无视私有构造器
  4. LazyMan instance2 = declaredConstructor.newInstance();新建实例
  • 第一次反击
private LazyMan(){
    synchronized (LazyMan.class){
        if (lazyMan!=null){
            throw  new RuntimeException("不要试图使用反射破坏异常");
        }
    }
}

通过另外给LazyMan.class加锁,lazyMan!=null就说明已经被重写了,然后抛出异常

  • 第二次破坏
public static void main(String[] args) throws Exception {
    //LazyMan instance = LazyMan.getInstance();
    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
    declaredConstructor.setAccessible(true);    //无视私有构造器
    LazyMan instance = declaredConstructor.newInstance();
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance);
    System.out.println(instance2);

}

不使用LazyMan,直接使用LazyMan instance = declaredConstructor.newInstance();,绕过第一次反击的锁

  • 第二次反击
public class LazyMan {
    private static boolean Q = false;   //创建标志位

    private LazyMan(){
        synchronized (LazyMan.class){
            if (Q == false){
                Q = true;
            }else {
                throw  new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

标记标志位

  • 第三次破坏

如果获得了隐藏标记位的名字

public static void main(String[] args) throws Exception {
    //LazyMan instance = LazyMan.getInstance();
    Field q = LazyMan.class.getDeclaredField("Q");
    q.setAccessible(true);  //破坏标记位私有权限

    Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
    declaredConstructor.setAccessible(true);    //无视私有构造器
    LazyMan instance = declaredConstructor.newInstance();
    q.set(instance,false);
    LazyMan instance2 = declaredConstructor.newInstance();

    System.out.println(instance);
    System.out.println(instance2);

}
  • 道高一尺魔高一丈
package JVMclass.DLC;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;

public class LazyMan {
    private static boolean Q = false;   //创建标志位

    private LazyMan(){
        synchronized (LazyMan.class){
            if (Q == false){
                Q = true;
            }else {
                throw  new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan lazyMan;
    //双重检测锁模式 简称DCL懒汉式
    public static LazyMan getInstance(){
        //单线程下if是ok的
        //但是如果是并发就会出现问题
        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();
        Field q = LazyMan.class.getDeclaredField("Q");
        q.setAccessible(true);  //破坏私有

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);//获得空参构造器
        declaredConstructor.setAccessible(true);    //无视私有构造器
        LazyMan instance = declaredConstructor.newInstance();
        q.set(instance,false);
        LazyMan instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);

    }
}

利用枚举防止反编译

枚举

package JVMclass.DLC;

import java.lang.reflect.Constructor;

//  enum本身也是一个Class类
public enum EnumSingle {
    INSTANCE;
    public EnumSingle getInstance(){

        return INSTANCE;
    }
}
//测试能不能被反射
class Test{
    public static void main(String[] args) throws Exception {
        EnumSingle instance1 = EnumSingle.INSTANCE;
        //Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        //没有无参构造,而是有参构造
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
        //Cannot reflectively create enum objects
        //反射不能破坏枚举
        declaredConstructor.setAccessible(true);
        EnumSingle instance2 = declaredConstructor.newInstance();

        //提示有空参构造方法
        //NoSuchMethodException: JVMclass.DLC.EnumSingle.<init>()
        // 但程序运行提示没有空参构造方法
        System.out.println(instance1);
        System.out.println(instance2);
    }
}

在这里插入图片描述

在这里插入图片描述

public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
    return (EnumSingle[])$VALUES.clone();
}

public static EnumSingle valueOf(String name)
{
    return (EnumSingle)Enum.valueOf(com/ogj/single/EnumSingle, name);
}

private EnumSingle(String s, int i)
{
    super(s, i);
}

public EnumSingle getInstance()
{
    return INSTANCE;
}

public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];

static 
{
    INSTANCE = new EnumSingle("INSTANCE", 0);
    $VALUES = (new EnumSingle[] {
        INSTANCE
    });
}
}

通过jad,将class文件反编成java文件,反编译后发现实际是个有参构造

19、深入理解CAS

什么是CAS?

大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构

public class casDemo {
    //CAS : compareAndSet 比较并交换
    public static void main(String[] args) {
        //原子类,原子类的底层用的CAS
        AtomicInteger atomicInteger = new AtomicInteger(2020);//实际初始值
	//boolean compareAndSet(int expect, int update)
    //expect期望值、update更新值
    //如果实际值 和 我的期望值相同,那么就更新
    //如果实际值 和 我的期望值不同,那么就不更新
    System.out.println(atomicInteger.compareAndSet(2020, 2021)); //能返回一个boolean,true or false
    System.out.println(atomicInteger.get());

    //因为期望值是2020  实际值却变成了2021  所以会修改失败
    //CAS 是CPU的并发原语
    atomicInteger.getAndIncrement(); //++操作
    System.out.println(atomicInteger.compareAndSet(2020, 2021));
    System.out.println(atomicInteger.get());
}
}

Unsafe类

在这里插入图片描述

getAndIncrement源码:

在这里插入图片描述

自旋锁

在这里插入图片描述

总结:

CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。

  • 缺点:
  1. 循环会耗时;
  2. 一次性只能保证一个共享变量的原子性;
  3. 它会存在ABA问题

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

在这里插入图片描述

比如:

  • 由于线程2速度快,将 A = 1改为3,又改回1

  • 线程1实际修改的 A=1 已经不是原理的 A=1 了,而是线程2修改过的 A=1

CAS 代码:

    public class casDemo {
   
    public static void main(String[] args) {
    AtomicInteger atomicInteger = new AtomicInteger(2020);
    //对于我们平时写的SQL : 使用 乐观锁!
        
    //====捣乱的线程 ====
    //2020-> 2021
    System.out.println(atomicInteger.compareAndSet(2020, 2021));
    System.out.println(atomicInteger.get());
	//2021 - > 2020
    System.out.println(atomicInteger.compareAndSet(2021, 2020));
    System.out.println(atomicInteger.get());

    
    //====期望的线程 ====
    //2020 已经不是原理的 2020 了
    System.out.println(atomicInteger.compareAndSet(2020, 2021));
    System.out.println(atomicInteger.get());
    }
}   

20、原子引用

解决ABA问题,对应的思想:就是使用了乐观锁~

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

带版本号的 原子操作!

package com.juc.test.CASClass;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class CASDemo {
    public static void main(String[] args) {
        //AtomicInteger atomicInteger = new AtomicInteger(2020);
        //注意,如果泛型是一个包装类,注意对象的引用问题
        AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(10,1);

        new Thread(()->{
            int stamp = atomicInteger.getStamp();   //获取版本号
            System.out.println("A1 => "+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //期望值,希望值,期望版本号,希望版本号
            System.out.println("A2-"+atomicInteger.compareAndSet(10, 12,
                    atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
            System.out.println();
            System.out.println("A2 => "+atomicInteger.getStamp());
            System.out.println("A3-"+atomicInteger.compareAndSet(12, 10,
                    atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
            System.out.println();
            System.out.println("A3 => "+atomicInteger.getStamp());
        },"A"
        ).start();

        //如果线程A先走,即使期望值修改回10,但是由于版本号已经改变,线程B无法执行
        //如果线程B先走,期望值和版本都所以改变,线程A无法执行
        //和乐观锁的原理相同
        new Thread(()->{
            int stamp = atomicInteger.getStamp();   //获取版本号
            System.out.println("B1 => "+stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println("B2-"+atomicInteger.compareAndSet(10, 66, stamp, stamp + 1));
            System.out.println("B2 => "+atomicInteger.getStamp());
        },"B"
        ).start();
    }
}
  • 如果线程A先走,即使期望值修改回10,但是由于版本号已经改变,线程B无法执行
  • 如果线程B先走,期望值和版本都所以改变,线程A无法执行
  • 和乐观锁的原理相同

注意

在这里插入图片描述

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

由于测试代码使用的是个包装类(Integer),正常业务操作中,我们一般使用的是一个个对象,一般情况不会遇到这种情况。

21、各种锁的理解

1、公平锁、非公平锁

公平锁:非常公平;不能插队的,必须先来后到;
加 true 后变成公平锁

Lock lock = new ReentrantLock(true);

/**

 * Creates an instance of {@code ReentrantLock} with the
 * given fairness policy.
   *
 * @param fair {@code true} if this lock should use a fair ordering policy
   */
   public ReentrantLock(boolean fair) {
   sync = fair ? new FairSync() : new NonfairSync();
   }

非公平锁:非常不公平,允许插队的,可以改变顺序

默认都是非公平

/**
 * Creates an instance of {@code ReentrantLock}.
 * This is equivalent to using {@code ReentrantLock(false)}.
   */
   public ReentrantLock() {
   sync = new NonfairSync();
   }

2、可重入锁

可重入锁(递归锁)

在这里插入图片描述

  • 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");
    }
}

正常逻辑 A 执行完 sms() 后就可以解锁,B就有可能执行。

但是 sms() 锁包含着 call() ,这样待A特底执行完 sms() 和 call() 之后,才算执行完毕,才能解锁,B才能执行。

  • lock锁
//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锁必须配对,否则就会死锁在里面
    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();
    }
}
}

lock锁必须配对,相当于lock和 unlock 必须数量相同,多lock 或多 unlock 会出现锁死报错情况

在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;

3、自旋锁

spinlock

不断迭代,直到成功为止

在这里插入图片描述

自我设计自旋锁:

MyLock 自己设计的锁:

public class SpinlockDemo {
//int 0(不写)
//Thread null
AtomicReference<Thread> atomicReference=new AtomicReference<>();

//加锁
public void myLock(){
    Thread thread = Thread.currentThread();
    System.out.println(thread.getName()+"===> mylock");

    //自旋锁 判断是否成立
    while (!atomicReference.compareAndSet(null,thread)){
        System.out.println(Thread.currentThread().getName()+" ==> 自旋中");
    }
}
    //解锁
public void myunlock(){
    Thread thread=Thread.currentThread();
    System.out.println(thread.getName()+"===> myUnlock");
    //如果为空,就解锁
    atomicReference.compareAndSet(thread,null);
}
}    

测试代码:

    public class TestSpinLock {
    public static void main(String[] args) throws InterruptedException {
        ReentrantLock reentrantLock = new ReentrantLock();
        reentrantLock.lock();
        reentrantLock.unlock();
    //使用CAS实现自旋锁
    SpinlockDemo spinlockDemo =new SpinlockDemo();
    new Thread(()->{
        spinlockDemo.myLock();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            spinlockDemo.myunlock();
        }
    },"t1").start();

    TimeUnit.SECONDS.sleep(1);
         new Thread(()->{
        spinlockDemo.myLock();
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            spinlockDemo.myunlock();
        }
    },"t2").start();
}
    }

运行结果:

t2进程必须等待t1进程 Unlock 后,才能 Unlock ,在这之前进行自旋等待。。。。

4、死锁排查

死锁是什么?

在这里插入图片描述

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

import java.util.concurrent.TimeUnit;
public class DeadLock {
    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+"===>get"+lockB);
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        synchronized (lockB){
            System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA);
        }
    }
}
}

解决问题

1、使用jps定位进程号,jdk的bin目录下: 有一个jps

命令:jps -l

在这里插入图片描述

2、使用jstack-进程号 找到死锁信息

在这里插入图片描述

一般情况信息在最后:

面试,工作中!排查问题!

1、日志

2、堆栈信息

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值