其他分享
首页 > 其他分享> > 大厂面试题第二季(阳哥)--上

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

作者:互联网

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 缺点



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 是线程不安全的

解决方法:
	(1)Collections.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 替代 HashMap
    (2)Collections.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();
    }

公平锁和非公平锁


在这里插入图片描述




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

理论


实操

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

    }
}


标签:面试题,Thread,--,System,线程,println,public,阳哥,out
来源: https://blog.csdn.net/m0_52462015/article/details/120589041