八股文4:高并发

本文深入探讨了Java并发编程的关键概念,包括进程与线程的区别、线程状态转换、线程池、锁机制如synchronized和volatile,以及并发容器如ConcurrentHashMap。还详细讲解了死锁预防、线程安全的单例模式实现以及并发工具如CountDownLatch和Semaphore的应用。

文章目录

进程与线程的区别

在这里插入图片描述

线程状态及转换

在这里插入图片描述

什么是线程挂起(非可执行的状态)

在这里插入图片描述

线程的创建方法

在这里插入图片描述

一般使用匿名内部类实现或者使用labmda表达式

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

什么是守护线程

区别于非守护线程,一旦没有非守护线程,守护线程就会自动结束自己的生命周期,此时就会关闭jvm进程

在这里插入图片描述

什么是死锁和如何防止死锁

在这里插入图片描述

在这里插入图片描述

并发编程的三个重要特性

在这里插入图片描述

CAS原理(无锁)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

原子操作类

在这里插入图片描述

CAS实现原子操作的三大问题

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

synchronized关键字(不透明锁,方法/代码块)

synchronized 关键字最主要的三种使用方式:

在这里插入图片描述

synchronized 关键字底层原理属于 JVM 层面

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

volatile关键字(读写屏障,变量)

JMM(java内存模型)

在这里插入图片描述
在这里插入图片描述

volatile解决数据不一致问题

在这里插入图片描述
在这里插入图片描述

volatile的读写屏障

在这里插入图片描述

synchronized 关键字和 volatile 关键字的区别

在这里插入图片描述

Lock锁(类)—AQS原理AbstractQueuedSynchronizer

在这里插入图片描述
在这里插入图片描述

AQS资源共享方式(进行分类不同的阻塞同步锁)

在这里插入图片描述

ReentrantLock(独占方式)–(公平锁与非公平锁的区别)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

ReentrantReadWriteLock(两种资源共享方式)

只有读读能共享,读写互斥,写写互斥:
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

CountDownLatch倒计时器(多线程相互等待全部完成)–(应用场景)

在这里插入图片描述
在这里插入图片描述

CompletableFuture的异步并行与等待

在这里插入图片描述

Semaphore(限流器–限制共享资源线程的数量)

Semaphore(信号量)可以指定多个线程同时访问某个资源。实际相当于刚开始permits数量的上限线程同时访问某个资源,后面最多只有permits个线程可以使用这个许可证,其余的线程必须等待由许可的线程release才有机会获得许可!!(所以semaphore可以用作限流器)
在这里插入图片描述

可以发现countdownLatch只能减少至0,没有释放的操作。但是semphore有acquire占用state,也可以release释放资源,显示了state的复用的功能

在这里插入图片描述

限流器的应用

Synchronized和Lock之间的区别

在这里插入图片描述

并发容器(JUC包下)

ConcurrentHashMap(对比HashMap,HashTable)

添加链接描述

CopyOnWriteArrayList(读读共享)

在这里插入图片描述

ConcurrentLinkedQueue(CAS非阻塞队列)

在这里插入图片描述

ConcurrentSkipListMap(跳表)

不知道是维护了多级的指针还是维护了多个链表
在这里插入图片描述
在这里插入图片描述

Threadlocal(本地线程局部变量)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

TreadLocal的set方法:先拿到自己线程的ThreadLocalMap,然后将这个Threadlocal作为键,set进入的参数value作为值(get方法是类似的思想)

在这里插入图片描述
在这里插入图片描述

经典的例子
在这里插入图片描述
在这里插入图片描述

ThreadLocal的内存泄漏问题

在这里插入图片描述

在这里插入图片描述

线程池(框架Executor)

在这里插入图片描述
在这里插入图片描述

线程池的状态

在这里插入图片描述

创建线程池的方式 (Excutors静态方法 | ThreadPollExcutor的构造函数)

在这里插入图片描述

线程池中执行 execute()方法和 submit()方法的区别是什么呢?

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

ThreadPoolExecutor 类及其构造方法分析

构造方法参数分析
在这里插入图片描述
在这里插入图片描述

具体的线程分配情况–执行的原理

任务队列都满时,才会启用急救线程
在这里插入图片描述
在这里插入图片描述

线程用完时的拒绝策略

在这里插入图片描述
在这里插入图片描述

一个线程池执行任务的demo

step1:首先实现一个自己的Runnable(Callbable)接口,来实现自己执行任务的方法
step2: 使用ThreadPoolExecutor创建自定义的线程池,实际上就是调用它的"构造方法",创建"自己的参数的线程池"
step3: 使用“线程池的execute(myrunnable)”来提交执行任务(实现的myCallable使用submit提交任务,同时有future对象返回用于获取执行的结果)

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

一些补充

指令重排序相关

在这里插入图片描述

在这里插入图片描述

as-if-serial语义(编译器优化重排序)

在这里插入图片描述

happens-before的定义(内存系统重排序)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

Synchronized锁升级

在这里插入图片描述

在这里插入图片描述

偏向锁(减少CAS)

判断偏向锁的标志位是否为1,如果不为1,此时先尝试CAS轻量级锁
在这里插入图片描述

轻量级锁(Object:CAS自旋竞争)

为每个线程创建一个锁记录对象(存放在本线程的栈帧中,锁重入就会再创建一个锁记录对象放在栈帧中),将锁记录对象的object reference指向锁定对象 && 将锁地址与锁定对象的Markword的hashcode进行CAS尝试交换。
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

锁膨胀(CAS自旋失败,Object申请Monitor)

在这里插入图片描述
在这里插入图片描述

重量级锁(Monitor–Owner&EntryList Blocked)

注意这张图:老式的直接重量级锁的情况,具体还是按照锁膨胀的那个为准,主要学习一下monitor的结构monitor的结构:(owner指向当前占用object对象的线程,entryList表示阻塞队列—后续锁释放会抢锁,waitSet等待队列–主动释放锁且需要唤醒才能加入阻塞队列进行抢锁)
在这里插入图片描述

owner重量级锁的线程wait()的原理

注意:wait和notify的方法属于object对象的!!(针对这个锁定对象object可以调用wait和notify分别用来对应waitset中的某个线程)

Object 类是一个特殊的类,是所有类的父类

public final native Class<?> getClass()//native方法,用于返回当前运行时对象的Class对象,使用了final关键字修饰,故不允许子类重写。

public native int hashCode() //native方法,用于返回对象的哈希码,主要使用在哈希表中,比如JDK中的HashMap。
public boolean equals(Object obj)//用于比较2个对象的内存地址是否相等,String类对该方法进行了重写用户比较字符串的值是否相等。

protected native Object clone() throws CloneNotSupportedException//naitive方法,用于创建并返回当前对象的一份拷贝。一般情况下,对于任何对象 x,表达式 x.clone() != x 为true,x.clone().getClass() == x.getClass() 为true。Object本身没有实现Cloneable接口,所以不重写clone方法并且进行调用的话会发生CloneNotSupportedException异常。

public String toString()//返回类的名字@实例的哈希码的16进制的字符串。建议Object所有的子类都重写这个方法。


//native方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
public final native void notify()

//native方法,并且不能重写。跟notify一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
public final native void notifyAll()


//native方法,并且不能重写。暂停线程的执行。注意:sleep方法没有释放锁,而wait方法释放了锁 。timeout是等待时间。
public final native void wait(long timeout) throws InterruptedException

//多了nanos参数,这个参数表示额外时间(以毫微秒为单位,范围是 0-999999)。 所以超时的时间还需要加上nanos毫秒。
public final void wait(long timeout, int nanos) throws InterruptedException


//跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
public final void wait() throws InterruptedException

protected void finalize() throws Throwable { }//实例被垃圾回收器回收的时候触发的操作

在这里插入图片描述

举个例子:

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

        //注意只有锁对象才能使用wait()和notify()方法
        Object ob = new Object();
//        int count=0;
//
//        while (count<10) {
//            count++;

            //创建一个顾客的买包子问候线程(利用匿名内部类),自动生成lambda表达式
            new Thread() {
                @Override
                public void run() {
                    //保证等待和唤醒的线程只有一个执行,使用同步技术
                    synchronized (ob) {
                        System.out.println("1:顾客告知老板需要包子的数量:你好,我要xxx个");
                        //try catch快捷键ctrl+alt+t
                        try {

                            //wait(带时间参数)如果没有notify()来唤醒,之后会自动醒来
                            ob.wait();//开始等待,当前线程放弃CPU的执行
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        //唤醒之后的执行操作
                        System.out.println("3:包子已经做好了,顾客开吃!!!");
                    }

                }
            }.start();

            //创建一个老板做包子的新线程
            new Thread() {
                @Override
                public void run() {
                    //花5s做包子
                    try {
                        Thread.sleep(3000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                    synchronized (ob) {
                        System.out.println("2:老板做好包子,告知顾客:拿好!!");
                        ob.notify();//主动唤醒等待的线程,继续执行wait()后的代码执行
                    }
                }
            }.start();
//        }
    }
}

在这里插入图片描述

应用篇

手写单例模式

基础概念

懒汉与饿汉的定义
在这里插入图片描述

静态方法的调用与静态方法内部只能调用静态的成员变量
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

懒汉式-线程不安全

public class Singleton1 {
    //没有在类加载阶段时初始化,所以是懒汉式(这边static的成员变量:静态方法调用静态变量)
    private static Singleton1 mySingleton;
    private Singleton1(){}//防止被外部类直接初始化

    //定义一个公共静态的方法的出口,来获取唯一的单例
    public static Singleton1 getMySingleton(){
        if(mySingleton==null){
            mySingleton=new Singleton1();
        }
        return mySingleton;
    }
}

在这里插入图片描述

饿汉式-线程安全

占内存–不推荐(类一加载就会初始化实例,生命周期长,占用内存),使用jvm来保证线程安全性

public class Singleton2 {
    //类加载的时候就初始化了(饿汉)
    private static Singleton2 mySingleton=new Singleton2();
    
    private Singleton2(){}
    
    public static Singleton2 getSingleton(){
        return mySingleton;
    }
}

懒汉式-同步代码块(安全版)

效率太低了,每个线程在想获得类的实例时候,执行getlnstance()方法都要进行同步。

public class Singleton3 {
    private static Singleton3 mySingleton;
    private Singleton3(){}
    
    //定义一个静态同步方法(每次想获取静态变量都需要加锁)
    public static synchronized Singleton3 getMySingleton(){
        if(mySingleton==null){
            mySingleton=new Singleton3();
        }
        return mySingleton;
    }
}

懒汉式-双重校验锁(DCL,即 double-checked locking)(同步代码块+violate)

这种方式采用双锁机制,安全且在多线程情况下能保持高性能。getInstance() 的性能对应用程序很关键。

public class Singleton4 {

    //采用volatile保证可见性+防止指令的重排序(new的时候实际的字节码指令有3条)
    private static volatile Singleton4 mySingleton;

    private Singleton4(){}

    public static Singleton4 getMySingleton(){
        if(mySingleton==null){
            //使用同步代码块(保证初始化时不会出现干扰线程)
            synchronized (Singleton4.class){
                if(mySingleton==null){
                    mySingleton=new Singleton4();
                }
            }
        }

        return mySingleton;
    }
}

懒汉式-静态内部类–延迟初始化–进化的饿汉(安全版)

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
使用jvm保证线程安全性
在这里插入图片描述

在这里插入图片描述
研读一下区别

在这里插入图片描述

在这里插入图片描述

public class Singleton5 {

    //使用静态内部类(来延迟初始化)
    private static class SingletonHolder{
        private static final Singleton5 MYSINGLETON=new Singleton5();
    }

    private Singleton5(){}

    //是有调用这个方法时,才会加载静态内部类
    public static Singleton5 getSingleton(){
        return SingletonHolder.MYSINGLETON;
    }
}

枚举方式不太懂!!!

多线程的交替打印实现

Synchronized(方法)+Object的wait() notify()

注意:synchronized的方法如果拿到锁,不释放锁就会导致死锁,所以一定要搭配wait()和notify进行使用。(由于开启了多个线程,为了防止后面的线程拿到锁之后连着打印A,所以这里必须要在开头进行while()循环的判断)

public class SynchronizedAB {
    private boolean flag=false;

    //锁住当前的类对象
    synchronized void printA(){
        while(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        flag=true;
        System.out.println("A");
        this.notify();
    }

    //锁住当前的类对象
    synchronized void printB(){
        while(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        flag=false;
        System.out.println("B");
        this.notify();
    }

    public static void main(String[] args) {
        SynchronizedAB synchronizedAB = new SynchronizedAB();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    synchronizedAB.printB();
                }
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    synchronizedAB.printA();
                }
            }
        }).start();
    }

}

Lock锁 + Condition的await()和sinal()

用于锁住大量的代码块
在这里插入图片描述
condition接口的一些方法
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

public class LockAB {
    public static void main(String[] args) {
        Lock lock=new ReentrantLock();
        Condition c1=lock.newCondition();
        Condition c2=lock.newCondition();

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                lock.lock();
                try{
                    while(i<5){
                        System.out.println("A");
                        i++;
                        c2.signal();//唤醒线程2
                        c1.await();//线程1释放锁
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();//锁一定要finally释放
                }

            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                lock.lock();
                try{
                    while(i<5){
                        System.out.println("B");
                        i++;
                        c1.signal();
                        c2.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            }
        }).start();
    }


}

volatile实现,可见性+有序性----还不如用锁(了解)

本质使用标志位的判断以及标志位的状态改变之间的位置进行锁住临界区的代码,while+if只要标志位不满足就是一个死循环,注意i++的变化必须位于标志位符合情况下的代码块内(相当于使用底层的jvm的来实现blockFlag来作为锁锁住整个对象----注意:一定要使用这个锁对象包裹住整个临界区的变化部分)

public class VolatileAB {

    static volatile boolean blockFlag = false;//相当于锁

    public static void main(String[] args) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while(i<20){
//                    注意:如果标志位不满足,也不会执行i++的操作,此时的i的次数是保证了的
//                    本质利用一个标志位锁住了临界区的代码块(如果一直不满足,其实会一直在while循环内死循环,反正i加加)
                    if(!blockFlag){
                        System.out.println(i+"  A");
                        i++;
                        blockFlag=true;//注意这里的标志位的改变,必须位于打印之后
                    }

                }


            }
        }).start();


        new Thread(new Runnable() {
            @Override
            public void run() {
                int i=0;
                while(i<20){
                    if(blockFlag){
                        System.out.println(i+"  B");
                        i++;
                        blockFlag=false;//注意这里标志位的改变必须位于打印之后
                    }
                }
            }
        }).start();
    }

}

注意:LockSupport的park和unpark不知道怎么实现,感觉都不太好操作呀!!!

阻塞队列的实现

Synchronized版本

public class MyBlockingQueue<E> {
    public int count;
    public Deque<E> blockingQueue;


    public MyBlockingQueue(int count) {
        this.count = count;
        this.blockingQueue = new LinkedList<E>();
    }

    public E take(){
        synchronized (blockingQueue){
            while(blockingQueue.size()==0){
                try {
                    System.out.println("空");
                    blockingQueue.wait();//释放锁,让生产者去生产数据
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            blockingQueue.notifyAll();//但是我们这个线程还是锁住的,还是得走完
            return blockingQueue.removeFirst();
        }
    }

    public void put(E e){
        synchronized (blockingQueue){
            while(blockingQueue.size()==count){
                try {
                    System.out.println("满");
                    blockingQueue.wait();//释放锁,让消费者去消费数据
                } catch (InterruptedException ex) {
                    ex.printStackTrace();
                }
            }
            blockingQueue.notifyAll();//注意:唤醒之后,还是排在组得队列内部,必须等待这个syn语句块执行完释放锁开始公平的竞争
            System.out.println("生产者放数:"+e.toString());
            blockingQueue.add(e);
        }
    }

}


在这里插入图片描述

Reentranlock版本


public class MyBlockQueue2<E> {
    public int count;
    public Deque<E> blockingQueue;
    ReentrantLock red=new ReentrantLock();
    Condition isNull=red.newCondition();
    Condition isFull=red.newCondition();

    public MyBlockQueue2(int count) {
        this.count = count;
        this.blockingQueue = new LinkedList<E>();
    }

    public E take(){
        red.lock();
        try {
            while (blockingQueue.size() == 0) {
                System.out.println("队列中暂时还没数据,等待生产数据");
                isNull.await();
            }
            E res = blockingQueue.removeFirst();
            isFull.signal();//等待放入数据(虽然这里唤醒了,但还是位于等待队列进行公平的竞争---注意前提这段代码释放锁之后进行)
            return res;

        } catch (InterruptedException e) {
//            isNull.signal();
            e.printStackTrace();
        }finally {
            red.unlock();
        }
        return null;
    }


    public void put(E e){
        red.lock();
        try {
            while (blockingQueue.size() >= count) {
                System.out.println("队列满了,等待消费数据");
                isFull.await();
            }
            System.out.println("生产者放入数:"+e.toString());
            blockingQueue.add(e);
            isNull.signal();//等待消费数据
        } catch (InterruptedException ex) {
//            isFull.signal();
            ex.printStackTrace();
        }finally {
            red.unlock();
        }
    }

}

生产者与消费者模式

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

        //阻塞队列的最大容量为10
        BlockingQueue queue = new ArrayBlockingQueue(10);

        //一个生产者,3个消费者并发(生产者不断的生产消息,但是有三个消费者不断的消费消息)
        new Thread(new Producer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
        new Thread(new Consumer(queue)).start();
    }
}



class Producer implements Runnable {

    private BlockingQueue<Integer> queue;

    //带参构造器
    public Producer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            for (int i = 0; i < 100; i++) {
                Thread.sleep(20);
                queue.put(i);//将数据交给队列来处理(当队列满时可以实现阻塞)
                System.out.println(Thread.currentThread().getName() + "生产:" + queue.size());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}




class Consumer implements Runnable {

    private BlockingQueue<Integer> queue;

    //带参构造器
    public Consumer(BlockingQueue<Integer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        try {
            while (true) {
                //用户消费的时间间隔没法确定,随机
                Thread.sleep(new Random().nextInt(1000));
                queue.take();//从阻塞队列获取数据
                System.out.println(Thread.currentThread().getName() + "消费:" + queue.size());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

限流器实现(semaphore实现)

真的很简易
在这里插入图片描述

本质就创建这么上限的可以调用的资源

import java.util.concurrent.*;
 
 
public class SemaphoreLimitTest {
 
  // 定义一个执行线程池
  private final Executor executor = new ThreadPoolExecutor(10, 20, 60L, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10));
  // 每次只能执行5个任务
  private final Semaphore semaphore = new Semaphore(5);
 
  private void process() {
    executor.execute(new Runnable() {
      @Override
      public void run() {
        try {
          semaphore.acquire();
          Thread.sleep(3000);
          semaphore.release();
        } catch (Exception e) {
          e.printStackTrace();
        }
      }
    });
  }
 
 
  // 模拟测试
  public static void main(String[] args) {
    final SemaphoreLimitTest semaphoreLimitTest = new SemaphoreLimitTest();
    // 同时进来8个任务
    for (int i = 0; i < 8; i++) {
      // 定义8个线程
      new Thread("线程" + i) {
        @Override
        public void run() {
          semaphoreLimitTest.process();
        }
      }.start();
    }
  }
 
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值