羽毛球网站建设网站,太湖县网站建设公司,网站建设自查及整改报告,厦门网站建设价格xm37❤️ Author#xff1a; 老九 ☕️ 个人博客#xff1a;老九的CSDN博客 #x1f64f; 个人名言#xff1a;不可控之事 乐观面对 #x1f60d; 系列专栏#xff1a; 文章目录 实现安全版本的单例模式饿汉模式类和对象的概念类对象类的静态成员与实例成员 懒汉模式如何保证… ❤️ Author 老九 ☕️ 个人博客老九的CSDN博客 个人名言不可控之事 乐观面对 系列专栏 文章目录 实现安全版本的单例模式饿汉模式类和对象的概念类对象类的静态成员与实例成员 懒汉模式如何保证懒汉模式的线程安全 阻塞队列让多个服务器之间充分解耦能让请求进行 削峰填谷标准库中的阻塞队列自己实现阻塞队列 定时器标准库计时器 线程池用户态和内核态标准的线程池库 实现安全版本的单例模式
单例模式是设计模式之一。代码当中的某个类只能有一个实例不能有多个。单例模式分为饿汉模式和懒汉模式
饿汉模式
饿汉模式表示很着急就想吃完饭剩下很多碗然后一次性把碗全洗了。就是比较着急的去创建实例。用static来创建实例利用在类加载时初始化只有一份拷贝存在于内存中的特性实现单例模式并且立即进行实例化。下面代码中的instance对应的实例就是该类唯一的实例
class Singleton{public static Singleton instance new Singleton();private Singleton(){}public static Singleton getInstance(){return instance;}
}public class Example{public static void main(String[] args) {Singleton instance Singleton.getInstance();Singleton instance2 Singleton.getInstance();System.out.println(instance instance2);}
}为了防止程序员在其他地方不小心new这个Singleton于是把构造方法设为private了
类和对象的概念
类是对象的模板描述了对象的行为和状态。 对象是类的实例它是在内存中分配的实体具有实际的属性和行为。
类对象
在Java中每个类在加载到内存后都会有一个对应的类对象。这个类对象存储了类的相关信息包括类的名称、方法、属性等。 类对象是Java虚拟机JVM在运行时对类的抽象表示。
类的静态成员与实例成员
静态成员类属性/类方法是与类关联的而不是与类的实例相关联的。它们在类加载时初始化并且只有一份拷贝存在于内存中被所有类的实例共享。 实例成员实例属性/实例方法是与类的实例相关联的每个类的实例都有自己的一份实例成员。
懒汉模式
懒汉模式主要是不立即初始化实例只有在被调用的时候才会创建实例
如何保证懒汉模式的线程安全
加锁把创建实例的代码加锁就可以了加锁的时候可以直接指定类对象.class作为锁对象。加锁之后线程安全问题得到了解决但是又有了新的问题。在多线程调用获取信息的时候可能涉及到读和修改但是一旦实例被初始化之后就只剩读操作了。
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;}
}public class Example{public static void main(String[] args) {Singleton instance Singleton.getInstance();Singleton instance2 Singleton.getInstance();System.out.println(instance instance2);}
}为什么要两次判断 因为在并发环境中多个线程可能会同时通过第一次检查此时可能会出现多个线程都创建实例的情况。第二次检查可以确保只有一个线程能够创建实例保证了单例模式的唯一性。 为什么需要加volatile 因为如果有多个线程的话都去读getInstance就可能导致内存可见性的问题所以需要加上volatile来避免内存可见性问题 和饿汉模式的区别就是懒汉模式只有在使用的时候才会创建实例饿汉模式在类加载的时候就会创建实例。
阻塞队列
队列的特性是先进先出相对于普通队列阻塞队列又有其他方面的功能
线程安全产生阻塞效果 a. 如果队列为空尝试出队列就会出现阻塞阻塞到队列不为空为止。 b. 如果队列为满尝试入队列就会出现阻塞阻塞到队列不为满为止。
通过上面这种特性就可以实现 “生产者消费者模型” 。就像我们烤串有人烤有人吃然后烤好的放在烤盘上面。对于吃烤串来说烤盘就是交易场所。此处的阻塞队列就可以作为生产者消费者模型当中的交易场所。
让多个服务器之间充分解耦
生产者消费者模型是实际开发当中非常有用的一种多线程开发手段尤其是在服务器开发场景当中。假设有两个服务器 A 和 BA 作为入口服务器直接接受用户的网络请求B 作为应用服务器来给 A 提供一些数据。如图 如果不使用生产者消费者模型此时 A 和 B 的耦合性是比较强的。在开发 A 代码的时候就得充分了解到 B 提供的一些接口开发 B 代码的时候也得充分了解到 A 是怎么调用的。一旦想把 B 换成 C A 的代码就需要较大的改动。而且如果 B 挂了也可能直接导致 A 也顺带挂了。 使用生产者消费者模型就可以降低这里的耦合就像这样
能让请求进行 “削峰填谷”
未使用生产者消费者模型的时候如果请求量突然暴涨。A 暴涨 B 暴涨A 作为入口服务器计算量较小不会产生问题。B 作为应用服务器计算量可能很大需要的系统资源也更多如果请求更大了就可能导致程序挂了。如图 如果使用阻塞队列的话A 的请求暴涨 阻塞队列的请求暴涨由于阻塞队列没啥计算量只是存数据所以抗压能力就更强。B 这边依然按照原来的速度进行处理数据就不会受到 A 的暴涨。所以就不会引起崩溃。也就是 “削峰”。这种峰值很多时候不是持续的过去之后就恢复了。B 仍然是按照原有的频率来处理之前积压的数据就是 “填谷” 。 实际开发当中阻塞队列不是一个简单的数据结构了而是一个/一组专门的服务器程序提供的功能不仅仅是队列阻塞。还会在这些基础上面提供更多的功能数据持久化存储多个数据通道多节点备份支持控制面板方便配置参数又叫”消息队列“。
标准库中的阻塞队列
通过 BlockingQueue 来实现阻塞队列代码如下
public class Example{public static void main(String[] args) throws InterruptedException {BlockingDequeString stringBlockingDeque new LinkedBlockingDeque();//入队stringBlockingDeque.put(hello);//出队String s stringBlockingDeque.take();System.out.println(s);}
}自己实现阻塞队列
1.先实现一个普通队列通过数组来实现 2.再加上线程安全 3.再加上阻塞 实现一个普通队列 出队列就是把 head 位置的元素返回去并且 head。当 tail 加满的时候就回到队列头。所以重要的就是区别空队列和满队列。所以我们创建一个变量来记录元素的个数size 0 就是空size arr.length 就是满。 保证线程安全 1.在多线程环境下使用入队和出队没有问题。 2.入队和出队的代码是属于公共操作变量所有给整个方法加锁。 实现阻塞效果 通过使用 wait 和 notify 机制来实现阻塞效果。 对于 入队 来说就是队列为满。 对于 出队 来说就是队列为空。 代码如下
class MyBlockQueue{private int[] data new int[1000];private int size 0;private int head 0;private int tail 0;private Object locker new Object();//入队列public void put(int value) throws InterruptedException {synchronized (locker){if(size data.length){//put 当中的 wait 要由 take 来唤醒只要 take 成功一个元素就可以唤醒了locker.wait();}//队列不满把新的元素放入 tail 位置上data[tail] value;tail;//处理 tail 到达数组末尾的情况if(tail data.length){tail 0;}size;locker.notify();}}//出队列public Integer take() throws InterruptedException {synchronized (locker){if(size 0){//说明队列为空就需要等待就需要 put 来唤醒locker.wait();}int ret data[head];head;if(head data.length){head 0;}size--;//就说明 take 成功了。然后唤醒 put 中的等待。locker.notify();return ret;}}
}public class Example{private static MyBlockQueue queue new MyBlockQueue();public static void main(String[] args) {//如果有多个生产者和多个消费者就再多创建几个线程Thread producer new Thread(()-{int num 0 ;while(true){try{System.out.println(生产了num);queue.put(num);num;}catch (InterruptedException e){e.printStackTrace();}}});producer.start();Thread customer new Thread(()-{while(true){int num 0;try{num queue.take();System.out.println(消费了num);//消费慢但是可以一直生产。1000 之后队列满了所以就阻塞了。直到消费了一个。Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});customer.start();}}put和take的相互唤醒之间的关系如下
定时器
像一个闹钟在一定时间之后被唤醒并执行某个之前设定好的任务。
标准库计时器
通过Timer的schedule任务来设计任务计划Timer内部有专门的线程来负责执行注册的任务所以执行完后并不会马上退出线程即使所有计划中的任务都已执行完毕这个内部线程也不会立即结束。它会继续运行等待其他可能的任务或直到显式地被取消。
public static void main(String[] args) {Timer timer new Timer();timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(Hello Timer);}}, 3000);//就是在 3 秒之后执行这个任务System.out.println(main);
}线程并没有结束因为 Timer 内部有专门的线程来负责执行注册的任务的。
线程池
因为进程比较重频繁的创建和销毁开销就会大解决方法进程池 or 线程 线程虽然比进程轻了但是如果创建和销毁的频率进一步增加发现开销还是有的解决方案线程池 or 协程。 线程池把线程提前创建好放到池子里需要的话就从池子里取。不用的话就放回池子里下次备用。这样创建销毁线程速度就快了。
用户态和内核态
操作系统中的用户态和内核态。操作系统软件结构图 1.我们写的代码就是在最上面的应用程序这一层来运行的这里的代码被称为”用户态“运行的代码 2.当应用程序需要执行一些底层操作例如文件访问网络通信线程管理等就需要调用操作系统中提供的API。这些API的内部实现会在内核态运行这是操作系统的核心部分 3.创建线程的本身就需要内核的支持创建线程的本质是在内核中搞个 PCB 加到链表里调用 Thread.start 归根结底也是要进入内核态来运行 4.线程池是一种高级的编程工具通常是在用户态实现的。线程池中的线程被预先创建并保留在池中而不需要频繁地创建和销毁线程。从线程池中获取线程执行任务时这个过程是在用户态中完成的不需要涉及到内核态。这提高了效率因为避免了频繁的内核态切换。 5.一般来说执行在用户态的操作比需要进入内核态的操作更高效因为内核态切换会涉及到更多的开销和复杂性。因此尽量减少进入内核态的操作对于提高程序性能是有益的。 6.线程池里面的线程一直保存在里面不会被内核回收。
标准的线程池库
ThreadPoolExecutor 是标准库的线程池构造方法有很多参数 最重要的还是这两个参数就是需要指定多少个线程可以通过性能测试判断出最合适的核心线程数和最大线程数 ♥♥♥码字不易大家的支持就是我坚持下去的动力♥♥♥ 版权声明本文为CSDN博主「亚太地区百大最帅面孔第101名」的原创文章