想进大厂,单例模式和阻塞队列你了解吗?

单例模式
概念
单例模式是校招中最长考的设计模式之一,所以我们先介绍什么是设计模式。

设计模式:软件开发中有许多常见的“问题场景”,比如各种各样的工厂它们的运营模式都是类似,针对这些问题场景,大佬们总结出了一些固定的套路,并且用代码实现了基本模板。

单例模式:能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。例如JDBC中的DataSource。

单例模式的具体实现方式,分为"饿汉"和"懒汉"两种

饿汉模式
何为饿汉模式?

顾名思义饥不择食,慌不择路,这里也是同义在类加载的同时,就开始创建实例。

代码实现

class Singleton {
 
//此处定义类变量实例并直接实例化,在类加载的时候就完成了实例化并保存在类中
 private static Singleton instance = new Singleton(); 
 
//定义无参构造器,用于单例实例
 private Singleton() {}
 
 //定义公开方法,返回已创建的单例
 public static Singleton getInstance() { 
    return instance; 
    } 
}
 


懒汉模式
何又为懒汉模式?

顾名思义,不做事,推一步走一步,这里也是同义,类加载的时候不创建实例,第一次使用的时候才创建实例。

代码实现(单线程)

class Singleton { 
     //定义一个私有类变量来存放单例,私有的目的是指外部无法直接获取这个变量,而要使用提供的公共方法来获取
      private static Singleton instance = null; 
     //定义私有构造器,表示旨在类内部使用,亦指大巴黎的实例只能在但单例类内部创建
      private Singleton() {} 
     //定义一个公共的公开方法来返回该类的实例。
      public static Singleton getInstance() { 
         if (instance == null) { 
             instance = new Singleton(); 
           }
           return instance; 
     } 
}
但是上面懒汉模式的实现是线程不安全的

线程安全问题发生在首次创建实例时,如果多个线程同时调用getlnstance方法,就可能会导致创建出多个实例
一旦实例已经创建好了,后面在多线程环境调用getlnstance就不会再有线程安全问题了(instance不会再修改了)
加上synchronized可以改善这里的线程安全问题

代码示例(多线程)

class Singleton { 
      private static Singleton instance = null; 
      private Singleton() {} 
      //通过加锁来保证原子性,确保创建实例的线程一定是第一个加锁的线程。
      public synchronized static Singleton getInstance() { 
        if (instance == null) { 
             instance = new Singleton(); 
      }
        return instance; 
   } 
}
以下代码在加锁的基础上, 做出了进一步改动:
使用双重 if 判定, 降低锁竞争的频率.
给 instance 加上了 volatile.
代码实例(多线程改进)

class Singleton { 
     private static volatile Singleton instance = null; 
     private Singleton() {} 
     public static Singleton getInstance() { 
         if (instance == null) { 
              synchronized (Singleton.class) { 
                 if (instance == null) { 
                     instance = new Singleton(); 
                 } 
             } 
         }
         return instance; 
    } 
}
如何理解双双重if判定/volatile?

加锁/解锁是一件开销比较高的事情,懒汉模式只需要在首次创建实例的时候,后续使用就不必要在进行加锁了
外层的if可以判定当前instance实例是否已经被创建出来了
补充volatile保证“内存可见性”,即通知其它线程instance实例已经被创建了。
阻塞队列
概念
阻塞队列 (BlockingQueue)是Java util.concurrent包下重要的数据结构,BlockingQueue提供了线程安全的队列访问方式:当阻塞队列进行插入数据时,如果队列已满,线程将会阻塞等待直到队列非满;从阻塞队列取数据时,如果队列已空,线程将会阻塞等待直到队列非空。并发包下很多高级同步类的实现都是基于BlockingQueue实现的。
 

BlockingQueue 的操作方法
BlockingQueue 具有 4 组不同的方法用于插入、移除以及对队列中的元素进行检查。如果请求的操作不能得到立即执行的话,每个方法的表现也不同。这些方法如下:



生产者消费者模型
概念

所谓的生产者消费者模型,是通过一个容器来解决生产者和消费者的强耦合问题。通俗的讲,就是生产者在不断的生产,消费者也在不断的消费,可是消费者消费的产品是生产者生产的,这就必然存在一个中间容器,我们可以把这个容器想象成是一个货架,当货架空的时候,生产者要生产产品,此时消费者在等待生产者往货架上生产产品,而当货架满的时候,消费者可以从货架上拿走商品,生产者此时等待货架的空位,这样不断的循环。那么在这个过程中,生产者和消费者是不直接接触的,所谓的‘货架’其实就是一个阻塞队列,生产者生产的产品不直接给消费者消费,而是仍给阻塞队列,这个阻塞队列就是来解决生产者消费者的强耦合的。就是生产者消费者模型。

总结一下:生产者消费者能够解决的问题如下:

生产与消费的速度不匹配
软件开发过程中解耦
常用的实现方法

wait()方法

1.wait()是Object里面的方法,而不是Thread里面的,这一点很容易搞错。它的作用是将当前线程置于预执行队列,并在wait()所在的代码处停止,等待唤醒通知。
2.wait()只能在同步代码块或者同步方法中执行,如果调用wait()方法,而没有持有适当的锁,就会抛出异常。
wait()方法调用后悔释放出锁,线程与其他线程竞争重新获取锁。
notify()方法

1.notify()方法也是要在同步代码块或者同步方法中调用的,它的作用是使停止的线程继续执行,调用notify()方法后,会通知那些等待当前线程对象锁的线程,并使它们重新获取该线程的对象锁,如果等待线程比较多的时候,则有线程规划器随机挑选出一个呈wait状态的线程。
2.notify()调用之后不会立即释放锁,而是当执行notify()的线程执行完成,即退出同步代码块或同步方法时,才会释放对象锁。
notifyAll()方法

唤醒所有等待的线程
阻塞队列的实现
通过 "循环队列" 的方式来实现.
使用 synchronized 进行加锁控制.
put 插入元素的时候, 判定如果队列满了, 就进行 wait. (注意, 要在循环中进行 wait. 被唤醒时不一定
队列就不满了, 因为同时可能是唤醒了多个线程).
take 取出元素的时候, 判定如果队列为空, 就进行 wait. (也是循环 wait)
代码示例

   //多消费者多生产者
public class BlockingQueue {
    private int[] items = new int[1000];
    private volatile int size = 0;
    private int head = 0;//永远在队列第一个元素的位置
    private int tail = 0;//永远队列最后一个元素的下一个位置
    public void put(int value) throws InterruptedException {
        synchronized (this) {
            // 此处最好使用 while.
            // 否则 notifyAll 的时候, 该线程从 wait 中被唤醒,
            // 但是紧接着并未抢占到锁. 当锁被抢占的时候, 可能又已经队列满了
            // 就只能继续等待
            while (size == items.length) {
                wait();
            }
            items[tail] = value;
            tail = (tail + 1) % items.length;
            size++;
            notifyAll();//唤醒所有的消费者
        }
    }
    public int take() throws InterruptedException {
        int ret = 0;
        synchronized (this) {
            while (size == 0) {
                wait();
            }
            ret = items[head];
            head = (head + 1) % items.length;
            size--;
            notifyAll();//唤醒所有生产者
        }
        return ret;
    }
    public synchronized int size() {
        return size;
    }
    // 测试代码
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue blockingQueue = new BlockingQueue();
        Thread customer = new Thread(() -> {
            while (true) {
                try {
                    //消费者取出产品
                    int value = blockingQueue.take();
                    System.out.println(value);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "消费者");
        customer.start();
        Thread producer = new Thread(() -> {
            Random random = new Random();
            while (true) {
                try {
                    //生产者存入产品
                    blockingQueue.put(random.nextInt(100));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "生产者");
        producer.start();
        customer.join();
        producer.join();
    }
}
 

#我要进大厂##Java#
全部评论
说实话我是想去大厂
点赞 回复 分享
发布于 2022-08-18 12:56 陕西
哥们比特的把
点赞 回复 分享
发布于 2022-10-09 18:55 陕西

相关推荐

头像
10-16 09:58
已编辑
门头沟学院 Java
点赞 评论 收藏
分享
10-28 11:04
已编辑
美团_后端实习生(实习员工)
一个2人:我说几个点吧,你的实习经历写的让人觉得毫无含金量,你没有挖掘你需求里的 亮点, 让人觉得你不仅打杂还摆烂。然后你的简历太长了🤣你这个实习经历看完,估计没几个人愿意接着看下去, sdk, 索引这种东西单拎出来说太顶真了兄弟,好好优化下简历吧
点赞 评论 收藏
分享
评论
点赞
4
分享
牛客网
牛客企业服务