2019 Java 底层面试题上半场(第一篇)

JUC多线程及高并发



请谈谈你对volatile的理解

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

    特点是 保证可见性 不保证原子性 禁止指令重排


什么是可见性?

    一个线程AAA修改了共享变量X的值但还未写回主内存时,另外一个线程BBB又对主内存中同一个共享变量X进行操作,但此时AAA线程工作内存中共享变量X对线程BBB来说并不可见,这种工作内存与主内存同步延迟现象就造成了可见性问题

可见性问题演示:

package com.example.demo;

import java.util.concurrent.TimeUnit;

class MyData {

    //请注意,此时number没加volatile关键字修饰的,不保证可见性

    int number = 0;

    public void addTO60() {

        this.number = 60;

    }

}

/**

* 可见性问题演示

*/

public class VolatileDemo {

    public static void main(String[] args) {

        MyData myData = new MyData();//资源类

        new Thread(() -> {

            System.out.println(Thread.currentThread().getName() + "\t come in");

            //暂停一会儿线程

            try {

                TimeUnit.SECONDS.sleep(3);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            myData.addTO60();

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

        }, "AAA").start();

        //第2个线程就是我们的main线程

        while (myData.number == 0) {

            //main线程就一直再这里等待循环,直到number值不再等于0

        }

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

    }

}

可见性问题解决:

volatile int number =0;


什么是原子性?

不可分割,也即某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。需要整体完整。

要么同时成功,要么同时失败

原子性问题演示:

package com.example.demo;

class MyData {

    //请注意,此时number前面是加了volatile关键字修饰的,volatile不保证原子性

    volatile int number = 0;

    public void addPlusPlus(){

        number++;

    }

}

/**

* 原子性问题演示

*/

public class VolatileDemo {

    public static void main(String[] args) {

        MyData myData = new MyData();//资源类

        for (int i = 1; i <= 20; i++) {

            new Thread(() ->{

                for (int j = 1; j <= 1000; j++) {

                    myData.addPlusPlus();

                }

            }, String.valueOf(i)).start();

        }

        //需要等待上面20个线程都全部计算完成后,再用main线程取得最终的结果值看是多少

        while (Thread.activeCount() > 2){

            Thread.yield();

        }

        System.out.println(Thread.currentThread().getName() + "\t int type, finally number value: "+myData.number);

    }

}

原子性问题解决:

加sync

加lock

使用JUC下Atomic


有序性(禁止指令)

计算机在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排,一般分以下3种

源代码—>编译器优化的重拍—>指令并行的重拍—>内存系统的重排—>最终执行的指令

处理器在进行重排序时必须要考虑指令之间的数据依赖性

多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程中使用的变量能否保证一致性时无法确定的,结果无法预测、

案例:

public void mySort(){

    int x =11;//语句1

    int y =q12;//语句2

    x = x +5;//语句3

    y = x * x;//语句4

}

执行顺序可能为以下任何一种:1234    2134    1324

请问语句4可以重排后变成第一条吗?

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


禁止指令重排小总结

volatile实现禁止指令重排优化,从而避免多线程环境下程序出现乱序执行的现象

原理是通过插入内存屏障禁止在内存屏障前后的指令执行重排序优化


你在哪些地方用到过volatile?

多线程下的单例模式DCL(双端检锁):

package com.example.demo;

public class SingletonDemo {

    private static volatile SingletonDemo instance = null;

    private SingletonDemo(){

        System.out.println(Thread.currentThread().getName() + "\t 我时构造方法SingletonDemo()");

    }

    //DCL(Doouble Check Lock 双端检锁机制)

    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 <= 10; i++) {

            new Thread(()->{

                SingletonDemo.getInstance();

            }, String.valueOf(i)).start();

        }

    }

}


number++在多线程下是非线程安全的,如何不加synchronized解决?

使用我们的JUC下AtomicInteger的getAndIncrement方法


CAS你知道吗?

CAS(CompareAndSwap)比较并交换

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

案例:

package com.example.demo;

import java.util.concurrent.atomic.AtomicInteger;

/**

* 比较并交换

*/

public class CASDemo {

    public static void main(String[] args) {

        AtomicInteger atomicInteger = new AtomicInteger(5);

        //main do ting......

        System.out.println(atomicInteger.compareAndSet(5, 2019) + "\t current data: " + atomicInteger.get());

        System.out.println(atomicInteger.compareAndSet(5, 1024) + "\t current data: " + atomicInteger.get());

    }

}


CAS底层原理?如果知道,谈谈你对UnSafe的理解?

CAS的操作依赖于Unsafe类的方法,Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存

Unsafe类中的所有方法都是native修饰的,也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务

CAS的全称为Compare And Swap,它是一条CPU并发原语。调用Unsafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。

由于CAS是一种系统原语,原语属于操作系统用语范围,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致性。

Unsafe类中CAS(CompareAndSwapInt),是一个本地方法,该方法的实现位于unsafe.cpp中


CAS缺点?

循环时间长开销很大(Unsafe类getAndAddInt方法有自旋锁,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,会给CPU带来很大的开销)

只能保证一个共享变量的原子操作(当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作 但是 对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性)

引出来ABA问题


原子类AtomicInteger的ABA问题谈谈?原子更新引用知道吗?

CAS会导致“ABA问题”

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化

比如说一个线程one从内存位置V中取出A,这时候另一个线程two也从内存中取出A,并且线程two进行了一些操作将值变成了B,然后线程two又将V位置的数据变成A,这时候线程one进行CAS操作发现内存中仍然是A,然后线程one操作成功

尽管线程one的CAS操作成功,但是不代表这个过程就是没有问题的,这个问题可以使用时间戳原子引用AtomicStampedReference类的getStamp()方法解决。

原子引用(AtomicReference)案例:

package com.example.demo;

import java.util.concurrent.atomic.AtomicReference;

class User{

    String userName;

    int age;

    public User(String userName, int age) {

        this.userName = userName;

        this.age = age;

    }

    @Override

    public String toString() {

        return "User{" +

                "userName='" + userName + '\'' +

                ", age=" + age +

                '}';

    }

}

public class AtomicReferenceDemo {

    public static void main(String[] args) {

        User z3 = new User("z3", 22);

        User li4 = new User("li4", 25);

        AtomicReference atomicReference = new AtomicReference<>();

        atomicReference.set(z3);

        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());

        System.out.println(atomicReference.compareAndSet(z3, li4) + "\t" + atomicReference.get().toString());

    }

}

时间戳原子引用(AtomicStampedReference)实现ABA问题和解决ABA问题案例:

package com.example.demo;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo { //ABA问题解决 AtomicStampedReference

    static AtomicReference atomicReference = new AtomicReference<>(100);

    static AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(100, 1);

    public static void main(String[] args) {

        System.out.println("======================================以下是ABA问题的产生=====================================");

        new Thread(()->{

            atomicReference.compareAndSet(100, 101);

            atomicReference.compareAndSet(101, 100);

        }, "t1").start();

        new Thread(()->{

            //暂停1秒钟t2线程,保证上面的t1线程完成了一次ABA操作

            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get());

        }, "t2").start();

        //暂停一会儿线程

        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println("======================================以下是ABA问题的解决=====================================");

        new Thread(()->{

            int stamp = atomicStampedReference.getStamp();

            System.out.println(Thread.currentThread().getName() + "\t 第1次版本号:" + stamp);

            //暂停1秒钟t3线程

            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println(Thread.currentThread().getName() + "\t 第2次版本号:" + atomicStampedReference.getStamp());

            atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);

            System.out.println(Thread.currentThread().getName() + "\t 第3次版本号:" + atomicStampedReference.getStamp());

        }, "t3").start();

        new Thread(()->{

            int stamp = atomicStampedReference.getStamp();

            System.out.println(Thread.currentThread().getName() + "\t 第1次版本号:" + stamp);

            //暂停3秒钟t4线程

            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }

            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);

            System.out.println(Thread.currentThread().getName() + "\t 修改成功否:" + result + "\t 当前最新实际版本号:" + atomicStampedReference.getStamp());

            System.out.println(Thread.currentThread().getName() + "\t 当前实际最新值:" + atomicStampedReference.getReference());

        }, "t4").start();

    }

}


我们知道ArrayList是线程不安全,请编码写一个不安全的案例并给出解决方案?

解决ArrayList线程不安全问题:

1.使用new Vector<>();

2.使用Collections.synchronizedList();

ArrayList线程不安全问题案例:

package com.example.demo;

import java.util.*;

/**

* 集合类不安全的问题

* ArrayList

*/

public class ContainerNotSafeDemo {

    public static void main(String[] args) {

        List list = new ArrayList<>();

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

        }

        /**

        * 1 故障现象

        * java.util.ConcurrentModificationException

        *

        * 2 导致原因

        * 并发争抢修改导致,参考我们的花名册签名情况。 

        * 一个人正在写入,另外一个同学过来抢夺,导致数据不一致异常,并发修改异常。

        *

        * 3 解决方案

        * new Vector<>();

        * Collections.synchronizedList(new ArrayList<>());

        */

    }

}

不用Vector和Collections工具类解决:

解决集合类不安全之List

new CopyOnWriteArrayList();

解决集合类不安全之Set

new CopyOnWriteArraySet();

解决集合类不安全之Map

new ConcurrentHashMap();


公平锁和非公平锁?

公平锁是指多个线程按照申请锁的顺序来获取锁,类似排队打饭,先来后到。

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

关于两者区别:

公平锁,在并发环境中,每个线程在获取锁时会先查看此锁维护的等待队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照FIFO的规则从队列中取到自己

非公平锁比较粗鲁,上来就直接尝试占有锁,如果尝试失败,就再采用类似公平锁那种方式

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


可重入锁?

可重入锁(又名递归锁)在同一个线程再外层方法获取锁的时候,再进入内层方法会自动获取锁

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

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

Synchronized可重入锁案例:

package com.example.demo;

class Phone{

    public synchronized void sendSMS() throws Exception{

        System.out.println(Thread.currentThread().getName() + "\t invoked sendSMS()");

        sendEmail();

    }

    public synchronized void sendEmail() throws Exception{

        System.out.println(Thread.currentThread().getName() + "\t invoked sendEmail()");

    }

}

public class ReentrantLockDemo {

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

    }

}

ReentrantLock可重入锁案例:

package com.example.demo;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

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

        }finally {

            lock.unlock();

        }

    }

    public void set() {

        lock.lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t invoked set()");

        }finally {

            lock.unlock();

        }

    }

}

public class ReentrantLockDemo {

    public static void main(String[] args) {

        Phone phone = new Phone();

        Thread t3 = new Thread(phone,"t3");

        Thread t4 = new Thread(phone,"t4");

        t3.start();

        t4.start();

    }

}


自旋锁?

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

自选锁案例:

package com.example.demo;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicReference;

public class SpinLockDemo {

    //原子引用线程,引用类型初始值null

    AtomicReference atomicReference = new AtomicReference<>();

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

        }, "AA").start();

        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

        new Thread(() -> {

            spinLockDemo.myLock();

            spinLockDemo.myUnlock();

        }, "BB").start();

    }

    public void myUnlock() {

        Thread thread = Thread.currentThread();

        atomicReference.compareAndSet(thread, null);

        System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");

    }

    public void myLock() {

        Thread thread = Thread.currentThread();

        System.out.println(Thread.currentThread().getName() + "\t come in O(∩_∩)O");

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

        }

    }

}


独占锁(写),共享锁(读),互斥锁?

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

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

互斥锁:在一个线程修改变量时加锁,则其他变量阻塞,等待加锁的变量解锁后再执行

主要使用JUC下的ReentrantReadWriteLock读写锁类的writeLock() readLock()方法实现

以上三个锁也可合称为读写锁

读写锁读写冲突问题案例:

package com.example.demo;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;

class MyCache {

    private volatile Map map = new HashMap();

    public void put(String key, Object value) {

        System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);

        //暂停一会儿线程 模拟网络拥堵

        try {

            TimeUnit.MILLISECONDS.sleep(300);

        } 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 正在读取:" + key);

        //暂停一会儿线程 模拟网络拥堵

        try {

            TimeUnit.MILLISECONDS.sleep(300);

        } catch (InterruptedException e) {

            e.printStackTrace();

        }

        Object result = map.get(key);

        System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);

    }

}

public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();

        //5个线程写

        for (int i = 1; i <= 5; i++) {

            final int tempInt = i;

            new Thread(() -> {

                myCache.put(tempInt + "", tempInt + "");

            }, String.valueOf(i)).start();

        }

        //5个线程读

        for (int i = 1; i <= 5; i++) {

            final int tempInt = i;

            new Thread(() -> {

                myCache.get(tempInt + "");

            }, String.valueOf(i)).start();

        }

    }

}

加入ReentrantReadWriteLock解决读写冲突问题:

package com.example.demo;

import java.util.HashMap;

import java.util.Map;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantReadWriteLock;

class MyCache {

    private volatile Map 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() + "\t 正在写入:" + key);

            //暂停一会儿线程 模拟网络拥堵

            try {

                TimeUnit.MILLISECONDS.sleep(300);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            map.put(key, value);

            System.out.println(Thread.currentThread().getName() + "\t 写入完成");

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            rwLock.writeLock().unlock();

        }

    }

    public void get(String key) {

        rwLock.readLock().lock();

        try {

            System.out.println(Thread.currentThread().getName() + "\t 正在读取:" + key);

            //暂停一会儿线程 模拟网络拥堵

            try {

                TimeUnit.MILLISECONDS.sleep(300);

            } catch (InterruptedException e) {

                e.printStackTrace();

            }

            Object result = map.get(key);

            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + result);

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            rwLock.readLock().unlock();

        }

    }

}

public class ReadWriteLockDemo {

    public static void main(String[] args) {

        MyCache myCache = new MyCache();

        //5个线程写

        for (int i = 1; i <= 5; i++) {

            final int tempInt = i;

            new Thread(() -> {

                myCache.put(tempInt + "", tempInt + "");

            }, String.valueOf(i)).start();

        }

        //5个线程读

        for (int i = 1; i <= 5; i++) {

            final int tempInt = i;

            new Thread(() -> {

                myCache.get(tempInt + "");

            }, String.valueOf(i)).start();

        }

    }

}


CountDownLatch?

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

CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。

其他线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)。

当计数器的值变为0时,因调用await方法被阻塞的线程会被唤醒,继续执行。

案例:

CountDownLatchDemo.java

package com.example.demo;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchDemo {

    public static void main(String[] args) throws Exception {

        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {

            new Thread(()->{

                System.out.println(Thread.currentThread().getName() + "\t 国,被灭");

                countDownLatch.countDown();

            }, CountryEnum.forEach_countryEnum(i).getRetMessage()).start();

        }

        countDownLatch.await();

        System.out.println(Thread.currentThread().getName() + "\t **********秦国一统华夏");

    }

}


CountryEnum.java

package com.example.demo;

public enum CountryEnum {

    ONE(1, "齐"),

    TWO(2, "楚"),

    THREE(3, "燕"),

    FOUR(4, "赵"),

    FIVE(5, "魏"),

    SIX(6, "韩");

    private Integer retCode;

    private String retMessage;

    public Integer getRetCode() {

        return retCode;

    }

    public String getRetMessage() {

        return retMessage;

    }

    CountryEnum(Integer retCode, String retMessage) {

        this.retCode = retCode;

        this.retMessage = retMessage;

    }

    public static CountryEnum forEach_countryEnum(int index) {

        CountryEnum[] myArray = CountryEnum.values();

        for (CountryEnum element : myArray) {

            if (index == element.getRetCode()) {

                return element;

            }

        }

        return null;

    }

}


CyclicBarrier?

CyclicBarrier的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,

让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,

屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。

案例:

package com.example.demo;

import java.util.concurrent.BrokenBarrierException;

import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierDemo {

    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() + "\t 收集到第:" + tempInt + "颗龙珠");

                try {

                    cyclicBarrier.await();

                } catch (InterruptedException e) {

                    e.printStackTrace();

                } catch (BrokenBarrierException e) {

                    e.printStackTrace();

                }

            }, String.valueOf(i)).start();

        }

    }

}


Semaphore?

主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

案例:

package com.example.demo;

import java.util.concurrent.Semaphore;

import java.util.concurrent.TimeUnit;

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 停车3秒后离开车位");

                } catch (InterruptedException e) {

                    e.printStackTrace();

                }finally {

                    semaphore.release();

                }

            }, String.valueOf(i)).start();

        }

    }

}


阻塞队列?

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

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

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

阻塞队列 (BlockingQueue)



为什么需要BlockingQueue?

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

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

BlockingQueue的核心方法?

抛出异常:

1)当阻塞队列满时,再往队列里add插入元素会抛出异常IIIegalStateException: Queue full

2)当阻塞队列空时,再往队列里remove移除元素会抛异常NoSuchElementException

3)当队列为空时, element会抛出一个异常

特殊值:

1)offer插入方法,成功true失败false

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

3)当队列为空时,peek返回null

一直阻塞:

1)当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出

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

超时退出:

1)当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出


BlockingQueue架构?

Collection

->

Queue

->

BlockingQueue

->

LinkedTransferQueue:由链表结构组成的无界阻塞队列。

LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

PriorityBlockingQueue:支持优先级排序的无界阻塞队列。

SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。

DelayQueue:使用优先级队列实现的延迟无界阻塞队列。

ArrayBlockingQueue:由数组结构组成的有界阻塞队列。

LinkedBlockingQueue:由链表结构组成的有界(但大小默认值位Integer.MAX_VALUE)阻塞队列


ArrayBlockingQueue / LinkedBlockingQueue使用?

BlockingQueue blockingQueue = new ArrayBlockingQueue();

BlockingQueue blockingQueue = new LinkedBlockingQueue();

然后参考 BlockingQueue的核心方法 来使用


SynchronousQueue?

与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue

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

案例:

package com.example.demo;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.SynchronousQueue;

import java.util.concurrent.TimeUnit;

public class SynchronousQueueDemo {

    public static void main(String[] args) {

        BlockingQueue blockingQueue = new SynchronousQueue();

        new Thread(()->{

            try {

                System.out.println(Thread.currentThread().getName()+"\t put 1");

                blockingQueue.put("1");

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

                blockingQueue.put("2");

                System.out.println(Thread.currentThread().getName()+"\t put 3");

                blockingQueue.put("3");

            }catch (Exception e){

                e.printStackTrace();

            }

        },"AAA").start();

        new Thread(()->{

            try {

                try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

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

                try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

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

                try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

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

            }catch (Exception e){

                e.printStackTrace();

            }

        },"BBB").start();

    }

}


JUC1.0和2.0?

synchronized被lock替代

wait被await替代

notify被sinqal替代


阻塞队列用在哪?

生产者消费者模式

线程池

消息中间件


传统版阻塞队列(生产者消费者)实现?

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

package com.example.demo;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class ShareData {

    private int number = 0;

    private Lock lock = new ReentrantLock();

    private Condition condition = lock.newCondition();

    public void increment() throws Exception {

        lock.lock();

        try {

            //1 判断

            while (number != 0) {

                //等待,不能生产

                condition.await();

            }

            //2 干活

            number++;

            System.out.println(Thread.currentThread().getName() + "\t" + number);

            //3 通知唤醒

            condition.signalAll();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

    public void decrement() throws Exception {

        lock.lock();

        try {

            //1 判断

            while (number == 0) {

                //等待,不能生产

                condition.await();

            }

            //2 干活

            number--;

            System.out.println(Thread.currentThread().getName() + "\t" + number);

            //3 通知唤醒

            condition.signalAll();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

}

/**

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

*/

public class ProdConsumer_TraditionDemo {

    public static void main(String[] args) {

        ShareData shareData = new ShareData();

        new Thread(() -> {

            for (int i = 1; i <= 5; i++) {

                try {

                    shareData.increment();

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }, "AA").start();

        new Thread(() -> {

            for (int i = 1; i <= 5; i++) {

                try {

                    shareData.decrement();

                } catch (Exception e) {

                    e.printStackTrace();

                }

            }

        }, "BB").start();

    }

}


JUC3.0版传统版阻塞队列(生产者消费者)实现?

package com.example.demo;

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

import java.util.concurrent.TimeUnit;

import java.util.concurrent.atomic.AtomicInteger;

class MyResource {

    private volatile boolean FLAG = true;  //默认开启,进行生产+消费

    private AtomicInteger atomicInteger = new AtomicInteger();

    BlockingQueue blockingQueue = null;

    public MyResource(BlockingQueue blockingQueue) {

        this.blockingQueue = blockingQueue;

        System.out.println(blockingQueue.getClass().getName());

    }

    public void MyProd() throws Exception {

        String data = null;

        boolean retValue;

        while (FLAG) {

            data = atomicInteger.incrementAndGet() + "";

            retValue = blockingQueue.offer(data, 2L, 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 MyConsumer() throws Exception {

        String result = null;

        while (FLAG) {

            result = blockingQueue.poll(2L, TimeUnit.SECONDS);

            if (null == result || result.equalsIgnoreCase("")) {

                FLAG = false;

                System.out.println(Thread.currentThread().getName()+"\t 超过2秒钟没有取到蛋糕,消费退出");

                System.out.println();

                System.out.println();

                System.out.println();

                return;

            }

            System.out.println(Thread.currentThread().getName()+"\t 消费队列蛋糕"+result+"成功");

        }

    }

    public void stop() throws Exception{

        this.FLAG = false;

    }

}

public class ProdConsumer_BlockQueueDemo {

    public static void main(String[] args) throws Exception {

        MyResource myResource = new MyResource(new ArrayBlockingQueue(10));

        new Thread(()->{

            System.out.println(Thread.currentThread().getName()+"\t 生产线程启动");

            try {

                myResource.MyProd();

            } catch (Exception e) {

                e.printStackTrace();

            }

        },"Prod").start();

        new Thread(()->{

            System.out.println(Thread.currentThread().getName()+"\t 消费线程启动");

            try {

                myResource.MyConsumer();

            } catch (Exception e) {

                e.printStackTrace();

            }

        },"Consumer").start();

        try {TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); }

        System.out.println();

        System.out.println();

        System.out.println();

        System.out.println("5秒钟时间到,大老板main线程叫停,活动结束");

        myResource.stop();

    }

}


Synchronized和Lock有什么区别?用新的Lock有什么好处?举例说明

1)synchronized是关键字。Lock是具体类。

2)synchronized不需要用户手动去释放锁。 ReentrantLock需要用户去手动释放锁 lock() 和 unlock() 方法。

3)synchronized执行过程中不可中断,除非抛出异常或运行完成。 ReentrantLock可中断 方法一:设置超时方法 tryLock() 方法二:lockInterruptibly() 放到代码块中调用 interrupt() 方法可中断

4)synchronized是非公平锁。ReentrantLock默认非公平锁 ReentrantLock(true)后可以改为公平锁

5)synchronized没有Condition。 ReentrantLock可以使用Condition来实现精确唤醒线程,而不像synchronized要么随机唤醒一个线程要么唤醒全部线程

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

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

紧接着

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

......

来10轮

package com.example.demo;

import java.util.concurrent.locks.Condition;

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

class ShareResource {

    private int number = 1; //A:1 B:2 C:3

    private Lock lock = new ReentrantLock();

    private Condition c1 = lock.newCondition();

    private Condition c2 = lock.newCondition();

    private Condition c3 = lock.newCondition();

    public void print5() {

        lock.lock();

        try {

            //1 判断

            while (number != 1) {

                c1.await();

            }

            //2 干活

            for (int i = 1; i <= 5; i++) {

                System.out.println(Thread.currentThread().getName() + "\t" + i);

            }

            //3 通知

            number = 2;

            c2.signal();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

    public void print10() {

        lock.lock();

        try {

            //1 判断

            while (number != 2) {

                c2.await();

            }

            //2 干活

            for (int i = 1; i <= 10; i++) {

                System.out.println(Thread.currentThread().getName() + "\t" + i);

            }

            //3 通知

            number = 3;

            c3.signal();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

    public void print15() {

        lock.lock();

        try {

            //1 判断

            while (number != 3) {

                c3.await();

            }

            //2 干活

            for (int i = 1; i <= 15; i++) {

                System.out.println(Thread.currentThread().getName() + "\t" + i);

            }

            //3 通知

            number = 1;

            c1.signal();

        } catch (Exception e) {

            e.printStackTrace();

        } finally {

            lock.unlock();

        }

    }

}

/**

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

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

* 紧接着

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

* ......

* 来10轮

*/

public class SyncAndReentrantLockDemo {

    public static void main(String[] args) {

        ShareResource shareResource = new ShareResource();

        new Thread(() -> {

            for (int i = 1; i <= 10; i++) {

                shareResource.print5();

            }

        }, "AA").start();

        new Thread(() -> {

            for (int i = 1; i <= 10; i++) {

                shareResource.print10();

            }

        }, "BB").start();

        new Thread(() -> {

            for (int i = 1; i <= 10; i++) {

                shareResource.print15();

            }

        }, "CC").start();

    }

}


链表结构与数组结构?

不同:链表是链式的存储结构;数组是顺序的存储结构。链表通过指针来连接元素与元素,数组则是把所有元素按次序依次存储。链表的插入删除元素相对数组较为简单,不需要移动元素,且较为容易实现长度扩充,但是寻找某个元素较为困难;数组寻找某个元素较为简单,但插入与删除比较复杂,由于最大长度需要再编程一开始时指定,故当达到最大长度时,扩充长度不如链表方便。


2019 Java 底层面试题(第二篇) -

你可能感兴趣的:(2019 Java 底层面试题上半场(第一篇))