网站采编队伍建设,广州网站建设哪家比较好,如何做蛋糕,做广告牌子一)Semphore:限流器用我就对了 Java中信号量Semphore是把操作系统原生的信号量封装了一下#xff0c;本质就是一个计数器#xff0c;描述了 可用资源的个数#xff0c;主要涉及到两个操作 如果计数器为0了#xff0c;继续Р操作#xff0c;就会出现阻塞等待的情况 P操作:申… 一)Semphore:限流器用我就对了 Java中信号量Semphore是把操作系统原生的信号量封装了一下本质就是一个计数器描述了 可用资源的个数主要涉及到两个操作 如果计数器为0了继续Р操作就会出现阻塞等待的情况 P操作:申请一个可用资源计数器-1V操作:释放一个可用资源计数器1 停车场门口有一个灯牌会显示停车位还剩余多少个每进去一辆车显示的停车位数量就-1就相当于进行了一次P操作每出去一辆车 显示的停车位数量就1就相当于进行了一次V操作而当停车场的剩余车位为0时显示的停车位数量就为0了 1)创建Semaphore示例, 初始化为4, 表示有4个可用资源. 2)acquire方法表示申请资源(P操作), release方法表示释放资源(V操作) public class Main{public static void main(String[] args) {Semaphore semaphorenew Semaphore(10);Runnable runnablenew Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName()开始申请资源);try {semaphore.acquire();System.out.println(Thread.currentThread().getName()已经获取到资源了);semaphore.release();System.out.println(Thread.currentThread().getName()开始释放资源了);} catch (InterruptedException e) {throw new RuntimeException(e);}}};for(int i0;i10;i){Thread tnew Thread(runnable);t.start();}}
}public class Main{public static int count0;public static void main(String[] args) throws InterruptedException {Semaphore semaphorenew Semaphore(1);Thread t1new Thread(()- {for (int i 0; i 10000; i) {try {semaphore.acquire();count;semaphore.release();} catch (InterruptedException e) {throw new RuntimeException(e);}}});Thread t2new Thread(()-{for(int i0;i10000;i){try {semaphore.acquire();count;semaphore.release();} catch (InterruptedException e) {throw new RuntimeException(e);}}});t1.start();t2.start();t1.join();t2.join();System.out.println(count);}
}基于Semphore可以实现限流器: 什么是限流:比如说某一个广场他的日载流量是6W那么如果说假设有一天来了10W人但是只能进去6W人这个时候就只能排队入园了因为工作人员始终会将人数控制在6W人 咱们再从生活中的事例回到程序当中假设一个程序只能为 10W 人提供服务突然有一天因为某个热点事件造成了系统短时间内的访问量迅速增加到了 50W那么导致的直接结果是系统崩溃任何人都不能用系统了显然只有少人数能用远比所有人都不能用更符合预期因此这个时候要使用限流了 Semphore本身是依靠计数器的思想来进行实现的它可以控制对于共享资源的访问数量当线程需要访问该资源的时候他必须先进行获取一个许可就是从计数器中获取到资源 当计数器本身大于0的时候线程可以获取到这个可用资源并且能够继续执行 当计数器本身等于0的时候线程将会被阻塞直到有其他的线程释放资源 Semphore本身有两个重要的操作acquire()和realse()操作 1)当线程需要访问共享资源的时候它会调用acquire方法来获取资源如果计数器的值大于0那么acquire()方法会将计数器的值减1并且允许线程继续运行如果计数器的值等于0那么acquire()方法会使得线程阻塞知道有其他线程释放资源 2)当线程使用完成共享资源以后该线程可以调用realse方法来释放资源realse()方法会使得计数器的值1表示有一个资源可以使用其他被阻塞的线程可以有机会获得可用资源并且1 关于公平模式和非公平模式: 在这里面所谓的公平模式就是说线程调用acquire的先后顺序来获取到这个可用资源的公平模式遵循先进先出原则所以非公平模式是抢占式的也就是说有可能一个新的获取线程恰好在一个许可证释放以后得到了这个许可证但是这个已经获取许可证的线程前面还存在着一些其他的线程当然在这里面非公平模式的性能比较高 假设说当有时候需要等待某一些线程执行完成了之后再来执行主线程的代码此时应该怎么做呢可能有人会说简单用 join() 方法等待线程执行完成之后再执行主线程就行了当然如果使用的是 Thread 来执行任务那这种写法也是可行的。然而真实的编码环境中我们是不会使用 Thread 来执行多任务的而是会使用线程池来执行多任务这样可以避免线程重复启动和销毁所带来的性能开销 二)CountDownLatch:别急等人齐了在开团 撞线:调用latch.countDown() 比赛结束统计成绩:latch.await()只要还存在着有任意的一个选手不进行撞线那么比赛就无法结束只有说所有的选手比赛撞了线那么最终的比赛才可以结束 public class Main {public static void main(String[] args) throws InterruptedException {CountDownLatch latchnew CountDownLatch(10);for(int i0;i10;i){Thread tnew Thread(()-{System.out.println(线程Thread.currentThread().getName()开始起跑);try {Thread.sleep(new Random().nextInt(10000));System.out.println(线程Thread.currentThread().getName()开始撞线);latch.countDown();} catch (InterruptedException e) {throw new RuntimeException(e);}});t.start();}latch.await();System.out.println(比赛完成);}
} 1)使用CountDownLatch可以实现等待所有任务执行完成以后再来执行主任务的功能他就是类似于说好像比赛中等待所有运动员都完成比赛以后再来公布排名一样当然咱们在大玩着荣耀的时候也是一样只有说所有人集合完毕以后在开团 2)而CountDownLatch就是通过计数器来实现等待功能的当创建CountDownLatch的时候会创建一个大于0的计数器每一次调用countDown()方法的时候计数器的值会减1直到计数器的值变成0以后等待的任务就可以继续执行了 3)countDownLatch在底层实现的时候是依靠内部创建并维护了一个voltaile的计数器当调用countDown()方法的时候会尝试将整数计数器-1CountDownLatch 在创建的时候需要传入一个整数在这个整数“倒数”到 0 之前主线程需要一直挂起等待直到其他的线程都执行之后主线才能继续执行 public static void main(String[] args) throws InterruptedException {//创建CountDownLatch实现两个计数器CountDownLatch latchnew CountDownLatch(2);//创建线程池执行任务ExecutorService service Executors.newFixedThreadPool(2);service.submit(new Runnable() {Overridepublic void run() {System.out.println(我是线程池提交的第一个任务);latch.countDown();}});service.submit(new Runnable() {Overridepublic void run() {System.out.println(我是线程池提交的第二个任务);latch.countDown();}});latch.await();System.out.println(线程池中的任务已经全部执行完成);} 三)循环栅栏(Cycbarrier):人齐了老司机就可以发车了 循环栅栏实现一个可以循环利用的屏障 https://img-blog.csdnimg.cn/img_convert/f10e1adb034e3ebaa2c02b11596386ee.gif 1)CycliBarrier作用是让一组线程之间可以相互等待当到达一个共同点的时候所有之前等待的线程会冲破栅栏一起向下执行 2)现在举个例子来说:伟哥要做末班车回家公交车站的司机会等待车上面的所有乘客坐满以后再来发车还有比如说王者荣耀得等待5个队友游戏都加载完了才可以进入到游戏 3)本质上来说是让多个线程共同相互等待知道说当所有的线程都到达了屏障点以后之前的所有线程才可以继续向下执行CycBarrier本身就象老司机开车一样如果车上面还有空闲的座位那么司机就得等着只有说当作为坐满以后老司机才发车 public static void main(String[] args) {CyclicBarrier barriernew CyclicBarrier(10, new Runnable() {Overridepublic void run() {System.out.println(现在司机上面的人都到齐了开始进行发车);System.out.println(当前线程池中的任务都已经执行完成了);}});ExecutorService serviceExecutors.newFixedThreadPool(10);for(int i0;i10;i){service.submit(new Runnable() {Overridepublic void run() {try {Thread.sleep(new Random().nextInt(5000));System.out.println(当前乘客开始上车Thread.currentThread().getName());barrier.await();//当前判断线程池中的任务执行完成可以执行多次System.out.println(当前线程下车Thread.currentThread().getName());} catch (InterruptedException e) {throw new RuntimeException(e);} catch (BrokenBarrierException e) {throw new RuntimeException(e);}}});}}
} 在CycliBarrier底层是基于计数器来实现的当count不为0的时候每一个线程在到达屏障点以后会先进行调用await()方法将自己阻塞此时计数器会减1此时这个线程会阻塞在这个屏障处当循环栅栏的计数器被减为0的时候所有调用await()的线程就会被唤醒就会冲破栅栏一起执行CountDownLatch和CycliBarrier在底层都是依靠计数器来实现的但是CountDownLatch只能使用一次但是CycliBarrier却可以使用多次这就是两者最大的区别 总结:CycliBarrier在底层是依靠ReentranLock来实现计数器的原子性更新的CycliBarrier最常使用的就是await()方法使用该方法就会将计数器的值减1并判断当前的计数器是否为0如果不是0就阻塞等待并且当计数器变成0以后该线程也就是阻塞在循环栅栏的线程才可以继续执行剩余任务 三)线程池的状态: 1)Running状态:运行状态线程池创建完成以后就进入到这个状态如果不手动调用关闭方法那么线程池在整个程序运行过程中都是这个状态 2)ShutDown状态:关闭状态线程池本身不再接受新任务的提交但是会有先将线程池中已经存在的任务处理完成 3)Stop停止状态:不再接受新任务的提交并且会中断正在执行的任务放弃任务队列中已经存在的任务 4)tidying状态:整理状态所有的任务都执行完成以后也包括任务队列中的任务执行完成当前线程池中的活动线程数降为0的状态到达此状态以后会调用线程池的terminated方法 5)terminated状态:销毁状态当调用线程池的terminated方法以后会进入到这个状态 ThreadPoolExecutor executornew ThreadPoolExecutor(10, 10,100, TimeUnit.SECONDS, new LinkedBlockingDeque(100), new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {return new Thread(r);}}){Overrideprotected void terminated() {super.terminated();System.out.println(线程池终止);}}; 1)当进行调用shutDown方法的时候线程池中的状态会由Running状态到达shutDown状态最后在到达tidying状态最后到达terminated状态 2)当进行调用shutDownNow方法的时候线程池中的状态会由running状态到达stop状态最后在到达tidying状态最后到达terminated状态 3)进行调用terminated方法线程池会直接从tidying状态到达terminated状态可以在阻塞队列的时候重写此方法默认来说这个方法是空的 四)如何判断线程池中的任务都已经执行完成了 1)在很多场景下都希望等待线程池中的所有任务都执行完然后再来执行下一步操作对于Thread类来说这样的实现是很简单的加上一个join方法就解决了但是对于线程池的判断就比较麻烦了 2)从上面的执行结果可以看出来程序先打印了任务执行完成再来继续打印并执行线程池的任务这种执行顺序混乱的结果不是我们想要看到的我们期望的结果就是等到鲜橙汁中的所有任务都执行完成了再来进行打印线程池执行完成的信息 3)产生少数问题的原因就是主线程main和线程池是并发执行的所以说当线程池还没有执行完main现成的打印结果就已经执行了想要解决这个问题就需要在打印结果之前先判断线程池中的任务是否已经执行完成如果没有执行完成就等到任务执行完成再来打印结果 public static void main(String[] args) {ExecutorService serviceExecutors.newFixedThreadPool(10);for(int i0;i10;i){service.submit(new Runnable() {Overridepublic void run() {System.out.println(开始执行线程池中的任务);}});}System.out.println(线程池中的所有任务执行完成);} 1)使用isTerminated()方法来判断: 1)使用线程池的终止状态来进行判断线程池中的任务是否已经全部执行完成但是如果想要让线程之中的状态改变就需要调用shutDown()方法不然线程池会一直处于Running运行状态那么就没有办法来进行判断是否处于终止状态来判断线程池中的任务是否已经全部执行 2)shutdown方法是启动线程池有序关闭的方法它在关闭之前会执行完成所有已经提交的任务并且不会再进行接收新的任务当线程池中的所有任务都执行完成以后线程池就处于终止状态了此时isTerminated()方法返回的结果也就是true了 缺点:需要关闭线程池 ThreadPoolExecutor executornew ThreadPoolExecutor(10, 10,100, TimeUnit.SECONDS, new LinkedBlockingDeque(100), new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {return new Thread(r);}}){Overrideprotected void terminated() {super.terminated();System.out.println(线程池终止);}};executor.submit(new Runnable() {Overridepublic void run() {System.out.println(执行任务1);}});executor.submit(new Runnable() {Overridepublic void run() {System.out.println(执行任务2);}});executor.shutdown();while(!executor.isTerminated()){}System.out.println(线程池中的任务已经执行完成了); 2)判断getCompletedTaskCount和getTaskCount是否相等 getTaskCount()返回执行计划任务的总数但是因为本身任务和线程的状态都在不断地发生变化因此返回的值是一个近似值 getCompetedTaskCount()返回完成执行的任务总数但是因为本身任务和线程的状态都在不断地发生变化因此返回的值是一个近似值但是在连续的调用中并不会减少 虽然不需要关闭线程池但是可能会造成一定的误差 3)调用countDownLatch和CycliBarrier 需要注意的是countDownLatch中的countDown()方法和CycliBarrier中的await()方法需要在线程池的run方法的最后调用 4)使用FutureTask FutureTask中的优势就是判断比较精准调用每一个线程的FutureTask的get方法就是等待该任务执行完成的需要使用submit进行提交: public static void main(String[] args) throws ExecutionException, InterruptedException {ThreadPoolExecutor executornew ThreadPoolExecutor(10,10,0,TimeUnit.SECONDS,new LinkedBlockingDeque(100));FutureTaskInteger task1new FutureTask(new CallableInteger() {Overridepublic Integer call() throws Exception {int a10;a;System.out.println(a完成);return a;}});FutureTaskInteger task2new FutureTask(new CallableInteger() {Overridepublic Integer call() throws Exception {int b11;b;System.out.println(b完成);return b;}});executor.submit(task1);executor.submit(task2);Integer result1 task1.get();Integer result2task2.get();System.out.println(线程池中的任务都已经执行完成);} 五)submit和execute的区别: 1)接收到的参数不同:submit方法只能接受到runnable接口的任务但是submit方法及可以接受到runnable方法的任务也可以接收到callablefutureTask类型的任务前者没有返回值后者可以后返回值 2)execute()的返回值是void线程提交后不能得到线程的返回值submit()的返回值是Future通过Future的get()方法可以获取到线程执行的返回值get()方法是同步的执行get()方法时如果线程还没执行完会同步等待直到线程执行完成 注意:虽然submit()方法可以提交Runnable类型的参数但执行Future方法的get()时线程执行完会返回null不会有实际的返回值这是因为Runable本来就没有返回值