h5响应式网站如何修改首页,湛江企业网站建站模板,wordpress网站添加密码访问,绍兴seo网站推广文章目录 单例模式什么是单例模式饿汉模式懒汉模式多线程- 懒汉模式分析多线程问题第一种添加sychronized的方式第二种添加sychronized的方式改进第二种添加sychronized的方式#xff08;DCL检查锁#xff09; 阻塞队列什么是阻塞队列什么是消费生产者模型标准库中的阻塞队列… 文章目录 单例模式什么是单例模式饿汉模式懒汉模式多线程- 懒汉模式分析多线程问题第一种添加sychronized的方式第二种添加sychronized的方式改进第二种添加sychronized的方式DCL检查锁 阻塞队列什么是阻塞队列什么是消费生产者模型标准库中的阻塞队列消息队列应用的场景自己模拟实现阻塞队列 定时器标准库中的定时器实现定时器 工厂模式线程池线程池的一些问题实现一个线程池创建系统自带的线程池 wait和sleep的区别 单例模式
什么是单例模式
单例模式能保证某个类在程序中只存在唯⼀⼀份实例, ⽽不会创建出多个实例单例模式实现方式很多最常用饿汉模式和懒汉模式实现
饿汉模式
创建过程 – 1. 定义一个static修饰的变量就可以包子这个变量全局唯一 – 2.构造方法私有化防止变量被修改 – 3.提供一个获取变量的get静态方法通过类名的方式去调用
public class Singleton {//懒汉模式//创建一个私有静态属性并且把对象new出来private static Singleton instance new Singleton();//私有化构造器private Singleton() {}//提供一个公共的静态方法返回单例对象public static Singleton getInstance() {return instance;}public static void main(String[] args) {Singleton s1 Singleton.getInstance();Singleton s2 Singleton.getInstance();System.out.println(s1 s2); // true}
}
把这种类加载时候就完成对象的初始化的创建方式就叫”饿汉模式“这种模式存在的问题是可能对象创建了但是没有使用从而导致资源浪费。
懒汉模式
public class SingLetonLazy {//创建一个对象不去new对象private static SingLetonLazy instance;//私有化构造器private SingLetonLazy() {}//提供一个公共的静态方法返回单例对象public static SingLetonLazy getInstance() {if(instancenull) {instancenew SingLetonLazy();}return instance;}public static void main(String[] args) {SingLetonLazy s1 SingLetonLazy.getInstance();SingLetonLazy s2 SingLetonLazy.getInstance();System.out.println(s1 s2); // true}
}
懒汉模式创建对象在要获得单例对象的时候创建避免了资源的浪费但是存在多线程安全问题。
多线程- 懒汉模式
public class SingLetonLazy {private static SingLetonLazy instance;//私有化构造器private SingLetonLazy() {}//提供一个公共的静态方法返回单例对象public static SingLetonLazy getInstance() {if(instancenull) {instancenew SingLetonLazy();}return instance;}public static void main(String[] args) {for (int i 0; i 10; i) {Thread t1 new Thread(()-{try {sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}SingLetonLazy s1 SingLetonLazy.getInstance();System.out.println(s1);});t1.start();}}
} 出现了多线程问题。
分析多线程问题 第一种添加sychronized的方式
public static SingLetonLazy getInstance() {if(instancenull) {synchronized (SingLetonLazy.class){instancenew SingLetonLazy();}}return instance;}这种写法不能保证多线程安全
第二种添加sychronized的方式
public static SingLetonLazy getInstance() {synchronized (SingLetonLazy.class){if(instancenull) {instancenew SingLetonLazy();}}return instance;}这种写法似乎可以保证多线程安全但是还是存在一个问题当一个线程进行这个方法如果没有初始化则获取锁进行初始化操作此时单例对象被第一个线程创建完成后面的线程以后永远不会在执行new对象的操作synchronized就没必要添加了第二次线程开始这个加锁解锁都是无效的操作lock和unlock对应的锁指令是互斥锁比较消耗系统资源。添加锁本质就会消耗很多资源
改进第二种添加sychronized的方式DCL检查锁 private volatile static SingLetonLazy instance;//给共享变量加上volatilepublic static SingLetonLazy getInstance() {//第一次判断是否加锁if(instancenull) {synchronized (SingLetonLazy.class) {判断是否创建了一个对象if (instance null) {instance new SingLetonLazy();}}}return instance;}阻塞队列
什么是阻塞队列
阻塞队列本质还是队列遵循”先进先出“的原则阻塞队列是一种线程安全的数据结构有以下特征 – 当队列满的时候继续入队就会发生阻塞等待直到队列中有其他线程取出元素后队列有空位才会再次入队 – 当队列空的时候继续出队就会放生阻塞等待知道队列中有其他线程插入元素时候队列有元素才会再次出队阻塞队列适用于一种典型场景‘消费生产者模型’
什么是消费生产者模型
生产者消费者模式就是通过一个容器解决消费者和生产者的强耦合问题。生产者和消费者不会直接影响生产者生产的资源直接放入容器阻塞队列中消费者消费的资源直接从容器阻塞队列中拿。从而保证生产者不会生产资源等待消费者消费消费者也不会等待生产者生产资源。阻塞队列相当于一个缓冲区平衡生产者和消费者的处理能力 – 比如双11时候会涌入大量的支付订单这时候如果服务器直接处理这些订单可能就会把服务器挤爆这时候中间设置一个阻塞队列把产生的大小支付订单扔进阻塞队列里面然后服务器根据自己的处理能力从队列里面取出要处理的订单从而达到削峰的效果防止服务器被挤爆。阻塞队列也能使生产者和消费者之间 解耦 – 过年期间大家都会包饺子擀饺子皮相当于生产者包饺子相当于消费者中间放个案板所有的饺子皮都放在案板上包饺子皮的人直接从案板上取擀饺子皮的可能是妈妈可能是爸爸可能是我无论是谁擀饺子皮消费者都不关心因为都是从案板上取的饺子皮。
标准库中的阻塞队列
在 Java 标准库中内置了阻塞队列. 如果我们需要在一些程序中使用阻塞队列, 直接使用标准库中的即可. – BlockingQueue 是一个接口. 真正实现的类是 LinkedBlockingQueue. – put 方法用于阻塞式的入队列, take 用于阻塞式的出队列. – BlockingQueue 也有 offer, poll, peek 等方法, 但是这些方法不带有阻塞特性.创建一个BlockingQueue – 其中capacity是这个队列的大小。 – 这里设置一个三个大小的阻塞队列当第四个元素入队时候就会发生阻塞等待 – 这里取出三个元素后队列为空队列阻塞等待 – put和take都会抛出一个InterrupteException异常
其他补充常问的方法 – add – offer – remove – poll
消息队列应用的场景 解耦 – 高内聚低耦合业务强相关的代码组织在一起不相关的单独定义便于以后的维护以为要把重复的代码尽量抽象出来封装成一个公共方法在需要的地方直接调用这个方法即可 – 生产消息的应用程序把消息写进消息队列生产者使用消息的应用程序从消息队列里面取出消息消费者 在这个模型中服务器A要时刻感应到服务器B在调用的过程中双方都要知道对方需要调用的参数和调用方式 在ABC整个调用的链路中秒如果其中一个出现了问题就会影响整个业务执行 削峰填谷流量 – 针对流量暴增的时候使用消息队列来进行缓冲 – 实例 异步操作 周末我和我女朋友取买包子
同步操作她一直等我买包子回来开始中间这个过程啥也不干同步发出请求后必须要等待响应才能- 进行下一步操作异步操作她让我去之后在家做点别的事情比如做点小菜熬点稀饭异步操作发出请求之后不需要等待响应而做其他的事情等待响应主动通知自己
自己模拟实现阻塞队列
public class MyBlockingDeque {int [] arr;volatile int head0;volatile int tail0;volatile int size0;MyBlockingDeque(int capacity){if(capacity0) {throw new RuntimeException(capacity must be positive);}arr new int[capacity];}public void put(int val) throws InterruptedException {while(sizearr.length) {synchronized (this){this.wait();}}arr[tail]val;tail;if(tailarr.length) {tail0;}size;synchronized (this){this.notifyAll();}}public synchronized int take() throws InterruptedException {while(size0) {this.wait();}int val arr[head];head;if(headarr.length) {head0;}size--;this.notifyAll();return val;}
}
class Main{public static void main(String[] args) throws InterruptedException {MyBlockingDeque myBlockingDeque new MyBlockingDeque(10);int i0;new Thread(()-{while (true){try {sleep(1000);int val myBlockingDeque.take();System.out.println(Thread.currentThread().getName()取出成功val);} catch (InterruptedException e) {e.printStackTrace();}}}).start();while (true){myBlockingDeque.put(i);System.out.println(Thread.currentThread().getName()添加成功i);i;}}
} put时候 take时候 我们上锁可以锁代码块也可以方法 if改为while的原因是防止大量现场
定时器
标准库中的定时器
标准库中定义一个TImer类。Timer类的核心方法为scheduleschedule包含两个参数第一个参数指定要执行的代码任务第二个参数指定多场实际之后执行。
import java.util.Timer;
import java.util.TimerTask;public class Demo_801 {public static void main(String[] args) {// 使用jdk中提供的类创建一个定器Timer timer new Timer();//向定时器添加任务timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(Hello World!);}},1000);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(任务1);}},1500);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(任务2);}},2000);timer.schedule(new TimerTask() {Overridepublic void run() {System.out.println(任务3);}},2500);}
}
ctrlp查看方法的参数列表 定义自己的任务 延迟多久执行的任务 任务具体执行的时间
实现定时器
设计思路
用一个类描述任务和执行任务的时间 – 具体任务逻辑用Runable表示执行时间可以用一个long型delay表示组织任务和时间对应的对象 – 可以考虑用一个阻塞队列我们选择用PriorityBlockingQueue(),保证扫描任务时候延时最少的任务先执行 提供一个方法提交任务 要有一个线程执行任务 – 在哪里定义扫描线程 –在构造方法里面直接定义线程 – 1.取出队首元素2.判断一下任务到执行的时间没有3如果到了就执行4.没有就放回队列
代码实现
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.PriorityBlockingQueue;public class MyTimer {//用一个阻塞队列来组织任务private BlockingQueueMyTask queue new PriorityBlockingQueue();private Object lock new Object();public MyTimer() {//创建线程Thread thread new Thread(()-{while(true){try {//从队列中取出任务MyTask taskthis.queue.take();//判断有没有到执行的时间long currentTimeSystem.currentTimeMillis();if(currentTimetask.getTime()){//时间到了执行task.getRunnable().run();} else{//时间没到将任务放回队列中this.queue.put(task);}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();}/*** 添加定时任务* param runnable 任务* param delay 延时* throws InterruptedException*/public void schedule(Runnable runnable,long delay) throws InterruptedException {//根据传的参数构造一个MyTask对象MyTask tasknew MyTask(runnable,delay);//将这个MyTask对象阻塞放入队列中queue.put(task);}
}//MyTask类用于封装任务和执行时间
class MyTask implements ComparableMyTask{//任务private Runnable runnable;//执行时间private long time;public MyTask(Runnable runnable, long delay) {//增强代码健壮性if(runnablenull){throw new IllegalArgumentException(任务不能为空);}if(delay0) {throw new IllegalArgumentException(延迟时间不能为负数);}this.runnable runnable;//计算出任务的执行时间this.time delaySystem.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}Overridepublic int compareTo(MyTask o) {if(this.getTime()o.getTime()){return -1;} else if(this.getTime()o.getTime()){return 0;}else {return 1;}//万一时间超过了long的范围溢出怎么办用上面的比较比较好//return (int)(this.getTime()-o.getTime());}
}
public class Test {public static void main(String[] args) throws InterruptedException {MyTimer timer new MyTimer();timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务1);}},1000);timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务2);}},500);timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务3);}},2000);//timer.schedule(null,-100);//任务加强健壮性}
}
注意事项 – 注意我们要实现Conparable接口指定排序规则
– 我们要添加校验防止非法的输入
– 解决数据可能会溢出的问题比如设置的时间
再次深度优化我们代码
以上代码我们存在“忙等”的情况优化后的代码 – 这里注意一下这个lambda表达式中的this引用的是他所在对象的实例。新的问题当任务1在等待时候这时候如果又put进来一个新的任务这个等待的时间就有问题。再次优化 每添加新的任务都进行一次唤醒保证执行的永远是最少延时的任务。从CPU调度的过程中可以会产生的执行顺序的问题或当一个线程执行到一半的时间被掉调度走的现象。 这个线程造成的原因就是没有保证原子性。优化代码 再次观察一种极端情况 – 我们发现当我们把三个任务的延时时间设置为0的时候结果只执行了任务1我们进行调试 – 调试之后我们又发现是正常情况但是运行时候不符合我们的预期结果这时候我们不要慌我们用jconsole工具去查看下扫描情况 我们发现在MyTimer。java22行出现了问题
1.创建一个定时器 2.向定时器添加任务1 3.第一个任务被添加到阻塞队列中 4.扫描线程启动处理第一个任务 5.扫描线程1循环获得第二个任务时候队列为空开始等待同时扫描线程获得锁 6.主线程向阻塞队列添加任务时候等待扫描对象的对象由于扫描线程无法释放锁对象主线程也就获取不到锁对象造成相互等待造成死锁
我们再次优化代码创造一个后台扫描线程只做定时唤醒操作定时1秒或者10ms唤醒一次 -最终的代码
public class MyTimer {//用一个阻塞队列来组织任务private BlockingQueueMyTask queue new PriorityBlockingQueue();private Object lock new Object();public MyTimer() {//创建线程Thread thread new Thread(()-{while(true) {try {synchronized (this) {//从队列中取出任务MyTask task this.queue.take();//判断有没有到执行的时间long currentTime System.currentTimeMillis();if (currentTime task.getTime()) {//时间到了执行task.getRunnable().run();} else {//时间没到将任务放回队列中long waitTime task.getTime() - currentTime;this.queue.put(task);this.wait(waitTime);}}} catch (InterruptedException e) {throw new RuntimeException(e);}}});thread.start();//创建守护线程,定时唤醒一次Thread deamonThreadnew Thread(()-{synchronized (this) {//唤醒一次this.notifyAll();//每隔100ms唤醒一次try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}}});//设置为守护线程deamonThread.setDaemon(true);deamonThread.start();}/*** 添加定时任务* param runnable 任务* param delay 延时* throws InterruptedException*/public void schedule(Runnable runnable,long delay) throws InterruptedException {//根据传的参数构造一个MyTask对象MyTask tasknew MyTask(runnable,delay);//将这个MyTask对象阻塞放入队列中queue.put(task);System.out.println(任务添加成功);}
}//MyTask类用于封装任务和执行时间
class MyTask implements ComparableMyTask{//任务private Runnable runnable;//执行时间private long time;public MyTask(Runnable runnable, long delay) {//增强代码健壮性if(runnablenull){throw new IllegalArgumentException(任务不能为空);}if(delay0) {throw new IllegalArgumentException(延迟时间不能为负数);}this.runnable runnable;//计算出任务的执行时间this.time delaySystem.currentTimeMillis();}public Runnable getRunnable() {return runnable;}public long getTime() {return time;}Overridepublic int compareTo(MyTask o) {if(this.getTime()o.getTime()){return -1;} else if(this.getTime()o.getTime()){return 0;}else {return 1;}//万一时间超过了long的范围溢出怎么办用上面的比较比较好//return (int)(this.getTime()-o.getTime());}
public class Test {public static void main(String[] args) throws InterruptedException {MyTimer timer new MyTimer();timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务1);}},0);timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务2);}},0);timer.schedule(new Runnable(){Overridepublic void run() {System.out.println(任务3);}},0);//timer.schedule(null,-100);//任务加强健壮性}
}
工厂模式
先看出现的问题 我们这里造成了重载参数的相同但是我们就是要这样的构造方法我们怎么解决呢 工厂方法模式。根据不同的业务需求定义不同的方法来获取对象。
线程池
线程池的一些问题
什么是线程池 1.线程池就是一次创建多个线程把这些线程放进一个池中用的时候从池中取出用完就还回去为什么要用线程池 我们首先要明白线程的创建和销毁都会消耗大量的资源线程池中的线程当有任务的时候就会执行任务没有任务的时候就阻塞等待并不销毁线程线程池最⼤的好处就是减少每次启动、销毁线程的损耗。为什么使用线程池可以提升效率 少量创建少量销毁创建一个线程要分为内核态和用户态用户态相当于jvm层面内核太相当于操作系统层面当我们在jvm层面创建一个线程就要在操作系统层面创建对应指令就会消耗大量资源消耗线程也如此所以线程池减少了频繁的销毁和创建用的时候就直接在线程池里面用已经创建多的从而提升效率。怎么用 – jdk给我们提供了一组针对不同场景的线程池实例
public static void main(String[] args) {//1.用来处理大量短时间的任务的线程池如果池没有可用的线程将创建线程如果线程空闲60秒将收回并移除缓存ExecutorService cachedThreadpool Executors.newCachedThreadPool();//2.创建一个操作无界队列线程池大小固定的线程池ExecutorService fixedThreadpool Executors.newFixedThreadPool(5);//可以指定线程数量//3.创建一个操作无界队列只有一个线程的线程池ExecutorService singleThreadExecutor Executors.newSingleThreadExecutor();//4.创建一个单线程执行器可以加时间给定时间后执行或者定期执行ScheduledExecutorService singleThreadScheduledExecutor Executors.newSingleThreadScheduledExecutor();//5.创建一个指定大小的线程池可以加时间给定时间后执行或者定期执行ScheduledExecutorService scheduledThreadpool Executors.newScheduledThreadPool(5);//6.创建一个指定大小不传参为当前机器的cpu核数的线程池并行处理任务不保证处理顺序Executors.newWorkStealingPool();
}Runtime.getRuntime().availableProcessors()
获取系统的cpu核数实现一个线程池
先构思思路先描述再组织
用Runable描述任务组织管理任务可以使用一个队列可以使用阻塞队列取实现提供一个向队列的添加任务的方法创建多个线程扫描队列里面的任务有任务时候执行没有任务时候等待
public class MyExectorService {//定义阻塞队列阻止任务private BlockingQueueRunnable queuenew LinkedBlockingQueue(100);private static Object locknew Object();public MyExectorService(int threadNum){for (int i 0; i threadNum; i){Thread threadnew Thread(()-{//不停扫描队列while (true) {try {synchronized (lock){Runnable runable queue.take();runable.run();}TimeUnit.MILLISECONDS.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}}//take()方法会阻塞直到队列中有任务});//启动线程thread.start();}}/*** 提交任务到线程池中* param runnable 具体的任务* throws InterruptedException*/public void sumbit(Runnable runnable) throws InterruptedException {if(runnablenull){throw new IllegalArgumentException(任务不能为空);}//把任务加入队列中queue.put(runnable);}}
class Test1{public static void main(String[] args) throws InterruptedException {MyExectorService myExectorServicenew MyExectorService(3);AtomicInteger j new AtomicInteger();for (int i 0; i 10; i) {myExectorService.sumbit(() - {System.out.println(Thread.currentThread().getName() j);j.getAndIncrement();});if(i%30){TimeUnit.SECONDS.sleep(1);}}}
}
创建系统自带的线程池
前面jdk提供的线程池比较固定也就是说我们不能自己定制但是我们看底层代码时发现这些线程池都是对ThreadPoolExecutor的封装 那我们可以根据ThreadPoolEecutor创建一个自定义线程池 用现实的两个例子去模拟线程工作的原理 周末去吃饭 银行办理业务
线程池的拒绝策略详解 我们注意一下3和4是不会抛出异常的1和2是会抛出异常的放弃的任务永远都找不回来所以指定拒绝策略的时候要关注任务是不是必须要执行如果必须要执行就指定“返回调用者”否则选134一个即可
public static void main(String[] args) {ThreadPoolExecutor threadPoolnew ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue(7),new ThreadPoolExecutor.AbortPolicy());for (int i 0; i 100; i) {int takeIi;threadPool.submit(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行任务 takeI);}});}}直接拒绝 实际只执行几个后面的都没执行 返回给调用者 有一部分代码返回给调用者main执行了 public static void main(String[] args) {ThreadPoolExecutor threadPoolnew ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue(7),new ThreadPoolExecutor.CallerRunsPolicy());for (int i 0; i 100; i) {int takeIi;threadPool.submit(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行任务 takeI);}});}}放弃最早的任务
public static void main(String[] args) {ThreadPoolExecutor threadPoolnew ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue(7),new ThreadPoolExecutor.DiscardOldestPolicy());for (int i 0; i 100; i) {int takeIi;threadPool.submit(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行任务 takeI);}});}}放弃最新的任务
public static void main(String[] args) {ThreadPoolExecutor threadPoolnew ThreadPoolExecutor(2,5,10, TimeUnit.SECONDS,new LinkedBlockingQueue(7),new ThreadPoolExecutor.DiscardPolicy());for (int i 0; i 100; i) {int takeIi;threadPool.submit(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName() 执行任务 takeI);}});}}wait和sleep的区别
1.共同点让线程休眠一会 2.从实现使用上来说是两种不同的方法 wait是Object类的方法与锁相关配合sychronized一起使用调用wait之后会释放锁 sleep是Thread类的方法与锁无关 wait可以通过notify和指定直线的方法唤醒唤醒之后重新竞争锁资源 sleep只能通过超时时间唤醒
补充