大厂面试题第二季(阳哥)--上

1、Volatile

volatile 是 JVM 提供的一种轻量级的同步机制

三大特性:

  • 保证可见性
  • 不保证原子性
  • 禁止指令重排

代码实践:

package com.yanghui;

import java.util.concurrent.TimeUnit;

/**
 * @ClassName VolatileDemo
 * @Author YH
 * @Date 2021/9/25
 * @Version 1.0
 */
public class VolatileDemo {
    
    

    /**
     * 1 验证 volatile 的可见性
     *  1.1 没有 volatile 关键字修饰    int num = 0;
     *  1.2 增加 volatile 关键字修饰    volatile int num = 0;
     * 2 验证 volatile 不保证原子性
     *  2.1 原子性是什么意思?
     *      不可分割,完整性,也即某个线程正在做某个具体业务时,
     *      中间不可以被加塞或者被分割,需要整体完整,要么同时成功,要么同时失败。
     *  2.2 volatile 不保证原子性的案例
     *  2.3 why?
     *
     */
    public static void main(String[] args) {
    
    
        MyData myData = new MyData();

        for (int i = 0; i < 20; i++) {
    
    
            new Thread(() -> {
    
    
                for (int j = 0; j < 1000; j++) {
    
    
                    myData.addPlusPlus();
                }
            }, String.valueOf(i)).start();
        }

        /**
         * 需要等待 20 个线程全部计算完成,再取得最后结果
         * Thread.activeCount() 得到当前有多少个线程在执行
         * 默认有两个线程在后台执行:main 线程和 GC 线程
         * Thread.yield()   线程让步,把自己的执行时间让给其他线程
         */
        while (Thread.activeCount() > 2) {
    
    
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + "\t finally number value:" + myData.num);
    }

    /**
     * volatile 保证内存的可见性,及时通知其他线程,主物理内存的值已经被修改
     */
    public static void seeOkByVolatile() {
    
    
        MyData myData = new MyData();

        // 线程操作资源类
        new Thread(() -> {
    
    
            System.out.println(Thread.currentThread().getName() + "\t" + "come in");
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            // 进行操作
            myData.addTo60();

            System.out.println(Thread.currentThread().getName() + "update value:" + myData.num);

        }, "AAA").start();

        // 一直循环,如果停止循环,说明 num 值已经改变了,并且被 main 线程感知到了
        while (myData.num == 0) {
    
    
            // main 线程一直在这里等待
        }
        // 这里输出 main 线程的名字
        System.out.println(Thread.currentThread().getName() + "\t" + "get value:" + myData.num);
    }

}

/**
 * 资源类
 */
class MyData {
    
    
    volatile int num = 0;

    public void addTo60() {
    
    
        this.num = 60;
    }

    public void addPlusPlus() {
    
    
        this.num++;
    }
}


2、JMM

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

JMM 三大特性:

  • 可见性
  • 原子性
  • 有序性

JMM 关于同步的规定:

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

由于 JVM 运行程序的实体是线程,而每个线程创建时 JVM 都会为其创建一个工作内存(有些地方称之为栈空间),工作内存是每个线程的私有数据区域,而 Java 内存模型中规定所有变量都存储在主内存中,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作内存空间,然后对变量进行操作,操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存完成,其简要访问过程如下图:

在这里插入图片描述


在这里插入图片描述



3、单例模式

/**
 * DCL 单例模式
 * @ClassName SingletonDemo
 * @Author YH
 * @Date 2021/9/26
 * @Version 1.0
 */
public class SingletonDemo {
    
    
	/**
     * volatile 禁止指令重排
     */
    private volatile static SingletonDemo singletonDemo = null;

    /**
     * 构造方法私有
     */
    private SingletonDemo() {
    
    
        System.out.println("构造了一个对象");
    }

    /**
     * 可以直接给方法加 synchronized
     * 重量级,并发性能下降
     * 所以使用 DCL(Double Check Lock,双重校验锁)
     * @return 实例对象
     */
    public static SingletonDemo getInstance() {
    
    

            // 双重校验锁
            if (singletonDemo == null) {
    
    
                synchronized (SingletonDemo.class) {
    
    
                    if (singletonDemo == null) {
    
    
                        /**
                         * 对象初始化过程:
                         * 1 分配对象内存空间
                         * 2 初始化对象
                         * 3 执行刚刚分配的内存空间
                         * 因为 步骤 2 和 步骤 3 没有数据依赖
                         * 有可能会出现指令重排
                         * 2 和 3 步骤对调,会导致对象还没有初始化成功
                         * 取出来的时候对象为 null
                         */
                        singletonDemo = new SingletonDemo();
                    }
                }
            }

            return singletonDemo;
    }
}


4、CAS

CAS 是什么

/**
 * 1 CAS 是什么
 *  (compareAndSet)比较并交换
 *
 */

在这里插入图片描述


在这里插入图片描述

CAS 底层原理


在这里插入图片描述


在这里插入图片描述


在这里插入图片描述


CAS 缺点

  • 循环时间长开销很大
  • 只能保证一个共享变量的原子操作
  • 引出来 ABA 问题


5、ABA 问题


在这里插入图片描述


代码演示:

public class ABADemo {
    
    
    static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);

    /**
     * 演示 ABA 问题
     * @param args
     */
    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            System.out.println("初始值为:" + atomicReference.get());
            atomicReference.compareAndSet(100, 101);
            System.out.println(Thread.currentThread().getName() + "\t第一次修改了值为:" + atomicReference.get());
            atomicReference.compareAndSet(101, 100);
            System.out.println(Thread.currentThread().getName() + "\t第二次修改了值为:" + atomicReference.get());
        }, "t1").start();

        new Thread(() -> {
    
    
           // 暂停 1 秒钟,确保 t1 执行成功
           try {
    
    
               TimeUnit.SECONDS.sleep(1);
               atomicReference.compareAndSet(100, 2021);
               System.out.println(Thread.currentThread().getName() + "\t" + atomicReference.get());
           } catch (Exception e) {
    
    
               e.printStackTrace();
           }
        }, "t2").start();
    }
}


6、原子引用

public class AtomicReferenceDemo {
    
    

    public static void main(String[] args) {
    
    
        User zs = new User("zhangsan", 14);
        User ls = new User("lisi", 15);

        /**
         * 原子引用
         * 既然有原子类 AtomicInteger,那么自定义的对象也需要有
         */
        AtomicReference<User> atomicReference = new AtomicReference<>();
        atomicReference.set(zs);
        System.out.println(atomicReference.compareAndSet(zs, ls));
    }
}

@Data
@AllArgsConstructor
@NoArgsConstructor
class User {
    
    
    String username;
    int age;
}


7、ABA 问题的解决

版本号原子引用

增加版本号,类似于乐观锁,不仅比较值,还比较版本号,都能对上才能进行修改

/**
 * 时间戳原子引用
 */
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);


8、集合不安全之 ArrayList

代码演示:

public class ContainerNotSafeDemo {
    
    

    /**
     * 集合不安全
     */
    public static void main(String[] args) {
    
    
        List<String> list = new ArrayList<>();

        for (int i = 0; i < 10; i++) {
    
    
            new Thread(() -> {
    
    
               list.add(UUID.randomUUID().toString().substring(0, 6));

                System.out.println(list);
            }, String.valueOf(i)).start();
        }

        /**
         * 方法论!!!
         * 1 故障现象
         *      会抛异常:java.util.ConcurrentModificationException
         *      并发修改异常
         *
         * 2 导致原因
         *      ArrayList 是线程不安全的,多个线程同时操作会出异常
         *      并发修改出现异常
         *
         * 3 解决方案
         *      (1)使用 CopyOnWriteArrayList 代替 ArrayList(推荐,主流使用)
         *      (2)Collections.synchronizedList(list) 进行转换
         *      (3)使用 Vector 代替 ArrayList,线程安全的,查看源码即可发现加了 synchronized 关键字
         *
         * 4 优化建议(同样的错误不放第二次)
         */
    }
}


在这里插入图片描述


CopyOnWriteArrayList 集合的 add() 方法源码:

/** 
 * 写时复制,读写分离的思想
 * 思想就是先拷贝出来,然后再进行修改,最后再替换回去
 * 这样的好处就是写的时候进行加锁,但不影响读
 */
public boolean add(E e) {
    
    
    final ReentrantLock lock = this.lock;
    // 加锁
    lock.lock();
    try {
    
    
        // 拷贝当前集合数组
        Object[] elements = getArray();
        // 获取当前集合数组长度,以便设置新集合数组容量
        int len = elements.length;
        // 开辟集合数组空间,同时将原集合数组复制进去
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        // 再把要设置的值赋值到新集合数组中
        newElements[len] = e;
        // 最后再把新集合数组设置到集合中
        setArray(newElements);
        return true;
    } finally {
    
    
        // 释放锁
        lock.unlock();
    }
}


9、集合不安全之 Set

HashSet 是线程不安全的

解决方法:
	(1Collections.synchronizedSet();
	(2)使用 CopyOnWriteArraySet 进行替代(推荐使用) 

HashSet 的底层实现就是 HashMap,源码的构造方法如下:
    public HashSet() {
    
    
        map = new HashMap<>();
    }

思考:既然是 HashSet 的底层是 HashMap,那么我们使用 add() 方法的时候为啥只填入了一个参数?
    查看源码即可发现:
    
    public boolean add(E e) {
    
    
        return map.put(e, PRESENT) == null;
    }

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

分析:使用 HashSet 保存元素的时候调用的就是 map 的 put() 方法,填入 map 的 key 中
     而 value 则是一个 Object 类型的常量,这就是 HashSet 没有重复元素的原因,因为 key 不能重复!

在这里插入图片描述



10、集合不安全之 HashMap

HashMap 是线程不安全的
    
解决方法:
    (1)使用 Hashtable 替代 HashMap2Collections.synchronizedMap()3)使用 ConcurrentHashMap 代替 HashMap(推荐使用)


11、Java 锁之公平锁和非公平锁

// new 一个锁
Lock lock = new ReentrantLock();

// 查看构造方法源码(根据英文单词可知是 非公平锁)
    /**
     * 无参构造方法
     * Creates an instance of {@code ReentrantLock}.
     * This is equivalent to using {@code ReentrantLock(false)}.
     */
    public ReentrantLock() {
    
    
        sync = new NonfairSync();
    }

    /**
     * 带参数的构造方法
     * 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();
    }

公平锁和非公平锁

  • 公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队,先来后到。
  • 非公平锁:是指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请锁的线程比先申请锁的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

在这里插入图片描述


  • 对于 Java ReentrantLock 而言,可以通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

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



12、Java 锁之可重入锁和递归锁

可重入锁又名递归锁

(1)指的是同一个线程外层函数获得锁之后,内层递归函数仍然能够获取该锁的代码。

(2)在同一个线程在外层方法获取锁的时候,在进入内层方法能够自动获取锁。

(3)也即是说,线程可以进入任何一个它以及拥有的锁所同步着的代码块

public synchronized void method01() {
    
    
    method02();
}

public synchronized void method02() {
    
    

}

ReentrantLock / Synchronized 就是一个典型的可重入锁。

可重入锁最大的作用就是避免死锁。


证明 Synchronized 是可重入锁代码


public class SynchronizedDemo {
    
    

    /**
     * 执行结果:
     * 20	 invoke sentSMS()
     * 20	 invoke sendEmail()
     * 21	 invoke sentSMS()
     * 21	 invoke sendEmail()
     */
    public static void main(String[] args) {
    
    
        Phone phone = new Phone();

        new Thread(() -> {
    
    
            try {
    
    
                phone.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, "t1").start();

        new Thread(() -> {
    
    
            try {
    
    
                phone.sendSMS();
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }, "t2").start();
    }
}


class Phone {
    
    

    public synchronized void sendSMS() throws Exception {
    
    
        System.out.println(Thread.currentThread().getId() + "\t invoke sentSMS()");
        // 调用邮件方法
        sendEmail();
    }

    public synchronized void sendEmail() throws Exception {
    
    
        System.out.println(Thread.currentThread().getId() + "\t invoke sendEmail()");
    }
}

证明 ReentrantLock 是可重入锁

public class ReentrantLockDemo {
    
    

    /**
     * 执行结果为:
     * 20	 invoke get()
     * 20	 invoke set()
     * 21	 invoke get()
     * 21	 invoke set()
     */
    public static void main(String[] args) {
    
    
        Phone1 phone1 = new Phone1();
        
        new Thread(phone1).start();
        new Thread(phone1).start();
    }
}


class Phone1 implements Runnable {
    
    
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
    
    
        get();
    }

    private void get() {
    
    
        lock.lock();
        try {
    
    
            System.out.println(Thread.currentThread().getId() +  "\t invoke get()");
            set();
        } finally {
    
    
            lock.unlock();
        }
    }

    private void set() {
    
    
        lock.lock();
        try {
    
    
            System.out.println(Thread.currentThread().getId() +  "\t invoke set()");
        } finally {
    
    
            lock.unlock();
        }
    }
}

在这里插入图片描述



13、Java 锁之自旋锁


在这里插入图片描述


实现自旋锁

/**
 * 实现一个自旋锁
 * 自旋锁好处:循环比较获取直到成功为止,没有类似 wait 的阻塞
 * 思想:核心就是利用 CAS
 * 执行结果:
 * 20	 invoke get()
 * 20	 invoke set()
 * 21	 invoke get()
 * 21	 invoke set()
 */
public class SpinLockDemo {
    
    

    /**
     * 原子引用线程
     */
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public static void main(String[] args) {
    
    
        SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
    
    
            spinLockDemo.myLock();
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        }, "t1").start();

        new Thread(() -> {
    
    
            spinLockDemo.myLock();
            try {
    
    
                TimeUnit.SECONDS.sleep(5);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            spinLockDemo.myUnlock();
        }, "t2").start();
    }

    /**
     * 加锁
     */
    public void myLock() {
    
    
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\t invoke myLock");

        while (!atomicReference.compareAndSet(null, thread)) {
    
    

        }
    }

    /**
     * 释放锁
     */
    public void myUnlock() {
    
    
        Thread thread = Thread.currentThread();
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\t invoke myUnlock");
    }
}


14、Java 锁之读写锁

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

共享锁:指该锁可以被多个线程所持有。对 ReentrantReadWriteLock 其读锁是共享锁,其写锁是独占锁

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


代码验证:

/**
 * 多个线程同时读一个资源类没有任何问题,所以为了满足并发量,
 * 读取共享资源应该可以同时进行。但是如果有一个线程想去写共享资源,
 * 就不应该再有其他线程可以对该资源进行读或写。
 * 小总结:
 *      读 - 读 能共存
 *      读 - 写 不能共存
 *      写 - 写 不能共存
 *
 *      写操作:原子 + 独占(整个过程必须是一个完整的整体,不许被分割和打断)
 */
public class ReadWriteLockDemo {
    
    

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

        // 5 个线程写
        for (int i = 0; i < 5; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                myCache.put(temp + "", temp + "");
            }, String.valueOf(i)).start();
        }

        // 5 个线程读
        for (int i = 0; i < 5; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                myCache.get(temp + "");
            }, String.valueOf(i)).start();
        }
    }
}

/**
 * 缓存资源类
 */
class MyCache {
    
    
    private volatile Map<String, Object> map = new HashMap<>();
    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    /**
     * 写方法
     */
    public void put(String key, Object value) {
    
    
        lock.writeLock().lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        } finally {
    
    
            lock.writeLock().unlock();
        }
    }

    /**
     * 读方法
     */
    public Object get(String key) {
    
    
        lock.readLock().lock();
        try {
    
    
            System.out.println(Thread.currentThread().getName() + "\t 正在读取");
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);

            return value;
        } finally {
    
    
            lock.readLock().unlock();
        }
    }

    /**
     * 清除缓存
     */
    public void clear() {
    
    
        map.clear();
    }
}

运行结果为:

/**
 * 0	 正在写入:0
 * 0	 写入完成
 * 1	 正在写入:1
 * 1	 写入完成
 * 2	 正在写入:2
 * 2	 写入完成
 * 4	 正在写入:4
 * 4	 写入完成
 * 3	 正在写入:3
 * 3	 写入完成
 * 0	 正在读取
 * 1	 正在读取
 * 2	 正在读取
 * 3	 正在读取
 * 4	 正在读取
 * 0	 读取完成:0
 * 4	 读取完成:4
 * 2	 读取完成:2
 * 1	 读取完成:1
 * 3	 读取完成:3
 */
// 分析:写没有被打断,读可以被打断


15、CountDownLatch

理论

  • 让一些线程阻塞直到另一些线程完成一系列操作之后才被唤醒。
  • CountDownLath 主要有两个方法
    • countDown():计数器减一。
    • await():当一个或者多个线程调用这个方法时会被阻塞,当计数器变为 0 的时候,因为调用 await() 方法的线程会停止阻塞,继续执行。

实操

Case 1 代码实现:

public class CountDownLatchDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 0; i < 6; i++) {
    
    
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室" );
                // 计数器减去 1
                countDownLatch.;
            }, String.valueOf(i)).start();
        }

        // 阻塞,当为 0 时放行
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t 锁上教室门");
    }
}

Case 2 代码实现:

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 国,被灭" );
                // 计数器减去 1
                countDownLatch.countDown();
            }, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
        }

        // 阻塞,当为 0 时放行
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t 秦帝国,一统华夏");
    }
}
@Getter
@AllArgsConstructor
@NoArgsConstructor
public enum CountryEnum {
    
    
    ONE(1, "齐"),
    TWO(2, "楚"),
    THREE(3, "燕"),
    FOUR(4, "赵"),
    FIVE(5, "魏"),
    SIX(6, "韩");

    private Integer retCode;
    private String retMessage;

    /**
     * 根据索引找到对应的数据
     */
    public static CountryEnum forEach_CountryEnum(int index) {
    
    
        // 获取所有枚举
        CountryEnum[] values = CountryEnum.values();

        for (CountryEnum value : values) {
    
    
            if (value.getRetCode() == index) {
    
    
                return value;
            }
        }

        return null;
    }
}


16、CyclicBarrier

理论

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


代码实现

public class CyclicBarrierDemo {
    
    

    public static void main(String[] args) {
    
    
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
    
    
            System.out.println("召唤神龙");
        });

        for (int i = 0; i < 7; i++) {
    
    
            final int temp = i;
            new Thread(() -> {
    
    
                System.out.println(Thread.currentThread().getName() + "\t 收集到第" + temp + "颗龙珠");
                try {
    
    
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
    
    
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}


17、Semaphore

理论

Semaphore 也被称为信号灯或者信号量,主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制

适用于高并发。


代码实现:

public class SemaphoreDemo {
    
    

    public static void main(String[] args) {
    
    
        // 模拟 3 个停车位
        Semaphore semaphore = new Semaphore(3);

        // 模拟 6 部汽车
        for (int i = 0; i < 6; i++) {
    
    
            new Thread(() -> {
    
    
                try {
    
    
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t 抢到停车位");

                    TimeUnit.SECONDS.sleep(3);

                    semaphore.release();
                    System.out.println(Thread.currentThread().getName() + "\t 离开停车位");
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}


18、阻塞队列

理论

阻塞队列:
     (1)阻塞队列有没有好的一面
     (2)不得不阻塞,你如何管理

在这里插入图片描述


  • 当阻塞队列是时,从队列中获取元素的操作将会被阻塞。

  • 当阻塞队列是时,从队列中添加元素的操作将会被阻塞。

  • 试图从空的阻塞队列中获取元素的操作将会被阻塞,直到其他的线程往空的队列插入新的元素。

  • 试图往已满的阻塞队列中添加新元素的操作也将会被阻塞,直到其他的线程从队列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增。


为什么用?有什么好处

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

为什么需要 BlockingQueue ?

​ 好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手包办了。

​ 在 java.util.concurrent 包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其是还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。


/**
 * ArrayBlockingQueue:是一个基于数组结构的的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
 * LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按 FIFO(先进先出)排序元素,
 *      吞吐量通常要高于 ArrayBlockingQueue。
 * SynchronousQueue:是一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,
 *      否则插入操作一直处于阻塞状态,吞吐量通常要高于 LinkedBlockingQueue。
 */

在这里插入图片描述


核心方法


在这里插入图片描述


在这里插入图片描述


// 第一组 API
public class BlockingQueueDemo {
    
    

    public static void main(String[] args) {
    
    
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.add("a"));
        System.out.println(blockingQueue.add("b"));
        System.out.println(blockingQueue.add("c"));
//        System.out.println(blockingQueue.add("d"));

        // 输出队首元素
        System.out.println(blockingQueue.element());

        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
        System.out.println(blockingQueue.remove());
//        System.out.println(blockingQueue.remove());


    }
}
// 第二组 API
public class BlockingQueueDemo {
    
    

    public static void main(String[] args) {
    
    
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("a"));
        System.out.println(blockingQueue.offer("a"));
//        System.out.println(blockingQueue.offer("x"));


        System.out.println(blockingQueue.peek());


        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
        System.out.println(blockingQueue.poll());
//        System.out.println(blockingQueue.poll());


    }
}
// 第三组 API
public class BlockingQueueDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        blockingQueue.put("a");
        blockingQueue.put("a");
        blockingQueue.put("a");
//        blockingQueue.put("a");



        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
        System.out.println(blockingQueue.take());
//        System.out.println(blockingQueue.take());

    }
}
// 第四组 API
public class BlockingQueueDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));


        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
        System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));

    }
}

SynchronousQueue

理论

Synchronous 没有容量,与其他 BlockingQueue 不同,SynchronousQueue 是一个不存储元素的 BlockingQueue。

每一个 put 操作必须要等待一个 take 操作,否则不能继续添加元素,反之亦然。

代码验证

public class BlockingQueueDemo {
    
    

    public static void main(String[] args) throws InterruptedException {
    
    
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();

        new Thread(() -> {
    
    
            try {
    
    
                System.out.println(Thread.currentThread().getName() + "\t1");
                blockingQueue.put("1");

                System.out.println(Thread.currentThread().getName() + "\t2");
                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName() + "\t3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }, "A").start();

        new Thread(() -> {
    
    
            try {
    
    
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }, "B").start();

    }
}


猜你喜欢

转载自blog.csdn.net/m0_52462015/article/details/120589041