栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

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

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

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

1、Volatile

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

三大特性:

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

代码实践:

package com.yanghui;

import java.util.concurrent.TimeUnit;


public class VolatileDemo {

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

        
        while (Thread.activeCount() > 2) {
            Thread.yield();
        }

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

    
    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、单例模式
public class SingletonDemo {
	
    private volatile static SingletonDemo singletonDemo = null;

    
    private SingletonDemo() {
        System.out.println("构造了一个对象");
    }

    
    public static SingletonDemo getInstance() {

            // 双重校验锁
            if (singletonDemo == null) {
                synchronized (SingletonDemo.class) {
                    if (singletonDemo == null) {
                        
                        singletonDemo = new SingletonDemo();
                    }
                }
            }

            return singletonDemo;
    }
}


4、CAS

CAS 是什么

 


CAS 底层原理





CAS 缺点

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


5、ABA 问题


代码演示:

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

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

        
        AtomicReference 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 atomicStampedReference = new AtomicStampedReference<>(100, 1);


8、集合不安全之 ArrayList

代码演示:

public class ContainerNotSafeDemo {

    
    public static void main(String[] args) {
        List 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();
        }

        
    }
}



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

// 查看构造方法源码(根据英文单词可知是 非公平锁)
    
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    
    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 {

    
    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 {

    
    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 锁之自旋锁


实现自旋锁:

public class SpinLockDemo {

    
    AtomicReference 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 锁之读写锁

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

共享锁:指该锁可以被多个线程所持有。对 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 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();
    }
}

运行结果为:

// 分析:写没有被打断,读可以被打断


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


 


核心方法:




// 第一组 API
public class BlockingQueueDemo {

    public static void main(String[] args) {
        BlockingQueue 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 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 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 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 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();

    }
}


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/287037.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号