大厂面试题第二季(周阳老师)

文章目录

1、Volatile

1.1什么是volatile

volatile是java虚拟机提供的轻量级的同步机制

1.2volatile的特性

1.2.1volatile保证可见性

1.什么是JMM(java内存模型)

​ JMM本身是一种抽象的概念并不真实存在,它描述的是一组规则或者是规范,通过这组规范定义了程序中的各个变量的(包括实例字段、静态字段和构成数组对象的元素)访问方式。

JMM关于同步的规定

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

内存的读取速度:硬盘<内存<cpu(计算)

在这里插入图片描述

三个线程同时去操作age这个属性,首先都会对age=25进行变量拷贝,在各自线程的工作内存进行操作,如果t1线程对age做了修改,而t2、t3并不知道age做了修改,为了让t2、t3与主内存的数据保持一致,这就有了volatile。

2、可见性代码验证

不可见:

public class ThreadDemo001 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        //第一个线程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t come in");
            //暂停线程3s
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //3s钟以后将number改为60
            myData.addTo60();
            System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);
        },"AAA").start();
        //第二个线程,main线程
        while (myData.number==0){

        }
        System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number over: "+myData.number);

    }
}
class MyData{
    int number=0;
    public void addTo60(){
        this.number=60;
    }
}

在这里插入图片描述

这个时候呢,主线程一直感知不到线程1对number的修改,所以一直再等待number的改变而不会停止。

可见性

public class ThreadDemo001 {
    public static void main(String[] args) {
        MyData myData = new MyData();
        //第一个线程
        new Thread(()->{
            System.out.println(Thread.currentThread().getName()+"\t come in");
            //暂停线程3s
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //3s钟以后将number改为60
            myData.addTo60();
            System.out.println(Thread.currentThread().getName()+"\t update number value:"+myData.number);
        },"AAA").start();
        //第二个线程,main线程
        while (myData.number==0){

        }
        System.out.println(Thread.currentThread().getName()+"\t mission is over,main get number over: "+myData.number);

    }
}
class MyData{
   volatile int number=0;
    public void addTo60(){
        this.number=60;
    }
}

在这里插入图片描述

当对变量number加了volatile后,代码可见性体现,主线程执行。

1.2.2volatile不保证原子性

​ 原子性:不可分割,完整性,也即某个线程正在做某个具体业务时,中间不可以加塞或者被分割,需要整体完整要么同时成功,要么同时失败。

1、代码验证
public class ThreadDemo001
{
    public static void main(String[] args)
    {
        MyData myData = new MyData();
        //新建20个线程
        for (int i=1;i<=20;i++){
                    new Thread(()->{
                        //每个线程执行1000次
                        for (int j = 0; j < 1000; j++) {
                            myData.addPlusPlus();
                        }
                    },String.valueOf(i)).start();
                }
        //等待上面20个线程都执行完成之后,再用main线程取得最终结果 2:main+gc
         while (Thread.activeCount()>2) {
             Thread.yield();
         }
        System.out.println(Thread.currentThread().getName()+"\t final number"+myData.number);
    }
}
class MyData {
   volatile int number=0;
    public void addTo60(){
        this.number=60;
    }
    public void addPlusPlus(){
        number++;
    }
}

每一次运行结果都不一样,结果都是小于20000

2、为什么

在这里插入图片描述

​ 如图所示t1、t2、t3三个线程同时对number进行+1,突然t2在本地加完(number=2)还没放回主内存挂掉了,紧接着t3执行了+1,number由1->2,

3、解决
  • synchronized
  • Atomic
package com.ljh;

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

/**
 * @auth 刘佳浩
 * @create 2020-12-16-22:59
 * volatile可见性代码验证、不保证原子性
 */
public class ThreadDemo001
{
    public static void main(String[] args)
    {
        MyData myData = new MyData();
        //新建20个线程
        for (int i=1;i<=20;i++){
                    new Thread(()->{
                        //每个线程执行1000次
                        for (int j = 0; j < 1000; j++) {
                            myData.addPlusPlus();
                            myData.addAtomic();
                        }
                    },String.valueOf(i)).start();
                }
        //等待上面20个线程都执行完成之后,再用main线程取得最终结果 2:main+gc
         while (Thread.activeCount()>2) {
             Thread.yield();
         }
        System.out.println(Thread.currentThread().getName()+"\t int type,final number"+myData.number);
        System.out.println(Thread.currentThread().getName()+"\t atomic type,final number"+myData.atomicInteger);
    }
}
class MyData {
   volatile int number=0;
    public void addTo60(){
        this.number=60;
    }
    public void addPlusPlus(){
        number++;
    }
    AtomicInteger atomicInteger=new AtomicInteger();
    public void addAtomic(){
        atomicInteger.getAndIncrement();
    }
}

结果如下:

在这里插入图片描述

1.2.3volatile禁止指令重排(有序性)

​ 计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分为以下3种:
在这里插入图片描述

  • 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致
  • 处理器在进行重排序时必须要考虑指令之间的数据依赖性
  • 多线程环境种线程交替执行,由于编译器优化重排的存在,多个线程中使用的变量能否保证一致性是无法确定的,结果无法预测。

案例一

  public void mysort(){
        int x=3; //1
        int y=4; //2
        x=x+5;  //3
        y=x*x;  //4
    }

执行顺序的可能:1234 、 2134 、 1324

那么问题来了4重排之后可以第一条执行吗?

​ 不可以,因为上面讲到,处理器在进行重排序时必须要考虑指令之间的数据依赖性

案例二

int a,b,x,y=0;
线程1线程2
x=ay=b
b=1a=2
x=0,y=0

如果编译器对这段程序代码执行重排优化后,可能出现下列情况:

线程1线程2
b=1a=2
x=ay=b
x=2,y=1

案例三

public class ReSortSeqDemo {
    int a=0;
    Boolean flag=false;
    public void method1(){
        a=1;                //1 
        flag=true;          //2
    }
    public void method2(){
       if (flag){
           a=a+5;
           System.out.println("*****Value:"+a);
       }
    }
}

1.3单例模式

你在哪些地方用到过volatile?

单例模式

最初学过的单线程的单例模式,在多线程下,有很大的问题:

public class SingletonDemo {
    public static SingletonDemo instance=null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
    }
    public static SingletonDemo getInstance(){
        if (instance==null){
            instance=new SingletonDemo();
        }
        return instance;
    }

    public static void main(String[] args) {
         for (int i=1;i<=20;i++){
                     new Thread(()->{
                         SingletonDemo.getInstance();
                     },String.valueOf(i)).start();
                 }
    }
}
1	 我是构造方法SingletonDemo()
2	 我是构造方法SingletonDemo()
2	 我是构造方法SingletonDemo()
3	 我是构造方法SingletonDemo()
1	 我是构造方法SingletonDemo()

DCL(Double Check Lock双端检锁机制):

public class SingletonDemo {
    public static volatile SingletonDemo instance=null;
    private SingletonDemo(){
        System.out.println(Thread.currentThread().getName()+"\t 我是构造方法SingletonDemo()");
    }
    public static SingletonDemo getInstance(){
        if (instance==null){
            synchronized (SingletonDemo.class){
                if (instance==null){
                    instance=new SingletonDemo();
                }
            }

        }
        return instance;
    }

    public static void main(String[] args) {
         for (int i=1;i<=20;i++){
                     new Thread(()->{
                         SingletonDemo.getInstance();
                     },String.valueOf(i)).start();
                 }
    }
}

2、CAS

2.1什么是CAS?

compare and swap比较并交换

public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2020)+"\t value:"+atomicInteger);
        System.out.println(atomicInteger.compareAndSet(5, 2021)+"\t value:"+atomicInteger);
    }
}
true	 value:2020
false	 value:2020

2.2底层原理

我们之前用到过

 atomicInteger.getAndIncrement();
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

这里的this指的是当前对象,valueOffset指的是该变量值在内存中的偏移地址,即内存地址,1为固定+1;

这里的unsafe类是什么?

​ upsafe是CAS的核心类,由于java方法无法直接访问底层系统,需要通过本地(Native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据。Unsafe类存在与sum.misc包中,其内部方法操作可以像C的指针一样直接操作内存,所以说CAS依赖与Unsafe类。注意Unsaf类中的所有方法都是native修饰的,也就是说,Unsafe类中的方法都直接调用操作系统底层资源执行相应的任务

2.3总结

​ CAS的全称为Compare-And-Swap,它是一条CPU并发原语。他的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,否则,不改,这个过程是原子的。

​ CAS并发原语体现在JAVA语言中就是sum.misc.Unsafe类中的各个方法。调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令。这是一种完全依赖与硬件的功能,通过他实现了原子操作。再次强调,由于CAS是一种系统原语,原语是属于操作系统范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被打断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
 public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this  var2:valueOffset  var4:1
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
     //将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
        return var5;
    }

2.4简单总结

什么是CAS?

​ CAS(CompareAndSwap)比较当前工作内存中的值和主内存的值,如果相同执行规定操作,否则继续比较直到主内存和工作内存中的值一致为止。

CAS应用

​ CAS有3个操作数,内存值V,旧的预期值A,要修改的值B,当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。

2.5CAS缺点

  • 循环时间长,开销很大。通过上面源码可知,getAndAddInt()方法执行时,有个do while。如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能给CPU带来很大的开销。
  • 只能保证一个共享变量的原子操作。通过源码可知,cas只对当前变量(this)起作用。
  public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }
  • ABA问题。即狸猫换太子,A->B->A。线程t1 、t2同时去修改某个值A,t1慢,t2快,t2将A->B->A,t1再去compareAndSwap时发现内存指V和预期值A相同,就会执行相关操作。尽管t1线程操作成功,但不代表这个过程是没有问题的。
public class ABADemo {
    public static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
    public static void main(String[] args) throws InterruptedException {
         new Thread(()->{
                    atomicReference.compareAndSet(100,101);
                    atomicReference.compareAndSet(101,100);
                 },"t1").join();//注意这里是用到了join()方法,会等待当前线程执行完之后,才会执行其它线程。

          new Thread(()->{
              System.out.println(atomicReference.compareAndSet(100, 2021)+"\t"+atomicReference.get());
          },"t2").start();

    }
}

运行结果:

true	2021

2.6ABA问题的解决

public class ResolveABADemo {
    public static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第二次版本号" + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "\t第三次版本号" + atomicStampedReference.getStamp());

        }, "t1").start();
        new Thread(() -> {
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean b = atomicStampedReference.compareAndSet(100, 2021, stamp, stamp+ 1);
            System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + b + "\t第四次版本号" + atomicStampedReference.getStamp());
        }, "t2").start();
    }
}

t2	第一次版本号1
t1	第二次版本号2
t1	第三次版本号3
t2	修改成功否:false	第四次版本号3

3、集合类不安全

3.1ArrayList

public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
         for (int i=1;i<=30;i++){
                     new Thread(()->{
                         list.add(UUID.randomUUID().toString().substring(0,5));
                         System.out.println(list);
                     },String.valueOf(i)).start();
                 }
    }
}

运行之后报了异常java.util.ConcurrentModificationException

3.1.1解决方案:

  • 改用vector,
public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new Vector<>();
         for (int i=1;i<=30;i++){
                     new Thread(()->{
                         list.add(UUID.randomUUID().toString().substring(0,5));
                         System.out.println(list);
                     },String.valueOf(i)).start();
                 }
    }
}

通过下面源码可知,vector对整个方法加了锁

public synchronized void addElement(E obj) {
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = obj;
    }
  • 给ArrayList加锁
public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = Collections.synchronizedList(new ArrayList<>());
         for (int i=1;i<=30;i++){
                     new Thread(()->{
                         list.add(UUID.randomUUID().toString().substring(0,5));
                         System.out.println(list);
                     },String.valueOf(i)).start();
                 }
    }
}
  • CopyOnWriteArrayList写时复制
public class ArrayListDemo {
    public static void main(String[] args) {
        List<String> list = new CopyOnWriteArrayList<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 5));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

3.2HashSet

public class HashSetDemo {
    public static void main(String[] args) {
        Set<String> list = new HashSet<>();
        for (int i = 1; i <= 30; i++) {
            new Thread(() -> {
                list.add(UUID.randomUUID().toString().substring(0, 8));
                System.out.println(list);
            }, String.valueOf(i)).start();
        }
    }
}

运行之后报了异常java.util.ConcurrentModificationException

3.2.1解决方案

  • Collections.synchronizedSet(new HashSet<>())

  • new CopyOnWriteArraySet<>()

3.2.2底层原理

HashSet底层是HashMap,当我们添加元素时

HashSet<String> strings = new HashSet<String>;
        strings.add("a");

那么这个value去哪了?看下源码

public boolean add(E e) {
        return map.put(e, PRESENT)==null;
    }

点开PRESENT发现:

  private static final Object PRESENT = new Object();

3.3HashMap

public class HashMapDemo {
    public static void main(String[] args) {
        HashMap<String, String> hashMap = new HashMap<>();
         for (int i=1;i<=30;i++){
                     new Thread(()->{
                         hashMap.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));
                         System.out.println(hashMap);
                     },String.valueOf(i)).start();
                 }
    }
}

运行之后报了异常java.util.ConcurrentModificationException

3.3.1解决方案

  • ConcurrentHashMap
  • Collections.synchronizedMap(new HashMap<>())

4、锁

4.1公平锁和非公平锁

公平锁:排队,先来后到。类似与排队打饭。

非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的

4.2可重入锁

​ 可重入锁又名递归锁,指的是自己可以再次获取自己的内部锁。比如一个线程获得了某个对象的锁,此时这个对象锁还没有释放,当其再次想要获取这个对象的锁的时候还是可以获取的,如果不可锁重入的话,就会造成死锁。同一个线程每次获取锁,锁的计数器都自增 1,所以要等到锁的计数器下降为 0 时才能释放锁。

​ ReentrantLock/Synchronized就是典型的可重入锁。可重入锁最大的作用是避免死锁。

4.2.1Synchronized

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

        new Thread(()->{
            phone.sendSms();
        },"t2").start();
    }
}
public class Phone {
    public  synchronized void sendSms(){//外部锁
        System.out.println(Thread.currentThread().getId()+"\t invoked sendSms()");
        sendEmail();//内部锁
    }
    public  synchronized void sendEmail(){
        System.out.println(Thread.currentThread().getId()+"\t ##invoked sendEmail()");
    }
}

运行结果:

11	 invoked sendSms()
11	 ##invoked sendEmail()
12	 invoked sendSms()
12	 ##invoked sendEmail()

4.2.2ReentrantLock

public class Phone implements Runnable {
    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        get();
    }
    public void get(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t invoked get()");
            set();//内部锁
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t invoked set()");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class ReentrantLockDemo  {
    public static void main(String[] args) {
        Phone phone=new Phone();
        Thread t1 = new Thread(phone,"t1");
        Thread t2 = new Thread(phone,"t2");
        t1.start();
        t2.start();
    }
}

t1	 invoked get()
t1	 invoked set()
t2	 invoked get()
t2	 invoked set()

面试真题

我在get()方法里面又加了一把锁lock.lock();lock.unlock();现在总共有两把锁,结果怎样?

public class Phone implements Runnable {

    Lock lock=new ReentrantLock();
    @Override
    public void run() {
        get();
    }
    public void get(){
        lock.lock();
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t invoked get()");
            set();//内部锁
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
            lock.unlock();
        }
    }
    public void set(){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t invoked set()");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
t1	 invoked get()
t1	 invoked set()
t2	 invoked get()
t2	 invoked set()

4.3自旋锁

​ 自旋锁(spinlock)是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程的上下文切换,缺点是循环会消耗cpu。下面的源码是cas学习时的代码。

 public final int getAndAddInt(Object var1, long var2, int var4) {//var1:this  var2:valueOffset  var4:1
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
     //将var5与当前内存的值进行比较,相同更新var5+var4并且返回true,不同,继续取值并且比较,直到更新完成。
        return var5;
    }

代码验证:

public class SpinLockDemo {
    //原子引用线程
    AtomicReference<Thread> atomicReference=new AtomicReference<>();
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName()+"\t come in O(∩_∩)O");
        while (!atomicReference.compareAndSet(null,thread)){

        };
    }
    public void unLock(){
        Thread thread=Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName()+"\t invoked unLock()");
    }

    public static void main(String[] args) {
        SpinLockDemo spinLockDemo = new SpinLockDemo();
         new Thread(()->{
                   spinLockDemo.myLock();
                   try {
                       TimeUnit.SECONDS.sleep(5);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
                   spinLockDemo.unLock();
                 },"AA").start();
         
         try {
             TimeUnit.SECONDS.sleep(1);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         
          new Thread(()->{
                     spinLockDemo.myLock();
                     try {
                         TimeUnit.SECONDS.sleep(1);
                     } catch (InterruptedException e) {
                         e.printStackTrace();
                     }
                     spinLockDemo.unLock();
                  },"BB").start();
    }
}
AA	 come in O(∩_∩)O		
BB	 come in O(∩_∩)O
AA	 invoked unLock()
BB	 invoked unLock()

AA先进来,AA需要等待5s才能释放,1s之后,BB进来等待AA锁的释放,5s之后呢,AA释放锁,BB在1s之后释放锁。

4.4独占锁(写锁)/共享锁(读锁)/互斥锁

独占锁:指该锁一次只能被一个线程持有。对ReentrantLock和Synchronized而言都是独占锁

共享锁:指该锁可被多个线程所持有。对ReentrantReadWriteLock而言,它的读写,写读,写写的过程是互斥的。

4.4.1代码验证读写锁

写:写的过程是独占+原子

Befare

public class MyCache {
    private volatile Map<String,Object>  map=new HashMap<>();
    private ReentrantReadWriteLock rwLook=new ReentrantReadWriteLock();
    public void put(String key,Object value){
            System.out.println(Thread.currentThread().getName()+"\t 正在写入"+key);
            try {
                TimeUnit.MICROSECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"\t 写入完成");
    }
    public void get(String key){

            System.out.println(Thread.currentThread().getName()+"\t 正在读取");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);

    }
}

public class ReentrantReadWriteLockDemo {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
         for (int i=1;i<=5;i++){
             int finalI = i;
             new Thread(()->{
                  myCache.put(finalI+"", finalI+"");
                     },String.valueOf(i)).start();
                 }

        for (int i=1;i<=5;i++){
            int finalI = i;
            new Thread(()->{
                myCache.get(finalI+"");
            },String.valueOf(i)).start();
        }
    }
}

运行结果:

1	 正在写入1
2	 正在写入2
3	 正在写入3
4	 正在写入4
2	 写入完成
1	 写入完成
3	 写入完成
5	 正在写入5
1	 正在读取
4	 写入完成
5	 写入完成
2	 正在读取
3	 正在读取
5	 正在读取
4	 正在读取
1	 读取完成:1
2	 读取完成:2
3	 读取完成:3
5	 读取完成:5
4	 读取完成:4

After:加了读写锁

public class MyCache {
    private volatile Map<String,Object>  map=new HashMap<>();
    private ReentrantReadWriteLock rwLook=new ReentrantReadWriteLock();
    public void put(String key,Object value){
        rwLook.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t 正在写入"+key);
            try {
                TimeUnit.MICROSECONDS.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        map.put(key,value);
            System.out.println(Thread.currentThread().getName()+"\t 写入完成");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLook.writeLock().unlock();
        }
    }
    public void get(String key){
        rwLook.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"\t 正在读取");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object result = map.get(key);
            System.out.println(Thread.currentThread().getName()+"\t 读取完成:"+result);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLook.writeLock().unlock();
        }
    }
}

运行结果:

1	 正在写入1
1	 写入完成
2	 正在写入2
2	 写入完成
3	 正在写入3
3	 写入完成
4	 正在写入4
4	 写入完成
5	 正在写入5
5	 写入完成
1	 正在读取
1	 读取完成:1
2	 正在读取
2	 读取完成:2
3	 正在读取
3	 读取完成:3
4	 正在读取
4	 读取完成:4
5	 正在读取
5	 读取完成:5

4.5CountDownLatch(闭锁)

4.5.1初识CountDownLatch

让一些线程阻塞,直到另一些线程完成一系列操作之后才被唤醒

before

public class CountDownLatchDemo {
    public static void main(String[] args) {
         for (int i=1;i<=10;i++){
                     new Thread(()->{
                         System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室~~~");
                     },String.valueOf(i)).start();
                 }

        System.out.println(Thread.currentThread().getName()+"\t 学生都走完了,班长来锁门~~~~");
    }
}
1	 上完自习离开教室~~~
2	 上完自习离开教室~~~
main	 学生都走完了,班长来锁门~~~~
3	 上完自习离开教室~~~
4	 上完自习离开教室~~~
5	 上完自习离开教室~~~
6	 上完自习离开教室~~~
9	 上完自习离开教室~~~
7	 上完自习离开教室~~~
10	 上完自习离开教室~~~
8	 上完自习离开教室~~~

after

public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(10);
        for (int i=1;i<=10;i++){
                     new Thread(()->{
                         System.out.println(Thread.currentThread().getName()+"\t 上完自习离开教室~~~");
                         countDownLatch.countDown();
                     },String.valueOf(i)).start();
                 }
        countDownLatch.await();//等待减到0才能往下走
        System.out.println(Thread.currentThread().getName()+"\t 学生都走完了,班长来锁门~~~~");
    }
}
1	 上完自习离开教室~~~
2	 上完自习离开教室~~~
3	 上完自习离开教室~~~
4	 上完自习离开教室~~~
5	 上完自习离开教室~~~
6	 上完自习离开教室~~~
9	 上完自习离开教室~~~
10	 上完自习离开教室~~~
7	 上完自习离开教室~~~
8	 上完自习离开教室~~~
main	 学生都走完了,班长来锁门~~~~

4.5.2CountDownLatch与Enum结合的妙用

public enum CountEnum {
ONE(1,"齐"),TWO(2,"楚"),THREE(3,"韩"),FOUR(4,"魏"),FIVE(5,"赵"),SIX(6,"燕");


    private Integer retCode;
    private String retMessage;

    //遍历,才能确定你要用哪一个值
    public static CountEnum forEach_CountEnum(int index){
        CountEnum[] myArray = CountEnum.values();
        for (CountEnum element : myArray) {
            if (index==element.retCode){
                return element;
            }
        }
        return null;
    }




    CountEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public Integer getRetCode() {
        return retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }
}
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i=1;i<=6;i++){
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"\t 国,被灭掉~~~");
                countDownLatch.countDown();
            },CountEnum.forEach_CountEnum(i).getRetMessage()).start();
        }
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName()+"\t  #####秦国,一统华夏!!!");
    }
}
齐	 国,被灭掉~~~
楚	 国,被灭掉~~~
韩	 国,被灭掉~~~
赵	 国,被灭掉~~~
燕	 国,被灭掉~~~
魏	 国,被灭掉~~~
main	  #####秦国,一统华夏!!!

4.6CyclicBarrier

​ CyclicBarrier的字面意思是可循环使用的屏障。它要做的事情就是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。

​ 说人话,集齐七颗龙珠才能召唤神龙。

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

         for (int i=1;i<=7;i++){
             int finalI = i;
             new Thread(()->{
                         System.out.println(Thread.currentThread().getName()+"集齐第"+ finalI +"颗龙珠~~~");
                 try {
                     cyclicBarrier.await();
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 } catch (BrokenBarrierException e) {
                     e.printStackTrace();
                 }
             },String.valueOf(i)).start();
                 }
    }
}
1集齐第1颗龙珠~~~
2集齐第2颗龙珠~~~
3集齐第3颗龙珠~~~
4集齐第4颗龙珠~~~
5集齐第5颗龙珠~~~
6集齐第6颗龙珠~~~
7集齐第7颗龙珠~~~
***集齐七颗龙珠,召唤神龙~~~

CyclicBarrier与``CountDownLatch`的用法有些类似,最大的区别就是,CyclicBarrier是做加法的,CountDownLatch是来做减法的。

4.7Semaphore

​ Semaphore(信号量)主要有两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制

public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3);//3个车位
         for (int i=1;i<=6;i++){//6个线程来抢占这个车位
                     new Thread(()->{
                         try {
                             semaphore.acquire();
                             System.out.println(Thread.currentThread().getName()+"\t 抢占车位");
                             try {
                                 TimeUnit.SECONDS.sleep(3);
                             } catch (InterruptedException e) {
                                 e.printStackTrace();
                             }
                             System.out.println(Thread.currentThread().getName()+"\t 停车3s,开走");
                         } catch (InterruptedException e) {
                             e.printStackTrace();
                         } finally {
                             semaphore.release();

                         }

                     },String.valueOf(i)).start();
                 }
    }
}
1	 抢占车位
2	 抢占车位
3	 抢占车位
2	 停车3s,开走
1	 停车3s,开走
4	 抢占车位
3	 停车3s,开走
5	 抢占车位
6	 抢占车位
4	 停车3s,开走
6	 停车3s,开走
5	 停车3s,开走

5、阻塞队列

5.1BlookingQueue的概念

  • 当阻塞队列是空时,从队列中获取元素的操作将会被阻塞,直到有线程往队列中添加元素
  • 当阻塞队列时满时,从队列中添加元素的操作将会被阻塞,直到有线程从队列中获取元素

5.2BlockingQueue的优点

​ 在多线程领域,所谓阻塞,就是在某些情况下会``挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒`。

​ 有了BlockingQueue之后,我们就不需要关心什么时候阻塞线程(挂起),什么时候需要唤醒,因为这一切BlockingQueue都给你一手包办了。在concurrent包发布以前,在多线程的环境下,我们每个程序员都必须要自己去控制这些细节,尤其是还要兼顾效率和线程安全,而这样会给我们的程序带来不小的复杂度。

5.3BlockingQueue的实现类

在这里插入图片描述

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界阻塞队列(但是默认值为Integer.MAX_VALUE ,21亿)
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现的延迟无边界阻塞队列
  • SynchronousQueue不存储元素的阻塞队列,也即单个元素的队列,生产一个消费一个,你不消费我不生产
  • LinkedTransferQueue:由链表结构组成的无界阻塞队列
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列

5.4BlockingQueue的核心方法

方法类型抛出异常特殊值阻塞超时
插入add(e)offer(e)put(e)offer(e,time,unit)
移除remove()poll()take()poll(time,unit)
检查elementpeek()不可用不可用
  • 抛出异常:当阻塞队列满时,再往队列里面add()时,会抛出java.lang.IllegalStateException: Queue full

    ​ 当阻塞队列空时,再去队列里面remove()时,会抛出java.util.NoSuchElementException

  • 特殊值:插入方法,成功true,失败false

    ​ 移除方法,成功返回出队列的元素,队列里面没有就返回null

  • 阻塞:当阻塞队列满时,生产者线程继续往队列里面put元素,队列会一直阻塞生产线程直到put数据成功或响应中断退出

    ​ 当阻塞队列空时,消费者线程视图从队列里面take元素,队列会一直阻塞消费者线程直到队列可用

  • 超时:当阻塞队列满时,队列会阻塞生产者线程指定的时间,超时后,生产者线程自动退出。

5.5阻塞队列的用处

  1. 生产者消费者模式
  2. 线程池
  3. 消息中间件

5.6生产者消费者模式

1、线程 操作 资源类

2、判断 干活 通知

3、防止虚假唤醒

题目:一个初始值为0的变量,两个线程对其交替操作,一个加一一个减一,来5轮

  • 传统方式
public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
          new Thread(()->{
              for (int i = 0; i <= 5; i++) {
                  shareData.increase();
              }
                  },"AA").start();
        new Thread(()->{
            for (int i = 0; i <= 5; i++) {
                shareData.desc();
            }
        },"BB").start();
    }
}
class ShareData{
    private int number=0;
    private Lock myLock=new ReentrantLock();
    final Condition condition  = myLock.newCondition();
    public void increase(){
        myLock.lock();
        try {
        while (number!=0){
            condition.await();
        }
        number++;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myLock.unlock();
        }
    }
    public void desc(){
        myLock.lock();
        try {
            while (number==0){
                condition.await();
            }
            number--;
            System.out.println(Thread.currentThread().getName()+"\t"+number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myLock.unlock();
        }
    }
}
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0
AA	1
BB	0

Process finished with exit code 0
  • 阻塞队列模式
public class shareData{
    private  volatile boolean FLAG=true;
   AtomicInteger atomicInteger= new AtomicInteger();
   BlockingQueue<String> blockingQueue=null;

    public shareData(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
    }

    public void product() throws InterruptedException {
        String data=null;
        boolean retValue=false;
        while (FLAG){
            data=atomicInteger.incrementAndGet()+"";
             retValue = blockingQueue.offer(data, 2, TimeUnit.SECONDS);
            if (retValue){
                System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"成功");
            }else{
                System.out.println(Thread.currentThread().getName()+"\t 插入队列"+data+"失败");
            }
            TimeUnit.SECONDS.sleep(1);
            }
        System.out.println(Thread.currentThread().getName()+"\t 大老板叫停了,表示flag=false");
        }
    public void consume() throws InterruptedException {
        String result=null;
        while (FLAG){
             result = blockingQueue.poll(2, TimeUnit.SECONDS);
             if (result==null  || result.equalsIgnoreCase("")){
                 FLAG=false;
                 System.out.println(Thread.currentThread().getName()+"\t 超过2s没有取到蛋糕,消费结束");
                 System.out.println();
                 System.out.println();
                 return;
             }
            System.out.println(Thread.currentThread().getName()+"\t 消费队列"+result+"成功");
        }
    }
    public void stop(){
        this.FLAG=false;
    }
}


public class ProdConsumer_BlockingQueueDemo {
    public static void main(String[] args) {
        shareData shareData = new shareData(new ArrayBlockingQueue<>(10));
         new Thread(()->{
             System.out.println("生产者线程启动。。。。");
             try {
                 shareData.product();
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
         },"prod").start();
        new Thread(()->{
            System.out.println("消费者线程启动。。。。");
            try {
                shareData.consume();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"consume").start();
        //停止线程5s
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println();
        System.out.println();
        System.out.println();
        System.out.println("5s时间到,大老板叫停,活动结束");
        shareData.stop();
    }
}
生产者线程启动。。。。
消费者线程启动。。。。
prod	 插入队列1成功
consume	 消费队列1成功
prod	 插入队列2成功
consume	 消费队列2成功
prod	 插入队列3成功
consume	 消费队列3成功
prod	 插入队列4成功
consume	 消费队列4成功
prod	 插入队列5成功
consume	 消费队列5成功



5s时间到,大老板叫停,活动结束
prod	 大老板叫停了,表示flag=false
consume	 超过2s没有取到蛋糕,消费结束



Process finished with exit code 0

5.7详解Condition接口

​ condition接口可以实现精确唤醒 。

题目:多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:

AA打印5次,BB打印10次,CC打印15次

紧接着

AA打印5次,BB打印10次,CC打印15次

。。。

。。。

总共10轮

public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
          new Thread(()->{
              for (int i = 0; i < 10; i++) {
                  shareData.print5();
              }
                  },"AA").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                shareData.print10();
            }
        },"BB").start();
        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                shareData.print15();
            }
        },"CC").start();
    }
}

class ShareData{
    private int number=0;//A:0  B:1  C:2
    private Lock myLock=new ReentrantLock();
    final Condition c1  = myLock.newCondition();
    final Condition c2  = myLock.newCondition();
    final Condition c3  = myLock.newCondition();
    public void print5(){
        myLock.lock();
        try {
            while (number!=0){c1.await();}
            for (int i = 1; i <=5; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number=1;
            c2.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myLock.unlock();
        }
    }
    public void print10(){
        myLock.lock();
        try {
            while (number!=1){c2.await();}
            for (int i =1; i <=10; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number=2;
            c3.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myLock.unlock();
        }
    }
    public void print15(){
        myLock.lock();
        try {
            while (number!=2){c3.await();}
            for (int i =1; i <=15; i++) {
                System.out.println(Thread.currentThread().getName()+"\t"+i);
            }
            number=0;
            c1.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            myLock.unlock();
        }
    }
}
AA	1
AA	2
AA	3
AA	4
AA	5
BB	1
BB	2
BB	3
BB	4
BB	5
BB	6
BB	7
BB	8
BB	9
BB	10
CC	1
CC	2
CC	3
CC	4
CC	5
CC	6
CC	7
CC	8
CC	9
CC	10
CC	11
CC	12
CC	13
CC	14
CC	15
AA	1
AA	2
AA	3
AA	4
AA	5
BB	1
BB	2
BB	3
BB	4
BB	5
BB	6
BB	7
BB	8
BB	9
BB	10
CC	1
CC	2
CC	3
CC	4
CC	5
CC	6
CC	7
CC	8
CC	9
CC	10
CC	11
CC	12
CC	13
CC	14
CC	15
···
···
···

6、Callable

6.1java实现多线程的方式:

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池

6.2Callable和Runnable的区别

public class CallableDemo implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return null;
    }
}
class RunnableDemo implements Runnable{
    @Override
    public void run() {
        
    }
}
  • Callable可以抛出异常
  • Callable有返回值
  • 实现的方法不一样,run() call()

6.3Callable的使用

public class MyThread implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println(Thread.currentThread().getName()+"*******Callable   come in");
        return 10;
    }
}
class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask<Integer> integerFutureTask = new FutureTask<>(new MyThread());
        FutureTask<Integer> integerFutureTask2 = new FutureTask<>(new MyThread());
        Thread t1 = new Thread(integerFutureTask,"A");
        Thread t2 = new Thread(integerFutureTask2,"B");
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName()+"\t ****");
        int result2=10;
        Integer result1 = integerFutureTask.get();
        System.out.println("result:"+(result1+result2));//获取返回值
    }
}
main	 ****
A*******Callable   come in
B*******Callable   come in
result:20

7、线程池

7.1为什么要用线程池

​ 线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出的数量排队等候,等其它线程执行完毕,再从队列中取出任务来执行。主要特点:线程复用:控制最大的并发数;管理线程。

7.2线程池的使用

  1. Executors.newFixedThreadPool(int)

    • 固定线程数量的线程池,可以控制最大并发数,超出的线程会在队列中等待

      底层源码:

       public static ExecutorService newFixedThreadPool(int nThreads) {
              return new ThreadPoolExecutor(nThreads, nThreads,
                                            0L, TimeUnit.MILLISECONDS,
                                            new LinkedBlockingQueue<Runnable>());
          }
      

      代码示例:

    public class ThreadPoolDemo {
        public static void main(String[] args) {
            ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
            //模拟10个用户来办理"业务,每个用户就是一个请求线程
            try {
                for (int i = 0; i < 10; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() +"\t 办理业务");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            threadPool.shutdown();
            }
    
        }
    }
    
    pool-1-thread-1	 办理业务
    pool-1-thread-4	 办理业务
    pool-1-thread-3	 办理业务
    pool-1-thread-4	 办理业务
    pool-1-thread-4	 办理业务
    pool-1-thread-2	 办理业务
    pool-1-thread-2	 办理业务
    pool-1-thread-4	 办理业务
    pool-1-thread-3	 办理业务
    pool-1-thread-5	 办理业务
    
  2. Executors.newSingleThreadExecutor()

    • 一池一线程,创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有的任务按照舒徐执行。

      底层源码:

       public static ExecutorService newSingleThreadExecutor() {
              return new FinalizableDelegatedExecutorService
                  (new ThreadPoolExecutor(1, 1,
                                          0L, TimeUnit.MILLISECONDS,
                                          new LinkedBlockingQueue<Runnable>()));
          }
      
    public class ThreadPoolDemo {
        public static void main(String[] args) {
            //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
            ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程
            //模拟10个用户来办理"业务,每个用户就是一个请求线程
            try {
                for (int i = 0; i < 10; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() +"\t 办理业务");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            threadPool.shutdown();
            }
    
        }
    }
    
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    pool-1-thread-1	 办理业务
    
  3. Executors.newCachedThreadPool()

    • 一池N线程,创建一个可缓存的线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则创建新线程。使用了SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就会销毁线程。底层:

      public static ExecutorService newCachedThreadPool() {
              return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                            60L, TimeUnit.SECONDS,
                                            new SynchronousQueue<Runnable>());
          }
      

      代码示例:

    public class ThreadPoolDemo {
        public static void main(String[] args) {
            //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
            //ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程
            ExecutorService threadPool = Executors.newCachedThreadPool();//
            //模拟10个用户来办理"业务,每个用户就是一个请求线程
            try {
                for (int i = 0; i < 10; i++) {
                    threadPool.execute(()->{
                        System.out.println(Thread.currentThread().getName() +"\t 办理业务");
                    });
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
            threadPool.shutdown();
            }
        }
    }
    
    
    pool-1-thread-1	 办理业务
    pool-1-thread-2	 办理业务
    pool-1-thread-3	 办理业务
    pool-1-thread-4	 办理业务
    pool-1-thread-6	 办理业务
    pool-1-thread-7	 办理业务
    pool-1-thread-8	 办理业务
    pool-1-thread-9	 办理业务
    pool-1-thread-10	 办理业务
    pool-1-thread-5	 办理业务
    

7.3线程池的七大参数

 public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }
  1. int corePoolSize 核心线程数(一直存在,除非设置了(allowCoreThreadTimeOut));线程池创建好以后就准备就绪的线程数量,就等待来接收异步任务去执行。好比与Thread thread=new Thread(); 而没有调用start()。

  2. int maximumPoolSize 最大可执行线程数量,控制资源

  3. long keepAliveTime 存活时间 如果当前正在运行的线程数量大于corePoolSize数量,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁,只剩下corePoolSize

  4. TimeUnit unit keepAliveTime时间单位

  5. BlockingQueue<Runnable> workQueue 阻塞队列。被提交,但是尚未执行的任务。

  6. ThreadFactory threadFactory 线程的创建工厂

  7. RejectedExecutionHandler handler 如果队列满了,按照指定的拒绝策略,拒绝执行任务

    案例:5个人到银行办理业务,银行有两个窗口,这里的5个人,是没有超过最大可执行线程数量的(5),所以说只会使用指定了的核心线程数(2),当我们把5个人改为10个人之后,就会直接使用最大可执行线程(5)的数量来执行,超过的(5)其中会有指定了的阻塞队列的数量(3),在阻塞区等候,剩下的2个会根据拒绝策略来执行相关的操作,我们这里使用的是超过的直接丢弃策略。

public class ThreadPoolDemo1 {
    public static void main(String[] args) {
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                100L,
                TimeUnit.SECONDS,
                new LinkedBlockingDeque<>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.AbortPolicy()
        );

       try {
           for (int i = 1; i <= 5; i++) {//顾客数
               int finalI = i;
               threadPool.execute(()->{
                   System.out.println(Thread.currentThread().getName()+"号窗口,"+"服务顾客"+ finalI);
                   try {
                       TimeUnit.SECONDS.sleep(4);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               });

           }
       } catch (Exception e) {
           e.printStackTrace();
       } finally {
           threadPool.shutdown();
       }
    }
}

7.4线程池的底层原理

原理图:

在这里插入图片描述

这个原理图可以结合上面去银行办理业务那个例子来理解。

线程池的主要处理流程

在这里插入图片描述

  1. 在创建了线程之后,等待提交过来的任务请求。

  2. 当调用execute()方法添加一个任务请求时,线程池会做如下判断

    ​ 2.1如果正在 运行的线程数量小于corePoolSize,马上创建线程执行这个任务。

    ​ 2.2如果正在运行的线程数量大于或者等于corePoolSize,那么将这个任务放入队列

    ​ 2.3如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻来运行这个任务。

    ​ 2.4如果队列满了且正在运行的线程数量大于或者等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。

  3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

  4. 当一个线程无事可做超过指定的时间(keepAliveTime)时,线程会判断:

    ​ 如果当前运行的线程数大于corePoolSize,那么这个线程就会被停掉。

    ​ 如果线程池的所有任务完成后它最终会收缩到corePoolSize的大小

7.5线程池的拒绝策略

  • AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
  • CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
  • DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中再次尝试提交当前任务
  • DiscardPolicy:直接丢弃超出的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

以上拒绝策略均实现了RejectedExceutionHandler接口。

7.6线程池那么多怎么选

一个都不选,阿里巴巴开发手册。

7.7合理配置线程数

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

CPU密集型:任务配置尽可能少的线程数量,一般公式:CPU核数+1个线程的线程池。

IO密集型:由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2

8、死锁编码及定位分析

8.1什么是死锁

​ 死锁是指两个或以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉,那它们就无法推进下去

在这里插入图片描述

public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA="lockA";
        String lockB="lockB";
        new Thread(new HoldLockThread(lockA,lockB),"ThreadAAA").start();
        new Thread(new HoldLockThread(lockB,lockA),"ThreadBBB").start();

    }
}
public class HoldLockThread implements Runnable{
    private String lockA;
    private String lockB;

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

    @Override
    public void run() {
        synchronized (lockA){
            System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockA+"\t 尝试获得:"+lockB);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB){
                System.out.println(Thread.currentThread().getName()+"\t 自己持有:"+lockB+"\t 尝试获得:"+lockA);
            }
        }

    }
}

8.2死锁定位分析

-jps -l 定位

在这里插入图片描述

jstack +线程id 查看故障报告

在这里插入图片描述

9、JVM

9.1什么是GC Roots

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象。譬如各个线程被调用的方法堆栈中使用到的参数、局部变量表、临时变量等。
  2. 在方法区中类静态属性引用的对象,譬如Java类的应用类型静态变量
  3. 方法区中常量引用的对象,譬如字符串常量池(string pool)里的引用
  4. 本地方法栈中Native引用的对象

9.2JVM的参数类型

XX参数

9.2.1Boolean类型:

公式:-XX:+或者 -某个属性值

​ +表示开启

​ -表示关闭

public class GcDemo {
    public static void main(String[] args) {
        System.out.println(&quot;###hello!!!&quot;);
        try {
            TimeUnit.SECONDS.sleep( Integer.MAX_VALUE);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

是否打印GC细节

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

9.2.2KV设值类型

公式:-XX属性key=属性值value

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

其它更多的类型可以使用 jinfo -flags +线程id查看

 -XX:CICompilerCount=3
 -XX:InitialHeapSize=268435456
 -XX:MaxHeapSize=4263510016 
 -XX:MaxNewSize=1420820480
 -XX:MetaspaceSize=1073741824
 -XX:MinHeapDeltaBytes=524288 
 -XX:NewSize=89128960 
 -XX:OldSize=179306496 
 -XX:+UseCompressedClassPointers 
 -XX:+UseCompressedOops 
 -XX:+UseFastUnorderedTimeStamps 
 -XX:-UseLargePagesIndividualAllocation 
 -XX:+UseParallelGC
 Command line:  -XX:MetaspaceSize=1024m //人工设置

9.2.3题外话

-Xms是属于哪一种?-XX:InitialHeapSize

-Xmx是属于哪一种?-XX:MaxHeapSize

9.3JVM默认值

java -XX:+PrintFlagsInitial 用来查看初始默认值

java -XX:+PrintFlagsFinal -version 用来查看修改更新

带冒号的是JVM或者认为修改过的值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z8AdKWqb-1608452293195)(%E5%A4%A7%E5%8E%82%E9%9D%A2%E8%AF%95%E9%A2%98%E7%AC%AC%E4%BA%8C%E5%AD%A3.assets/image-20201219140403775.png)]

java -XX:+PrintCommandLineFlags -version

9.4JVM常用参数设置

  • -Xms 初始内存大小,默认为物理内存的1/64,等价于==-XX:InitialHeapSize==
  • -Xmx 最大分配内存,默认为物理内存的1/4,等价于==-XX:MaxHeapSize==
  • -Xss 设置单个栈的大小,一般默认为512k~1024k ,等价于==-XX:ThreadStackSize==
  • -Xmn 设置年轻代的大小
  • -XX:MetaspaceSize 设置元空间大小,元空间的本质和永久代类似,都是对JVM规范中方法区的实现,不过元空间和永久代最大的区别在于:元空间并不在虚拟内存中,而是使用直接内存(Direct Memory),因此默认情况下,元空间的大小仅受本地内存的限制。元空间的出厂默认值:-XX:MetaspaceSize=21m
  • 2
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值