2020大厂面试JUC线程重要技术点【集合+线程+阻塞队列+线程池】(上)

简介: 2020大厂面试JUC线程重要技术点【集合+线程+阻塞队列+线程池】(上)

一、集合安全问题


1.1 ArrayList


  • 空的集合初始值为10
  • object类型的数组
  • 扩容Arrays.copyOf 原始大小的一倍
  • 线程不安全


1.1.1 不安全

java.util.concurrentModificationException

20200720135836521.png


  • Vector加了锁保证了数据一致性,但是并发性急剧下降,所以很少用!
  • ArrayList牺牲了线程安全从而保证并发性


1.1.2 如何解决ArrayList线程不安全问题


1.new Vector<>()

2.Collection与Collections


  • Collection为集合类的父接口
  • Collections为辅助类来解决ArrayList线程不安全问题
List<String> list = Collections.synchronizedList(new ArrayList<>());


3.CopyOnWriteArrayList<>()类


写时复制 读写分离的思想

List<String> list = new CopyOnWriteArrayList<>();

private tranisent volatile []…


1.2 HashSet


底层:HashMap 初始值16 负载因子0.75


线程不安全解决的问题与上面雷同


解决办法一Collections.synchronizedSet():


20200720151403239.png


解决办法二CopyOnWriteArraySet<>():

20200720151548562.png

1.3 HashMap


演示错误 java.util.concurrentModificationException

20200720152506746.png

解决办法一:


Map<String,String> map = new ConcurrentHashMap<>();


解决办法二:

Collections.synchronizedMap();


二、JAVA锁机制


公平锁:多个新线程按照申请顺序来获取锁,先到先得 非

非公平锁:多个线程并不是按照申请的顺序,有可能造成优先级反转或者饥饿现象。



2.1 可重入锁【递归锁】


ReentrantLock

线程可以进入任何一个它已经拥有的锁同步着的代码块

通过构造函数制定该锁是否为公平锁,默认是非公平锁

非公平锁的优势在于吞吐量比较大

对于Synchronized而言,也是一种非公平锁

作用:避免死锁


2.2 自旋锁

是指尝试获取锁的线程不会阻塞,而是采用循环的方式来尝试乎获取锁,这样的好处就是减少线程上下文的切换消耗,缺点是循环会消耗CPU.
do
while()
CAS
期望值与工作区间的值比较

2.3 独占锁(写锁)/共享锁(读锁)


独占锁:指该锁一次只能被一个线程所持有的。

ReentrantLock Synchronized 都是独占锁

共享锁:该锁可以被多个线程所持有

ReentrantReadWriteLock为共享锁,写锁为独占锁

读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程都是互斥的。


一个线程去写【原子+独占】绝对不可以被阻断,多个线程可以读

【问题描述如下:】


class MyCache{//缓存资源类
  //volatile 可见性 不保证原子性 禁止指令重排
  private volatile Map<String,Object> map = new HashMap<>();
  //解决问题 原子性
  //private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock ();
  public void put(String key,Object value){
    //加写锁
    rwLock.writeLock().lock();
    try{
      System.out.println(Thread.currentThread().getName()+"正在写入"+key);
      try{Time.MILLSECONDS.sleep(300);}catch(){};
      map.put(key,value);
      System.out.println(Thread.currentThread().getName()+"写入完成");
    }catch(Exception e){
    }finally{
      rwLock.writeLock().unlock();
    }
  }
  public void get(String key,Object value){
    //加读锁
    rwLock.readLock().lock();
    try{
    System.out.println(Thread.currentThread().getName()+"正在读取"+key);
    try{Time.MILLSECONDS.sleep(300);}catch(){};
    Object result = map.get(key);
    System.out.println(Thread.currentThread().getName()+"读取完成"+result);
    }catch(Exception e){
    }finally{
      rwLock.readLock().unlock();
    }
  }
}
public class ReadWriteLockDemo{
  public static void main(String[] args){
    MyCache myCache = new MyCache();
    //写
    for(int i = 1;i <= 5;i++){
      new Thread(() -> {
        final int tempInt = i;
        myCache.put(tempInt+"",tempInt+"");
      },String.valueOf(i)).start();
    }
    //读
    for(int i = 1;i <= 5;i++){
      new Thread(() -> {
        final int tempInt = i;
        myCache.get(tempInt+"");
      },String.valueOf(i)).start();
    }
  }
}

这样既保证了数据一致性,有保证了并发性,读写分离。

Synchronized太重量。


三、CountDownLatch【线程做减法倒计时】


3.1 离开教室锁门问题产生!

public class CountDownLatchDemo{
  public static void main(String[] args){
    for(int i = 1;i<=6;i++){
      new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
      },String.valueOf(i)).start(); 
    }
    System.out,println(Thread.currentThread().getName()+"班长最后关门走人");
  }
}

2020072114222671.png


3.2 解决问题:CountDownLatch

public class CountDownLatchDemo{
  public static void main(String[] args){
    // 计数
    CountDownLatch countDownLatch = new CountDownLatch(6);
    for(int i = 1;i<=6;i++){
      new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"上完自习,离开教室");
        countDownLatch.countDown();//减1操作
      },String.valueOf(i)).start(); 
    }
    // 主线程等待
    countDownLatch.await();
    System.out,println(Thread.currentThread().getName()+"班长锁门,最后关门走人");
  }
}

20200721142611649.png


四、CyclicBarrier【加法】


加法 与CountDownLatch【减法】相反

加到一定的数值然后做事

最后一个线程到达屏障时候才会进行

public class CountDownLatchDemo{
  public static void main(String[] args){
    CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("***召唤神龙***");});
    for(int i = 1;i<=7;i++){
      final int tempInt  = i;
      new Thread(()->{
        System.out.println(Thread.currentThread().getName()+"收集到第"+tempInt +"颗龙珠");
        cyclicBarrier.await();
      },String.valueOf(i)).start();
    }
  }
}

20200721151044492.png


五、Semaphore【信号量】


多个共享资源的互斥使用

并发线程数量的控制

public class CountDownLatchDemo{
  public static void main(String[] args){
    // 模拟3个停车位
    Semaphore semaphore = new Semaphore(3);
    for(int i = 1; i <= 6;i++){
      new Thread(()->{
        try{
          semaphore.acquire();
          System.out.println(Thread.currentThread().getName()+"抢到车位");
          TimeUnit.SECONDS.sleep(3);
          System.out.println(Thread.currentThread().getName()+"停车3S后,离开车位");
        }catch(...){
        }finally{
          semaphore.release();
        } 
      },String.valueOf(i)).start();
    }
  }
}

20200721153122928.png

六、阻塞队列【MQ核心】

20200721154101133.png

20200721154829144.png

6.1 阻塞队列ArrayBlockingQueue<>()

20200721160542467.png

报异常

20200721163925468.png

没有异常,直接返回布尔类型false

20200721164245788.png

一直阻塞,取出用take方法


20200721165142895.png

过时不候


6.2 阻塞队列 SynchronousQueue<>()

20200721165636702.png

目录
相关文章
|
11天前
|
监控 安全 Java
在 Java 中使用线程池监控以及动态调整线程池时需要注意什么?
【10月更文挑战第22天】在进行线程池的监控和动态调整时,要综合考虑多方面的因素,谨慎操作,以确保线程池能够高效、稳定地运行,满足业务的需求。
88 38
|
9天前
|
Java
线程池内部机制:线程的保活与回收策略
【10月更文挑战第24天】 线程池是现代并发编程中管理线程资源的一种高效机制。它不仅能够复用线程,减少创建和销毁线程的开销,还能有效控制并发线程的数量,提高系统资源的利用率。本文将深入探讨线程池中线程的保活和回收机制,帮助你更好地理解和使用线程池。
33 2
|
11天前
|
Prometheus 监控 Cloud Native
JAVA线程池监控以及动态调整线程池
【10月更文挑战第22天】在 Java 中,线程池的监控和动态调整是非常重要的,它可以帮助我们更好地管理系统资源,提高应用的性能和稳定性。
42 4
|
11天前
|
Prometheus 监控 Cloud Native
在 Java 中,如何使用线程池监控以及动态调整线程池?
【10月更文挑战第22天】线程池的监控和动态调整是一项重要的任务,需要我们结合具体的应用场景和需求,选择合适的方法和策略,以确保线程池始终处于最优状态,提高系统的性能和稳定性。
68 2
|
29天前
|
Dubbo Java 应用服务中间件
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
剖析Tomcat线程池与JDK线程池的区别和联系!
|
29天前
|
存储 消息中间件 资源调度
C++ 多线程之初识多线程
这篇文章介绍了C++多线程的基本概念,包括进程和线程的定义、并发的实现方式,以及如何在C++中创建和管理线程,包括使用`std::thread`库、线程的join和detach方法,并通过示例代码展示了如何创建和使用多线程。
38 1
C++ 多线程之初识多线程
|
14天前
|
Java 开发者
在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口
【10月更文挑战第20天】在Java多线程编程中,创建线程的方法有两种:继承Thread类和实现Runnable接口。本文揭示了这两种方式的微妙差异和潜在陷阱,帮助你更好地理解和选择适合项目需求的线程创建方式。
13 3
|
14天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
12 2
|
14天前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
27 2
|
14天前
|
Java 开发者
Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点
【10月更文挑战第20天】Java多线程初学者指南:介绍通过继承Thread类与实现Runnable接口两种方式创建线程的方法及其优缺点,重点解析为何实现Runnable接口更具灵活性、资源共享及易于管理的优势。
26 1