想进大厂,单例模式和阻塞队列你了解吗?
单例模式
概念
单例模式是校招中最长考的设计模式之一,所以我们先介绍什么是设计模式。
设计模式:软件开发中有许多常见的“问题场景”,比如各种各样的工厂它们的运营模式都是类似,针对这些问题场景,大佬们总结出了一些固定的套路,并且用代码实现了基本模板。
单例模式:能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。例如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#
概念
单例模式是校招中最长考的设计模式之一,所以我们先介绍什么是设计模式。
设计模式:软件开发中有许多常见的“问题场景”,比如各种各样的工厂它们的运营模式都是类似,针对这些问题场景,大佬们总结出了一些固定的套路,并且用代码实现了基本模板。
单例模式:能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。例如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#